Drachtio Server

Installing

Source for drachtio server can be obtained from github (build instructions can also be found there). The drachtio server has been tested on most Linux distributions, but the preferred deployment (because it has been the most heavily tested) is Debian 8 (Jesse).

For those who use Ansible for automating builds, an ansible role is available.

Finally, a docker image is also available via:

docker pull drachtio/drachtio-server:latest

Please post any issues here if you have problems building

Running

The drachtio-server executable built from source code is simply named drachtio. By default, the build process installs it in /usr/local/bin/drachtio.

On systemd distributions, drachtio can be installed as a systemd service -- the ansible role does this automatically; on a build from source the systemd script can be found here. The following options are supported:

$ systemctl [start|stop|restart|status] drachtio

Command-line options for the drachtio executable will be covered in the following section.

Configuring

The drachtio process can either be configured via command-line parameters, a configuration file, or a combination of the two.

The configuration file is installed by default at /etc/drachtio.conf.xml. As the name suggests, it's an XML file. The structure of the file is described below, and a heavily commented version of the file can be found here that provides additional detail to the summary provided below.

drachtio.conf.xml

A drachtio configuration file has the following high-level structure:

<drachtio>
  <admin/>
  <request-handlers/>
  <sip/>
  <cdrs/>
  <logging/>
</drachtio>

admin section

The admin section is required and specifies how the drachtio server will listen for incoming connections from drachtio applications. The information includes the tcp port to listen on, the address(es) to listen on (0.0.0.0 means all available interfaces), and the shared secret that is used for authentication.

Note that as of release 0.8.0, there is also an option to use tls encryption on connections. For inbound connections, this is specified by providing a 'tls-port' option. The server can be configured to handle either, or both, tcp and tls connections.

<admin port="9022" tls-port="9023" secret="cymru">0.0.0.0</admin>

request-handlers section

The request-handlers section is optional and configures the drachtio process to establish outbound connections to drachtio servers for some or all SIP methods instead of inbound connections.

The <request-handlers> element can have zero or more child <request-handler> elements. Each <request-handlers> defines a specific SIP method (or * to wildcard all methods) and an http(s) web callback to invoke when a new request of the specified method type arrives. It is the responsibility of the user-supplied web callback to return information in an HTTP 200 OK response indicating how to route the call.

<request-handlers>
    <request-handler sip-method="INVITE">http://38.187.89.96:8080</request-handler>
</request-handlers>

With the configuration above in place, when the drachtio server receives a new incoming INVITE request, it will send an HTTP GET to the URL above, with HTTP query arguments

  • method: the SIP method of the request
  • domain: the SIP domain in the Request-URI
  • protocol: the transport protocol used (e.g. 'udp', 'tcp', 'wss', etc)
  • source_address: the IP address of the sender
  • fromUser: the user part of the uri in the From header
  • toUser: the user part of the uri in the To header
  • uriUser: the user part of the uri in the Request-URI
  • contentType: the Content-Type header in the request, if any
  • uri: the full Request-URI

An example HTTP URL that gets sent out looks like this:

 http://38.187.89.96:8080/?method=INVITE&domain=server-01.drachtio.org&protocol=udp&source_address=10.132.0.29&fromUser=%2b15083084809&toUser=calltest&uriUser=r-ee78299f-2f85-4d92-97ab-24f1d11e2b69&contentType=application%2fsdp&uri=sip%3ar-ee78299f-2f85-4d92-97ab-24f1d11e2b69%server-01.drachtio.org%3bdst%3d%2b15083084809%2540139.59.165.83%3a5060&dst=%2b15083084809%2540139.59.165.83%3a5060

Based on the information above provided in the HTTP request, the user-supplied callback is responsible for indicating one of the following actions in a JSON body of the HTTP 200 OK response:

  • return a non-success response to the request
  • proxy the request
  • redirect the request (valid for INVITE only)
  • route the request to a specified drachtio application

The first three actions completely disposition the incoming SIP request -- i.e. no further interaction with a drachtio application occurs.

