This project is now discontinued.
This is because I came across the Drachtio project which shares the same vision and structure I had in mind for this project. Drachtio is only one component of this project - so this projected has morphed into other projects which will eventually off all the components required for a soft PBX.
- https://github.com/tinpotnick/babble-drachtio-callmanager
- https://github.com/tinpotnick/babble-drachtio-registrar
- https://github.com/tinpotnick/babble-projectrtp
- https://github.com/tinpotnick/projectrtp
There is more to do, including presence.
Project mediaswitch is designed to be a scalable VOIP switch. Forget a Swiss-army knife supporting 20 protocols bridging old ISDN routes with H323 or SIP. This project is designed to be slim, efficient and fast using an event-driven, asynchronous architecture. SIP, call control and RTP.
The design is broken down into 3 core components. Each component is primarily a single threaded architecture with I/O completion ports for network (think Nginx over Apache). This is to:
- Increase reliability by removing the possibility of race conditions
- Reduce the need to lock threads using mutex's or equivalents
- Increase the scalability - green threads with I/O completion ports have a track record of much better efficiency
Note re green threads - otherwise known as cooperative multi-threading. The one drawback with using this technique is your software has to cooperate with other threads - i.e. give up processing time. The SIP component uses C++ Boost ASIO and call control uses Node JS - both very good frameworks for this style. Both are high performance frameworks. RTP also uses C++ Boost ASIO.
For most users this means when you are writing Node scripts for your own call control scenarios, you must understand the asynchronous nature of Node and how to properly write asynchronous Javascript. You can lock up the whole control thread by not giving up processor time. However, when you get it right, you end up with a very efficient server.
The 3 main components:
- project-control: Call control server
- project-sip: A SIP server
- project-rtp: An RTP server
Each component can run either on the same server or on separate servers. This allows, for example, server 1 running SIP and Call control, then if you heavily transcode which increases the load of the RTP servers, multiple RTP servers.
Events are communicated via a HTTP event mechanism. i.e. if a new SIP call is generated by a client (which sends a SIP INVITE) then project-sip will pass a HTTP request back to the control server. Control server will then communicate back instructions to both the SIP server and the RTP server(s).
The design of this project is designed to work with cloud services so that workloads can be scaled up and down appropriately.
Part of the design is to be in-memory. i.e. the SIP server should no be required to query a database to get directory information. After starting, directory information should be pushed to the SIP server (either by the control server or your web site - a friendly host) - think memcache.
All three projects are designed to either run on the same physical server or separate servers. This way load balancing between servers can be achieved. Multiple RTP servers can then be run to handle large volumes of transcoding for every SIP and control server.
All services communicate with each other via HTTP. The following section defines the interfaces. In this section all of the examples use curl to get or post data.
Like project-sip, control has a http interface. This is simplified by the control library, writing call control scripts become simple:
/* Indicate ringing to the caller - did we really need a comment! */
call.ring();
The library can be included with
const projectcontrol = require( "projectcontrol" )
(note this will change when released!).
The SIP server requires user information to be uploaded to it.
projectcontrol.directory( "bling.babblevoice.com", [ { "username": "1003", "secret": "1123654789" } ] )
We can control which CODECs we allow. Suported: pcma, pcmu, 722, ilbc@20, 2833. (note ilbc and 2833 TODO).
projectcontrol.codecs = [ "722", "pcma", "pcmu", "ilbc", "2833" ]
We would like to be informed about new calls
projectcontrol.onnewcall = ( call ) =>
{
console.log( "new call" )
}
The 'call' object which is passed in contains internal information to track the call. Information regarding if it is ringing, answered, hungup etc. You can also set callback functions on the call
projectcontrol.onnewcall = ( call ) =>
{
console.log( "new call" )
call.onhangup = () =>
{
console.log( "hung up" )
}
/* Indicate ringing to the caller - not needed as the second leg ringing signal will be passed back */
call.ring()
/* Make a call */
if( "3" == call.destination )
{
call.newcall( { to: { user: "1003" } } )
}
}
Once projectcontrol has been configured run needs calling which places it all in its event loop.
projectcontrol.run()
List of setters for call backs in projectcontrol:
- onringing
- onanswer
- onhangup
These can be called multiple times and the callbacks will be stacked and all called.
Getters
- ringing
- answered
- hungup
Methods
- ring
- answer
- hangup (which takes the param reason, if left it is normal hangup, 'busy' is currently supported)
Similar to the sip server invite this call will originate a new call - but it will go through the call processing first (compared to the SIP interface which will blindly call the SIP endpoint).
Notify the control server of a SIP registration. Generated by the SIP server and sent to the control server configured using the directory interface.
PUT http://127.0.0.1:9001/reg/bling.babblevoice.com/1003
{
"host": "127.0.0.1",
"port": 45646,
"agent": "Z 5.2.28 rv2.8.114"
}
The host and port are the client network (where the request came from) and the agent is the agent string reported by the SIP client.
Example: curl -X POST --data-raw '{ "domain": "bling.babblevoice.com", "user": "1000" }' -H "Content-Type:application/json" http://control/reg
DELETE http://control/reg
Generated by the SIP server and sent to the control address against the domain configured using the directory interface. Notify the control server of a SIP de-registration.
DELETE http://127.0.0.1:9001/reg/bling.babblevoice.com/1003
Example using curl: curl -X DELETE --data-raw '{ "domain": "bling.babblevoice.com", "user": "1000" }' -H "Content-Type:application/json" http://control/reg
This interface is used to add directory information to the SIP server.
PUT http://127.0.0.1:9000/dir/bling.babblevoice.com
{
"control": "http://127.0.0.1:9001",
"users":
[
{
"username": "1003",
"secret": "1123654789"
}
]
}
Returns 201 on success.
Example using curl:
curl -X PUT --data-raw '{ "control": "http://127.0.0.1:9001", "users": [ { "username": "1003", "secret": "1123654789"}]}' -H "Content-Type:application/json" http://127.0.0.1/dir/bling.babblevoice.com
- domain: the name of the sip domain
- control: a structure of host and port of the control server responsible for this domain
- users: an array of user structures containing username and secret
This is synonymous with PATCH.
This will replace the user only. When PUT the domain, this replace the whole domain object.
PUT http://127.0.0.1:9000/dir/bling.babblevoice.com/1003
{
"secret": "1123654789"
}
curl -X PUT --data-raw '{ "secret": "1123654789" }' -H "Content-Type:application/json" http://127.0.0.1/dir/bling.babblevoice.com/1003
Returns JSON listing this domains entry in the directory.
Remove the entry in the directory. The user can also be specified - /dir/bling.babblevoice.com/1003.
GET http://sip/reg
Returns the number of registered clients.
The example:
GET http://127.0.0.1:9000/reg/bling.babblevoice.com
or to filter for a specific user
GET http://127.0.0.1:9000/reg/bling.babblevoice.com/1003
Returns 200 with the body:
{
"domain": "bling.babblevoice.com",
"count": 3,
"registered": 1,
"users": {
"1000": {
"registered": false
},
"1001": {
"registered": false
},
"1003": {
"registered": true,
"outstandingping": 0,
"remote": {
"host": "127.0.0.1",
"port": 42068,
"agent": "Z 5.2.28 rv2.8.114"
},
"epochs": {
"registered": 1552507958
}
}
}
}
When a request is for a specific domain the fields are:
- count - number of users that exist for this domain
- registered - the number of users that are registered
- users - array listing the state of the each user, most of which are self explanatory, outstandingping is the current OPTIONS failure count.
GET http://127.0.0.1:9000/reg/
Returns 200 with the body:
{
"count": 1255
}
This is a complete count of all registrations on this SIP server.
Originate a new call.
curl -X POST --data-raw '[{ "domain": "bling.babblevoice.com", "to: "", "from": "", "maxforwards": 70, "callerid": { "number": "123", "name": "123", "private": false }, "control": { "host": "127.0.0.1", "port": 9001 } }]' -H "Content-Type:application/json" http://127.0.0.1/invite
The control option is optional. If it is in this is the server which will receive updates regarding the call flow. If not, the default one listed in the "to" field will be used. If not this, then no updates will be sent.
Example: curl -X POST --data-raw '{ "callid": "", "alertinfo": "somealertinfo" }' -H "Content-Type:application/json" http://sip/dir
If the call is not in a ringing or answered state it will send a 180 ringing along with alert info if sent.
POST http://rtp/
Posting a blank document will create a new channel.
Example: curl -X POST --data-raw '{}' -H "Content-Type:application/json" http://rtp/
The server will return a JSON document. Including stats regarding the workload of the server so that the control server can make decisions based on workload as well as routing.
- RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1
- RFC 2617: HTTP Authentication: Basic and Digest Access Authentication
- RFC 3261: SIP: Session Initiation Protocol
- RFC 3881: An Extension to the Session Initiation Protocol (SIP) for Symmetric Response Routing
- RFC 3514: The Session Initiation Protocol (SIP) Refer Method
- RFC 3891: The Session Initiation Protocol (SIP) "Replaces" Header
- RFC 4028: Session Timers in the Session Initiation Protocol (SIP)
- RFC 4566: SDP: Session Description Protocol
- RFC 4317: Session Description Protocol (SDP) Offer/Answer Examples
- RFC 3550: RTP: A Transport Protocol for Real-Time Applications
- RFC 3551: RTP Profile for Audio and Video Conferences with Minimal Control
- RFC 3952: Real-time Transport Protocol (RTP) Payload Format for internet Low Bit Rate Codec (iLBC) Speech
- Real-Time Transport Protocol (RTP) Parameters
- RFC 3665: Session Initiation Protocol (SIP) Basic Call Flow Examples
- RFC 3903: Session Initiation Protocol (SIP) Extension for Event State Publication
Note, I have included RFC 4028 here for possible future work.
The SIP server can be run with the test flag:
project-sip --test
In the folder testfiles there are also other test files.
The default ports for the server are 9000 for the web server and 5060 for the SIP server.
Some notes on using valgrind for memory testing.
valgrind --tool=massif project-rtp --fg
After running, this will create a massif file in the directry you run valgrind from. i.e. massif.out.3823 You can use ms_print to pretify th econtents of this file:
ms_print massif.out.3823
valgrind --leak-check=yes project-rtp --fg
registerclient.xml & .csv.
Which are config files to be used with sipp which can test various scenarios.
sipp 127.0.0.1:9997 -sf registerclient.xml -inf registerclient.csv -m 1 -l 1 -trace_msg -trace_err
or without the logging files.
sipp 127.0.0.1:9997 -sf registerclient.xml -inf registerclient.csv -m 1 -l 1
To upload test data to the sip server use
sipp 127.0.0.1 -sf uaclateoffer.xml -m 1 -l 1
- Impliment session timers (RFC 4028)
- Research https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html in more detail to get the best performance out of a host CPU (i.e. SSE for filter and maybe CODECs)
dnf install ccache @development-tools g++ boost-devel ilbc-devel spandsp-devel openssl-devel nodejs