{"id":109,"date":"2016-08-11T08:40:24","date_gmt":"2016-08-11T12:40:24","guid":{"rendered":"http:\/\/blog.voipxswitch.com\/?p=109"},"modified":"2016-08-12T09:21:17","modified_gmt":"2016-08-12T13:21:17","slug":"kamailio-and-freeswitch-on-the-same-server-with-nsq-and-jansson-rpc","status":"publish","type":"post","link":"https:\/\/blog.voipxswitch.com\/?p=109","title":{"rendered":"Kamailio and FreeSWITCH on the same server with NSQ and JANSSON-RPC"},"content":{"rendered":"<p>This post will demonstrate how to run FreeSWITCH and Kamailio on a single server.<br \/>\nFreeSWITCH will handle authentication and act as registrar while Kamailio will handle presence updates using the NSQ module. You might be wondering why this setup would be useful. The reason we found, is that FreeSWITCH is not so great at handling presence updates. It relies heavily on SQL queries, and when you are dealing with 800+ user-agents all doing presence, FreeSWITCH can&#8217;t really keep up. You also may be wondering why not have Kamailio act as registrar. The reason FreeSWITCH will be registrar is we had a system in place already (single FreeSWITCH instance) and it would require too large of a change to move authentication to Kamailio. So this exact setup may not be so useful for many, it does contain helpful hints on how to run Kamailio and FreeSWITCH together on a single server, it also demonstrates use of NSQ and the JANSSON-RPC module.<\/p>\n<p>In order for FreeSWITCH and Kamailio to run on a single server, both services must bind to different ports on a single interface or on separate interfaces altogether. In this setup, I have FreeSWITCH setup to bind SIP on the loopback interface (127.0.0.1), RTP on the public facing interface and Kamailio binding to the public facing interface (4.5.6.7:5060). I used the dispatcher module to detect if FreeSWITCH is up or down.<br \/>\n<strong>Here is my dispatcher.list:<\/strong><\/p>\n<blockquote><p>#setid sipdest flags priority attrs<br \/>\n1 sip:127.0.0.1:5060 2 0 duid=1<\/p><\/blockquote>\n<p>Next you&#8217;ll have to setup a FreeSWITCH sofia profile to bind to loopback for SIP and public interface for RTP:<\/p>\n<pre>&lt;configuration name=\"sofia.conf\" description=\"Sofia\"&gt;\r\n\u00a0 &lt;profiles&gt;\r\n\u00a0\u00a0\u00a0 &lt;profile name=\"local\"&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;settings&gt;\r\n        ...\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;param name=\"sip-ip\" value=\"127.0.0.1\"\/&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;param name=\"sip-port\" value=\"5060\"\/&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;param name=\"rtp-ip\" value=\"4.5.6.7\"\/&gt;\r\n        ...\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;\/settings&gt;\r\n\u00a0\u00a0\u00a0 &lt;\/profile&gt;\r\n\u00a0 &lt;\/profiles&gt;\r\n&lt;\/configuration&gt;\r\n<\/pre>\n<p>I named the profile <code>local<\/code> but you can obviously name it whatever you like. That&#8217;s it for the FreeSWITCH side of things, make sure your profile is loaded by doing:<br \/>\n<code>fs_cli -x \"sofia status profile local\"<\/code><\/p>\n<p>You should see in output:<\/p>\n<pre>RTP-IP\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 4.5.6.7\r\nSIP-IP\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 127.0.0.1\r\nURL\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sip:mod_sofia@127.0.0.1:5060\r\nBIND-URL\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sip:mod_sofia@127.0.0.1:5060;transport=udp,tcp<\/pre>\n<p>Now it&#8217;s time to setup Kamailio, I&#8217;ll highlight some important sections and attach the entire <code>kamailio.cfg<\/code> at the end of post.<br \/>\nFirst thing I like to do in my <code>kamailio.cfg<\/code> is setup the global definitions that will be used later (that way the script is more portable):<\/p>\n<pre># - defines\r\n#!define DBURL \"postgres:\/\/kamailio:kamailio@localhost\/kamailio\"\r\n#!define JANSSON_RPC \"conn=presence;addr=localhost;port=8080;priority=10;weight=10\"\r\n#!define LISTEN 4.5.6.7:5060<\/pre>\n<p>The <code>DBURL<\/code> is required for presence and NSQ modules, so you will need to provision your database with the appropriate tables. Now define the module parameters, the ones I will highlight are related to NSQ:<\/p>\n<pre>modparam(\"nsq\", \"db_url\", DBURL)\r\nmodparam(\"nsq\", \"consumer_workers\", 1)\r\nmodparam(\"nsq\", \"topic_channel\", \"presence:kamailio\")\r\nmodparam(\"nsq\", \"max_in_flight\", 200)\r\nmodparam(\"nsq\", \"lookupd_address\", \"nsqlookup01\")\r\nmodparam(\"nsq\", \"pua_mode\", 1)<\/pre>\n<p>The <code>topic_channel<\/code> tells Kamailio to listen for messages on the <code>presence<\/code> topic and uses the <code>kamailio<\/code> channel. The <code>lookupd_address<\/code> tells kamailio to how to find the producers. If you are unfamiliar with NSQ, I suggest you check out the documentation (<a href=\"http:\/\/nsq.io\/\">http:\/\/nsq.io\/<\/a>)<\/p>\n<p>I setup a JANSSON-RPC server running on the localhost (written in golang) which checks for active calls when a user-agent subscribes and sends an NSQ message to Kamailio in the event the To-User is actually on a call. Here we define the JANSSON-RPC server:<\/p>\n<pre>modparam(\"janssonrpc-c\", \"result_pv\", \"$var(jsrpc_result)\")\r\nmodparam(\"janssonrpc-c\", \"server\", JANSSON_RPC)<\/pre>\n<p>The <code>result_pv<\/code> is used to receive results from the RPC request. In our case, we don&#8217;t care about the result since our NSQ module will receive a message on result.<\/p>\n<p>Now let&#8217;s check out the route section of our config. You&#8217;ll notice one of the first thing that happens in our config is <code>CHECK_SOURCE_IP<\/code><\/p>\n<pre>route[CHECK_SOURCE_IP] {\r\n\u00a0\u00a0\u00a0 if ($si == \"127.0.0.1\") {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 setflag(FLAG_FROM_FREESWITCH);\r\n\u00a0\u00a0\u00a0 } else {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 route(NAT_TEST_AND_CORRECT);\r\n\u00a0\u00a0\u00a0 }\r\n}<\/pre>\n<p>Since we should only be receiving requests from FreeSWITCH on the loopback, we can easily determine &#8220;who&#8221; is sending our SIP packet. I set a flag here, <code>FLAG_FROM_FREESWITCH<\/code>, which is very important later on. If the packet is <strong>not<\/strong> from FreeSWITCH, we do some NAT handling:<\/p>\n<pre>route[NAT_TEST_AND_CORRECT] {\r\n\u00a0\u00a0\u00a0 if (is_method(\"REGISTER\")) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (nat_uac_test(\"19\")) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 fix_nated_contact();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 } else {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (nat_uac_test(\"3\")) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0fix_nated_contact();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 force_rport();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (has_body(\"application\/sdp\") &amp;&amp; nat_uac_test(\"8\")) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 fix_nated_sdp(\"10\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 }\r\n}<\/pre>\n<p>We handle NAT for REGISTERs in a specific way here because we must send the correct <code>contact<\/code> header to FreeSWITCH, otherwise FreeSWITCH will get <code>contact<\/code> header containing a private IP and will not know how to route INVITEs to the user-agent.<\/p>\n<p>Next we handle the SIP SUBSCRIBE:<\/p>\n<pre>route[SUBSCRIBE] {\r\n\u00a0\u00a0\u00a0 if (is_method(\"SUBSCRIBE\")) {\r\n\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (!t_newtran()) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sl_reply_error();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 exit;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if ($tU == $null) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xlog(\"L_INFO\", \"$ci|stop|ignoring subscribe with empty TO username from a $ua\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sl_send_reply(400, \"Missing TO username\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 t_release();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 exit;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if ($fU == $null) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xlog(\"L_INFO\", \"$ci|stop|ignoring subscribe with empty FROM username from a $ua\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sl_send_reply(400, \"Missing FROM username\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 t_release();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 exit;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 # do an RPC request to check for Active calls\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if ($rU != $null) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0# forward message-summary SUBSCRIBEs to FreeSWITCH\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if ($hdr(Event) == \"message-summary\") {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xlog(\"L_INFO\", \"$ci|log|CHECKING MESSAGE SUMMARY\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 record_route();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 route(DISPATCH);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0route(RELAY);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 } else {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xlog(\"L_INFO\", \"$ci|log|CHECKING PRESENCE\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 janssonrpc_notification(\"presence\", \"Server.QueryPresence\", '[{\"CallId\":\"' + $ci + '\",\"FromUser\":\"' + $fU + '\",\"FromDomain\":\"' + $fd + '\",\"ToUser\":\"' + $rU + '\",\"ToDomain\":\"' + $rd + '\"}]');\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (!handle_subscribe()) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xlog(\"L_INFO\", \"$ci|stop|unsupported subsribe\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 t_release();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 exit;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 t_release();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 exit;\r\n\u00a0\u00a0\u00a0 }\r\n}<\/pre>\n<p>We are validating the SUBSCRIBE, then checking the event type. If it&#8217;s a message-summary event, we add a record-route header and forward it to FreeSWITCH. (We could handle message-summary using NSQ but right now it&#8217;s easier for FreeSWITCH to handle it and doesn&#8217;t put a strain on FreeSWITCH)<br \/>\nIf it&#8217;s not a message-summary event, we send an RPC request to our service which should know if the To-User is on a call or not, if they are on a call, our RPC server sends an NSQ message to the <code>presence<\/code> topic.<br \/>\nYou&#8217;ll notice the next section is for handling NSQ messages:<\/p>\n<pre># receive presence updates from NSQ and update watchers\r\nevent_route[nsq:consumer-event-presence-update] {\r\n\u00a0\u00a0\u00a0 $var(call-id) = $(nsqE{nsq.json,Call-ID});\r\n\u00a0\u00a0\u00a0 xlog(\"L_INFO\", \"$var(call-id)|log|payload $nsqE\");\r\n\u00a0\u00a0\u00a0 if ($(nsqE{nsq.json,Event-Package}) == \"dialog\") {\u00a0 \u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if($sht(p=&gt;$var(call-id)) != $(nsqE{nsq.json,State}) || $(nsqE{nsq.json,Flush-Level}) != $null) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xlog(\"L_INFO\", \"$(nsqE{nsq.json,Call-ID})|log|received $(nsqE{nsq.json,Event-Package}) update for $(nsqE{nsq.json,From}) state $(nsqE{nsq.json,State})\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $sht(p=&gt;$(nsqE{nsq.json,Call-ID})) = $(nsqE{nsq.json,State});\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsq_pua_publish($nsqE);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 pres_refresh_watchers(\"$(nsqE{nsq.json,From})\", \"$(nsqE{nsq.json,Event-Package})\", 1);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 } else {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xlog(\"L_INFO\", \"$var(call-id)|log|received duplicate $(nsqE{nsq.json,Event-Package}) update for $(nsqE{nsq.json,From}) state $(nsqE{nsq.json,State})\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xlog(\"L_INFO\", \"$var(call-id)|log|payload $nsqE\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 } \r\n\u00a0\u00a0\u00a0 } else {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xlog(\"L_INFO\", \"$var(call-id)|log|received $(nsqE{nsq.json,Event-Package}) update for $(nsqE{nsq.json,From}) $nsqE\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nsq_pua_publish($nsqE);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 pres_refresh_watchers(\"$(nsqE{nsq.json,From})\", \"$(nsqE{nsq.json,Event-Package})\", 1);\r\n\u00a0\u00a0\u00a0 }\r\n}<\/pre>\n<p>The NSQ message must be json format and contain the required fields for the presence module to send an update. I&#8217;ll save those details for another post.<\/p>\n<p>Let&#8217;s go back up to the main routing block of our config now.<\/p>\n<pre>\u00a0 \u00a0 # handle SUBSCRIBE requests\r\n\u00a0\u00a0\u00a0 route(SUBSCRIBE);\r\n\r\n\u00a0\u00a0\u00a0 # handle requests within SIP dialogs\r\n\u00a0\u00a0\u00a0 route(WITHINDLG);\r\n\r\n\u00a0\u00a0\u00a0 ###############################\r\n\u00a0\u00a0\u00a0 ### HANDLE INITIAL REQUESTS ###\r\n\u00a0\u00a0\u00a0 # handle retransmissions\r\n\u00a0\u00a0\u00a0 if(t_precheck_trans()) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 t_check_trans();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 exit;\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 t_check_trans();\r\n\r\n\u00a0\u00a0\u00a0 if (is_method(\"INVITE|REFER\")) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 record_route();\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 if (is_method(\"NOTIFY\") &amp;&amp; $hdr(event) == \"check-sync\" &amp;&amp; isflagset(FLAG_FROM_FREESWITCH)) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 record_route();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xlog(\"L_INFO\", \"$ci|log|Rebooting phone [$ru]\\n\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 t_on_reply(\"REPLY_FROM_DEVICE\"); # handle NAT\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 route(RELAY);\r\n\u00a0\u00a0\u00a0 }<\/pre>\n<p>After handling SUBSCRIBEs, we check for in-dialog request and route them. We add a <code>record_route<\/code> header if INVITE or REFER. Then we handle NOTIFYs with <code>check-sync<\/code> events specially. This allows us to remotely reboot phones which support the feature.<\/p>\n<p>The next portion is important when forwarding a REGISTER to another registrar:<\/p>\n<pre>\u00a0\u00a0\u00a0 if (!isflagset(FLAG_FROM_FREESWITCH) &amp;&amp; is_method(\"REGISTER\")) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 add_path();\r\n\u00a0\u00a0\u00a0 }<\/pre>\n<p>If we do not add a path header to the REGISTER before forwarding to FreeSWITCH, FreeSWITCH will not know to send INVITEs to user-agents through Kamailio.<\/p>\n<p>The last part of our main routing block does some final checks to decide how\/where to send the packet.<\/p>\n<pre>\u00a0\u00a0\u00a0 route(DISPATCH);\r\n\u00a0 \u00a0\r\n\u00a0\u00a0\u00a0 route(RELAY);<\/pre>\n<p>Our dispatch route checks if the SIP signal is from FreeSWITCH, if so, fix NAT on replies to it. If <strong>not<\/strong> from FreeSWITCH (e.g. from outside user-agent), send to FreeSWITCH:<\/p>\n<pre>route[DISPATCH] {\r\n\u00a0\u00a0\u00a0 if (isflagset(FLAG_FROM_FREESWITCH)) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 t_on_reply(\"REPLY_FROM_DEVICE\"); # handle NAT\r\n\u00a0\u00a0\u00a0 } else if (!ds_select_dst(\"1\", \"0\")) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 #if we are here that means no destination is available. We notify the user by 404 and exit the script.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xlog(\"L_NOTICE\", \"No destination available!\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 send_reply(\"404\", \"No destination\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 exit;\r\n\u00a0\u00a0\u00a0 }\r\n}<\/pre>\n<p>And here is the relay route:<\/p>\n<pre>route[RELAY] {\r\n\u00a0\u00a0 \u00a0if (is_method(\"INVITE\")) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if(!t_is_set(\"failure_route\")) t_on_failure(\"MANAGE_FAILURE\");\r\n\u00a0\u00a0 \u00a0}\r\n\r\n\u00a0\u00a0 \u00a0if (!t_relay()) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 sl_reply_error();\r\n\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0exit;\r\n}<\/pre>\n<p>And that&#8217;s it!<br \/>\nHere is the full <a href=\"http:\/\/blog.voipxswitch.com\/wp-content\/uploads\/2016\/08\/kamailio.cfg_.txt\" target=\"_blank\">kamailio.cfg<\/a><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post will demonstrate how to run FreeSWITCH and Kamailio on a single server. FreeSWITCH will handle authentication and act as registrar while Kamailio will handle presence updates using the NSQ module. You might be wondering why this setup would be useful. The reason we found, is that FreeSWITCH is not so great at handling [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20,7,21,11,22,25,15],"tags":[26],"class_list":["post-109","post","type-post","status-publish","format-standard","hentry","category-freeswitch","category-jansson-rpc","category-kamailio","category-nsq","category-rtp","category-sip","category-voip","tag-nsq"],"_links":{"self":[{"href":"https:\/\/blog.voipxswitch.com\/index.php?rest_route=\/wp\/v2\/posts\/109","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.voipxswitch.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.voipxswitch.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.voipxswitch.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.voipxswitch.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=109"}],"version-history":[{"count":22,"href":"https:\/\/blog.voipxswitch.com\/index.php?rest_route=\/wp\/v2\/posts\/109\/revisions"}],"predecessor-version":[{"id":133,"href":"https:\/\/blog.voipxswitch.com\/index.php?rest_route=\/wp\/v2\/posts\/109\/revisions\/133"}],"wp:attachment":[{"href":"https:\/\/blog.voipxswitch.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=109"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.voipxswitch.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=109"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.voipxswitch.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=109"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}