The final action (route to an application) causes the drachtio server to establish an outbound tcp connection to a drachtio application listening on a specified port, which then receives and processes the request normally (e.g. in a srf.invite((req, res))) or equivalent).

Note that as of release 0.8.0, it is possible route to a drachtio application over an outbound connection using tls. This is specified by appending a transport attribute to the uri and specifying 'tls', e.g. uri:myapp.example.com;transport=tls.

Example JSON responses for each of the above action are illustrated below (note: a response should include only one of the JSON payloads below):

// this would reject the call with a "503 Max Calls Exceeded" response
// note: reason is optional
{
  "reject":
  "data": {
      "status": 503,
      "reason": "Max Calls Exceeded"
  }
}

// this redirects the call to the address specified.
// the Contact header of the response will be populated accordingly
{
  "redirect":
  "data": {
      "contacts": [
          "sip:foo@bar.com"
      ]
  }
}

// this proxies the call accordingly
{
  "proxy":
  "data": {
      "destination": [
          "sip:foo@bar.com"
      ],
      recordRoute: true
  }
}

// this causes the request to be delivered to a drachtio app for further processing.
// the drachtio app must be listening on the uri provided; i.e an outbound connection.
{
  "route":
  "data": {
      "uri": "call-recording.default.svc.cluster.local:4000"
  }
}

// this causes the request to be delivered to a drachtio app for further processing.
// the drachtio app must be using tagged inbound connections.
{
  "route":
  "data": {
      "tag": "conferencing-app"
  }
}

Note: the last stanza above applies to using tagged inbound connections, which were added recently. For more details, see here

sip section

The <sip> section defines which addresses and ports the SIP stack will listen on, which protocols will be supported, where to find (if necessary) SSL certificates, and other SIP options.

contacts

The drachtio server can listen on multiple interfaces/addresses for SIP traffic. These are defined in a <contacts> element that has child <contact> elements for each SIP endpoint. Examples of possible configuration are shown below.

<!-- listen on all addresses, default port 5060 for udp and tcp protocols -->
<sip>
  <contacts>
    <contact>sip:*;transport=udp,tcp</contact>
  </contacts>
</sip>
<!-- listen on ports 5060 and 5080 -->
<contacts>
  <contact>sip:*;transport=udp,tcp</contact>
  <contact>sip:*:5080;transport=udp,tcp</contact>
</contacts>
<!-- listen for secure websockets on specific address and port -->
<contacts>
  <contact>sip:192.168.100.23:443;transport=wss</contact>
</contacts>

Additionally, if the SIP server has been assigned an external address that should be used in the SIP signaling, this should be specified as follows:

<contacts>
  <contact external-ip="35.195.28.194">sip:10.132.0.22;transport=udp,tcp</contact>
</contacts>

This will cause the drachtio server to advertise its address as 35.195.28.194 in Contact and Via headers, even though its local assigned IP address is 10.132.0.22.

Furthermore, if the drachtio server has an assigned DNS name, this should be configured as well so that it can detect when the Request-URI of an incoming SIP request is referring to the local host when the DNS name appears in the host portion.

<contacts>
  <contact dns-names="server01.drachtio.org" external-ip="35.195.28.194">sip:10.132.0.22;transport=udp,tcp</contact>
</contacts>

Note: multiple DNS names can be provided in comma-separated format.

timers

The SIP spec contains definitions for timers governing retransmissions of SIP requests and the like. Generally, there is no need to modify the setting for these timers, but if desired this can be done as follows:

<timers>
  <t1>500</t1>
  <t2>4000</t2>
  <t4>5000</t4>
  <t1x64>32000</t1x64>
</timers>

Note: values are in milliseconds. The example above actually sets the timers to their defined default values, so if you are using this section you would like be setting them to some other values. You only need to specify those timers that you want to adjust from their default values.

tls

If you are using either TLS or WSS as a transport, then you must specify where the associated tls certificates are stored on the server.

Additionally, when using tls on admin connections from applications, you must specify a dhparam file that contains the Diffie-Hellman (dh) parameters. (This is not required if you are only using TLS to secure SIP connections)

<tls>
  <key-file>/etc/letsencrypt/live/yourdomain/privkey.pem</key-file>
  <cert-file>/etc/letsencrypt/live/yourdomain/cert.pem</cert-file>
  <chain-file>/etc/letsencrypt/live/yourdomain/chain.pem</chain-file>
  <dh-param>/var/local/private/dh4096.pem</dh-param>
</tls>
outbound-proxy

This causes all outbound requests to be sent through an outbound proxy

<outbound-proxy>sip:10.10.10.1</outbound-proxy>
spammers

The drachtio server can examine the Contact, To, and From headers for distinctive signatures that indicate the request was sent from a spam source. If a spammer is detected, the message can either be rejected or silently discarded.

<spammers action="reject" action="discard">
  <header name="User-Agent">
    <value>sip-cli</value>
    <value>sipcli</value>
    <value>friendly-scanner</value>
  </header>
  <header name="To">
    <value>sipvicious</value>
  </header>
</spammers>
capture-server

The drachtio server can be configured to send to Homer using the HEP protocol.

<capture-server port="9060" hep-version="3" id="101">127.0.0.1</capture-server>
udp-mtu

Added in version 0.7.3-rc2

sofia-sip has an annoying feature where it forces an outbound request to go out TCP if the packet size exceeds a specific threshold (usually 1300 bytes). Tis configuration setting allows users to increase this threshold to an arbitrary value.

<udp-mtu>4096</udp-mtu>

logging section

The <logging> section defines where drachtio server will send logging information, including sip traces.

Logging destinations include the console, a named log file on the server, or syslog. Any or all of them may be used at one time.

console

To log output to the console simply include a <console\> child element.

syslog

To send log output to a syslog server via UDP, specify the following:

<syslog>
  <address>127.0.0.1</address>
  <port>514</port>
  <facility>local6</facility>
</syslog>
log file

To send log output to a log file on the server, specify the following:

 <file>
  <name>/var/log/drachtio/drachtio.log</name>
  <archive>/var/log/drachtio/archive</archive>
  <size>50</size> 
  <maxSize>100</maxSize>
  <minSize>2000</minSize>
  <auto-flush>true</auto-flush>
</file>

The options are as follows:

  • name: path to the log file
  • archive: path a directory where older log files were be archived
  • size: the size (in MB) at which the log file is truncated
  • maxSize: the max size (in MB) of archived files to keep
  • minSize: the minimum freespace (in MB) on the filesytem to maintain when archiving
  • auto-flush: if true, log information is written immediately to disk; otherwise log file is buffered and written intermittently (slightly better performance)
loglevel

The overall system log level: 'notice', 'warning', 'error', 'info, or 'debug'.

<loglevel>info</loglevel>

Note: 'info' is the recommended log level for production systems. At this log level you will get sip traces, which are useful for debugging.

sofia-loglevel

The drachtio server uses the sofia library internally. The log level for this library can be set from 0 (minimal) to 9 (extensive).

<sofia-loglevel>3</sofia-loglevel>

command-line arguments

The drachtio executable can accept command-line arguments that specify some configuration parameters. If provided, the command-line configuration parameters take preference over those specified in the configuration file.

The supported drachtio command-line arguments are:

  • --daemon detach from the console and run as a daemon process. Note: when running as a systemd service, this parameter is not necessary.
  • --noconfig ignore any logging configuration in the configuration file
  • --file|-f filename read configuration from specified file rather that /etc/drachtio.conf.xml
  • --user|-u user run as the named user rather than root
  • --port|-p port listen for tcp admin connections on the named port
  • --tls-port listen for tls admin connections on the named port.  Added in version 0.8.0-rc1.
  • --contact|-c specifies a listening address/port/protocol. Multiple instances of this parameter may be provided
  • --external-ip ip-address specifies an external address that the drachtio server should advertise in the SIP signaling. This parameter applies to the --contact parameter that it follows in the command line.
  • dns-name name a dns name that refer to the local server. This parameter applies to the --contact parameter that it follows in the command line.
  • http-handler url an HTTP URL of a web callback that will be invoked for all new incoming requests. Setting this parameter turns on outbound connections for all SIP request types.
  • --loglevel level the overall log level to set
  • --sofia-loglevel level the log level of the sofia library
  • --stdout write log output to console
  • --homer ip-address:port ip address of homer capture server to send to. HEP3 and udp transport will be used
  • --homer-id id id to use to represent this server when sending messages to homer
  • --version print the drachtio server version to console and exit.
  • --mtu specifies a message size, in bytes, for requests such that when outgoing requests exceed this threshold use of tcp is forced (this overrides the default sofia stack setting for the same).  Added in version 0.7.3-rc2.
  • --dh-param dhparam file used for inbound tls admin connections.  Added in version 0.8.0-rc1.

Messaging protocol

Note: This article is a work in progress.

The drachtio server is controlled by drachtio-srf applications using a utf8-based messaging protocol over a tcp connection.

Because some developers have expressed an interest in integrating other language frameworks (e.g. go) to the drachtio server, this section aims to give some details of that protocol. As highlighted above, it is a work in progress.

As per the documention found elsewhere on this site, drachtio supports both inbound and outbound connections. To begin with, we will focus here on describing the message flows for inbound connections.

Basic message format

Messages are utf8-encoded. (This was not always the case - originally ascii was used, but this broke down with applications that needed to receive and send information like emojis!).

Each message starts with a decimal number indicating the number of bytes in the message (again, this is utf-8 so number of characters does not necessarily mean number of bytes!), followed by a hash sign ('#'). The number of bytes specified does not include these leading decimal digits or the hash sign -- they indicate the length of the payload that follows.

The messsge payload that follows this consists of 1 or more lines of data, where each line is terminated by a CRLF.

Typically, the first line of payload data consists of a list of tokens, where each token is delimited by a '|' character. When SIP messages are exchanged between application and server, these are usually carried starting on the second line of data, and are simply represented exactly as they appear "on the wire" between SIP endpoints.

How to see messages being exchanged

For those interested, note that is is possible to generate a trace file of all messaging between the client and the server by setting a property on the Srf instance, e.g.:

const Srf = require('drachtio-srf');
const srf = new Srf();

// this will cause all messages between client and server to be logged to a file
srf.set('api logger', '/tmp/messages.log');

Connection and authentication

An inbound connection scenario starts when an application, acting as a TCP client, connects to a drachtio server on its configured admin port.

After the connection is established, the application must authenticate itself via the shared secret that is configured in the drachtio.conf.xml configuration file on the server. If the client is authenticated, the server returns information including the sip hostports (e.g. list of address:port) that the server is listening on.

Let's look at an example of a successful authentication. Not all of this will immediately be clear, but we will review the individual message elements below.

===>56#12ad33a1-9f33-40b7-b327-b2e32bfcd9e8|authenticate|cymru|

<===465#2f8216a3-babf-4cdb-93c6-254fe8dba42d|response|12ad33a1-9f33-40b7-b327-b2e32bfcd9e8|OK|tcp/[::1]:5060,udp/[::1]:5060,tcp/127.0.0.1:5060,tcp/10.0.0.121:5060,udp/127.0.0.1:5060,tcp/[2601:182:cd00:5786::4a1e]:5060,udp/[2601:182:cd00:5786::4a1e]:5060,tcp/[2601:182:cd00:5786:a534:e8d4:3dcb:3553]:5060,udp/[2601:182:cd00:5786:a534:e8d4:3dcb:3553]:5060,udp/10.0.0.121:5060,tcp/[2601:182:cd00:5786:1866:49f9:e83b:9574]:5060,udp/[2601:182:cd00:5786:1866:49f9:e83b:9574]:5060

Note: the direction ===> indicates a message sent from an application to the server; the reverse indicates a message sent from the server to the application

We see that after connecting, the application sends an authenticate request to the server. The authenticate request consists of a single line containing three (3) tokens:

  • a unique message identifier, generated by the client application (needs to be unique only for this client),
  • the message type ('authenticate')
  • the shared secret ('cymru')

The server validates the shared secret, and returns a message consisting of one line, containing the following tokens:

  • a unique message identifier for the response
  • the message type ('response')
  • the message id of the associated request
  • the response status ('OK')
  • a comma-delimited string containing the sip hostports the server is listening on

At this point, the client has connected and authenticated successfully, but it has not yet registered the SIP request types that it would like to receive. That happens next.

Registering to handle SIP requests

In order to notify the server that the application wishes to handle specific SIP request types, it is necessary to issue a 'route' command. This is typically the next thing a client application does after authenticating. It is only necessary to register for request types once, during startup (i.e., after actually handling an incoming request it is not necessary to re-register in order to get further requests).

Registering for request types is taken care of automatically in a drachtio-srf app when a srf.invite() statement is processed, for example.

# registering to receive INVITEs
===>49#5e60fef0-409f-4980-a859-94810d002ebb|route|invite
<===85#e7470b05-95be-448e-94cd-3935292c4956|response|5e60fef0-409f-4980-a859-94810d002ebb|OK

Note: to register for multiple request types (e.g. INVITE and REGISTER) the application must issue spearate 'route' commands.

The 'route' command consists of a single line containing the following tokens:

  • a message id
  • the command ('route')
  • the SIP request type

The response, as before, contains its own message id as well as the message id of the request, and the status ('OK').

At this point, the server will start sending SIP requests of the requested type to the application for handling. Let's look at what happens next.

Handling an incoming SIP request

Let's start with something simple: receiving a SIP INVITE and sending a non-success response.

The basic message flow is this:

  • The server receives a SIP INVITE, selects the client application to send it to for handling, and sends a 'sip' message type to the client app containing the full SIP request message, as well as additional detail (e.g. the source_address and port etc)
  • The client application receives the message and sends a sip response message, containing at least the SIP status as well as any headers that it wants set to any non-default values.
  • The server sends out the SIP response on the network and then sends another message back to the client containing the exact message that was sent out over the wire.

With that as background, let's examine the message trace. To make it interesting, we will have the client application add a custom SIP header to the response.

We will also see now our first example of messages containining multiple lines, since we are now carrying SIP messages back and forth between client and server:

<===866#83936ddb-523a-4cd0-886e-92b484905d85|sip|network|732|udp|127.0.0.1|50500|16:06:41.114233|8f62909b-0f25-4bb8-b0fc-2c0ec88a4afe|\r\n
INVITE sip:15083084809@127.0.0.1 SIP/2.0\r\n
Via: SIP/2.0/UDP 127.0.0.1:50500;branch=z9hG4bK-524287-1---f8002e51f06e3b38;rport=50500\r\n
Max-Forwards: 70\r\n
Contact: <sip:dhorton@127.0.0.1:50500>\r\n
To: <sip:15083084809@127.0.0.1>\r\n
From: <sip:dhorton@127.0.0.1>;tag=ce2ff21e\r\n
Call-ID: 92383ZWEzZGI3MTY4NDRiMGI1ZTRhYjc2YWQ4ZTE1OWY4N2E\r\n
CSeq: 1 INVITE\r\n
Allow: SUBSCRIBE, NOTIFY, INVITE, ACK, CANCEL, BYE, REFER, INFO, OPTIONS\r\n
Content-Type: application/sdp\r\n
Supported: replaces\r\nUser-Agent: Bria 5 release 5.3.1 stamp 92383\r\n
Content-Length: 202\r\n
\r\n
v=0\r\n
o=- 1531670801092727 1 IN IP4 127.0.0.1\r\n
s=Bria 5 release 5.3.1 stamp 92383\r\n
c=IN IP4 127.0.0.1\r\n
t=0 0\r\n
m=audio 52282 RTP/AVP 0 101\r\n
a=rtpmap:101 telephone-event/8000\r\n
a=fmtp:101 0-15\r\n
a=sendrecv\r\n

===>332#829ba63e-5010-4719-b72f-cb3cf6f4aab2|sip|8f62909b-0f25-4bb8-b0fc-2c0ec88a4afe|\r\n
SIP/2.0 486 Busy Here\r\n
Call-ID: 92383ZWEzZGI3MTY4NDRiMGI1ZTRhYjc2YWQ4ZTE1OWY4N2E\r\n
cseq: 1 INVITE\r\n
from: <sip:dhorton@127.0.0.1>;tag=ce2ff21e\r\n
to: <sip:15083084809@127.0.0.1>\r\n
Content-Length: 0\r\nuser-agent: drachtio\r\n
X-custom: my custom header value\r\n

<===600#d04f4d88-108c-4d93-a3f9-e9967b63ff55|response|829ba63e-5010-4719-b72f-cb3cf6f4aab2|OK|application|359|udp|127.0.0.1|50500|16:06:41.127954|8f62909b-0f25-4bb8-b0fc-2c0ec88a4afe|92383ZWEzZGI3MTY4NDRiMGI1ZTRhYjc2YWQ4ZTE1OWY4N2E;uas||Msg sent:|\r\n
SIP/2.0 486 Busy Here\r\n
Via: SIP/2.0/UDP 127.0.0.1:50500;branch=z9hG4bK-524287-1---f8002e51f06e3b38;rport=50500\r\n
From: <sip:dhorton@127.0.0.1>;tag=ce2ff21e\r\n
To: <sip:15083084809@127.0.0.1>;tag=cjSXXKtQejgrg\r\n
Call-ID: 92383ZWEzZGI3MTY4NDRiMGI1ZTRhYjc2YWQ4ZTE1OWY4N2E\r\n
CSeq: 1 INVITE\r\n
User-Agent: drachtio\r\n
Content-Length: 0\r\n
X-custom: my custom header value\r\n

Let's examine the first message -- the one sent by the server to the application, giving it an incoming INVITE message to handle.

As usual, after the length specifier we see the message payload begins with the message identifier.

After that, we have the message type, which in this case is 'sip'. We've now almost all of the message types (there aren't that many!), so let's pause here and briefly give the full list:

  • 'authenticate'
  • 'route'
  • 'response'
  • 'sip'
  • 'cdr:attempt'
  • 'cdr:start'
  • 'cdr:stop'

In the case of a new incoming 'sip' request sent by the drachtio server we have the following tokens on the first line of the message payload:

  • message id
  • message type ('sip')
  • message source ('network' or 'application', depending on whether the app received or sent the msg)
  • length of the SIP message, as received over the wire
  • transport protocol
  • source address
  • source port
  • time message was received (as reported by the sofia sip stack)
  • unique id assigned by the server for this SIP transaction
  • unique id assigned by the server for this SIP Dialog (not always present, since a Dialog may not exist for this request)

Following the first line of payload the incoming SIP message in its entirety is provided.

At this point it is the responsibility of the application to return the desired SIP response that it wishes the server to send out. We see in the example above that the application also uses the 'sip' message type to accomplish this. The first line of payload contains the following tokens:

  • message id
  • message type ('sip')
  • the transaction id that the sip response pertains to

Following that we have the message itself. Note that it is not necessary to provide the full SIP response message -- based on the transaction id, most of the headers will be supplied by the drachtio server. The application must minimally provide the SIP status line (in our example, drachtio-srf is populating several other headers such as To, From, etc but this should not be necessary). Additionally, if a body is desired in the response (which is not in our example), then that would be provided by the application as well (in the normal way of separating the body from any headers with two (2) CRLFs).

The drachtio server sends out the indicated response and then notifies the application of the exact SIP response that was sent out over the wire. This allows the application to learn about information, such as the tag on the SIP To header, that the server itself would have applied.

This final message has the following payload:

  • message id
  • message type ('response')
  • related message id (in this case, the 'sip' response message sent by the application)
  • message status ('OK')
  • message source ('application', indicating this was a message sent by the application)
  • length of the SIP message
  • transport protocol used
  • remote address sent to
  • remote port sent to
  • time message was sent (as reported by the sofia sip stack)
  • related transaction id
  • unique dialog id created by the server to represent this SIP Dialog

Note: the additional tokens at the end of the first line (a blank token, and the string 'Msg sent') can be ignored; they are placeholders that are used in scenarios where the application has sent a SIP request, not a response.