Monday, June 4, 2012

OpenSIPS as Load-Balancer for FreeSWITCH

With reference to my older posts in which I talked about increasing VoIP services capacity (with failover for load-balanced media-servers), then I tested the whole scenario using Kamailio and RTPproxy.This post, however, is replica of the above scenario but using OpenSIPS and RTPproxy.

I wanted to use MediaProxy  instead of RTPprxoy but media-Proxy unfortunately is not used for bridging RTPs from Public Internet to Private subnets and vice-versa [For details: See this forum discussion]

Following is the topology I wanted to achieve.

OpenSIPS+RTPproxy in Action
I used FreeSWITCH as my Media-Server layer. Asterisk servers can be used alternatively as well.
Now moving quickly to the opensips.cfg file changes required in order to make this work.

First add,

mhomed=yes

in Global Parameters, This will tell opensips that server has multiple interfaces to send/receive and relay traffic in-between subnets, so modify headers wisely and accordingly.

Then following modules needs to be declared.

(click on underlined modules to read their documentation)

loadmodule "rtpproxy.so"
loadmodule "nathelper.so"
loadmodule "dialog.so"
loadmodule "load_balancer.so"
loadmodule "avpops.so"

Then parameters for each modules:


# ------ RTP-proxy Parameters
#RTPproxy instance to be used: 9901 
#172.16.31.102<<=Transform=>> 192.168.30.3
modparam("rtpproxy", "rtpproxy_sock", "udp:127.0.0.1:9901") 

In my case, since using Virt.Environment, I consider 172.16.31.102 to be my Public IP and transform into the internal Private subnet 192.168.30.x.

NOTE[1]: My assumption  of 172.16.31.102 as Public IP has serious complications, as I understand, because the NAT-HELPER functions will always consider traffic from this subnet as Private rather public and this may've effect on OpenSIPS behavior, if used in production.
NOTE[2]: Please reply back in case if there is any discrepancy or suggestion in the configurations. This is a work in progress, so I apologize in case if you find anything incorrect. Changes and Suggestions are always welcome.


# ------ AVPOPS params --------
modparam("avpops","db_url", "mysql://opensips:opensipsrw@localhost/opensips")
modparam("avpops","avp_table","usr_preferences")
modparam("avpops","use_domain",1)

# ------ NAT-Helper params ------
modparam("nathelper", "ping_nated_only", 1)
modparam("nathelper", "received_avp", "$avp(i:42)")
modparam("nathelper", "sipping_bflag", 7)
modparam("nathelper", "sipping_from", "sip:pinger@saevolgo.com")

# ----- LOAD BALANCER MODULE -----
modparam("load_balancer", "probing_interval", 30) 
modparam("load_balancer", "probing_reply_codes", "501, 404") 
modparam("load_balancer", "probing_from","sip:opensips@saevolgo.com")
modparam("load_balancer", "db_url","mysql://opensips:opensipsrw@localhost/opensips")



Now main routing logic:

Add Highlighted NAT test just at the start of main route. I put that just under the Max-FWD header validation check.

      if (!mf_process_maxfwd_header("10")) {
  sl_send_reply("483","Too Many Hops");
  exit;
 }
      if (nat_uac_test("19")) {
xlog("L_INFO","[$pr:$fU@$si:$sp]: NAT Detection Test-19 Passed Now Fixing-Nat for '$rm' \n");
  if (is_method("REGISTER")) {
   fix_nated_register();
  } else {
   fix_nated_contact();
  }
xlog("L_INFO","[$pr:$fU@$si:$sp]: NAT Fixed for '$rm' \n");
  setflag(5);
 };
Soon after this I could see code Logic for "BYE" method. There I need to tell (on call hangup) RTPproxy to dis-engage its ports for this particular call. if (loose_route()) {
 if (is_method("BYE")) {
  setflag(1); # do accounting ...
  setflag(3); # ... even if the transaction fails
  unforce_rtp_proxy();

Then scrolling down at the end of the main route I could see this piece of code.
# do lookup with method filtering
xlog("L_NOTICE","[$pr:$fU@$si:$sp]: Call from '$fu' to '$ru' LOOKUP in 'Location' table\n");
 if (!lookup("location","m")) {
  switch ($retcode) {
   case -1:
   case -3:
    t_newtran();
    #t_reply("404", "Not Found");
xlog("L_NOTICE","[$pr:$fU@$si:$sp]: Call from '$fu' to '$ru' User Not Found - try Dialing Media-Server\n");
    route(3);
    route(1);
   case -2:
    sl_send_reply("405", "Method Not Allowed");
    exit;
  }
 }
I added the highlighted lines. Basically at this point the opensips default configuration searches its online sip users and if the dialed destination string is an online user it sends the call out to that user.

If that user is offline, or the dialed string don't matches any user it sends "404 Not Found" back to the caller.

Now, What I'm doing over here is that once I find out that a user is offline,or is not a defined user then route the call to route(3) which is where I'll call in Load-Balancer and select a LB'd destination Media-Server and record a VoiceMail or do some other call routing.

 Just after route(3); I've called route(1);. Route[1] is the default route already defined which only relays the call towards the destination. So here are these routes: Add these lines just after your main route ends.
# Media-Server Route, Engage Load-Balancer and Select Server here.
route[3] 
{
 xlog("L_NOTICE","[$pr:$fU@$si:$sp]: This is Media-Server Route Use Load-balancer NOW!!\n");
 if (!load_balance("1","calls")) {
  sl_send_reply("500","Service full");
        exit;
   }
 xlog("L_NOTICE","[$pr:$fU@$si:$sp]: Selected destination Media-Server : $du\n");
}
See as simple as this to use Load-Balancer. But the real game for RTPproxy is handled in route[1] where I test if the selected destination of this call is one of my Media-Server defined in loadbalancer DB-table or not.

If results in True I engage RTPproxy using flags sequence IE, if not and call is coming in from my Media-Servers to outside use RTPproxy flags in EI sequence.

See this post for more information on making RTPproxy work for you.

Now, in route[1] I added the following Highlighted code and RTPproxy started working for me.

route[1]{
# for INVITEs enable some additional helper routes
 xlog("L_INFO","[$pr:$fU@$si:$sp]: SET flags for '$rm' & Relay the Message\n");
 if (is_method("INVITE")) {
  t_on_branch("2");
  t_on_reply("2");
  t_on_failure("1");
  if (isflagset(5)) {
xlog("L_NOTICE","[$pr:$fU@$si:$sp]: Engage Media-Proxy here\n");
xlog("L_NOTICE","[$pr:$fU@$si:$sp]: InBound-OR-OutBound Call Check!!\n");
   if(avp_db_query("select dst_uri from load_balancer where dst_uri like '%$dd%'")){
#INCOMING CALL NEEDS TO GOTO PRIVATE-IP FreeSWITCH USE - IE
    engage_rtp_proxy("rie");
 
   }
   else if(avp_db_query("select dst_uri from load_balancer where dst_uri='sip:$si:$sp'")){
#INCOMING CALL FROM PRIVATE-IP FreeSWITCH USE - EI
    engage_rtp_proxy("rei");

   }
   else
   {
    engage_rtp_proxy("r");

   }

  }
  
 }
Now opensips.cfg is all done.  Don;t forget to add FreeSWITCH server IPs in the loadbalancer table.

# mysql -uopensips -popensipsrw opensips
mysql>INSERT INTO load_balancer (group_id,dst_uri,resources,probe_mode,description) VALUES (1,'sip:192.168.30.4:5090','calls=50',2,'FreeSWITCH-B Server');
mysql>INSERT INTO load_balancer (group_id,dst_uri,resources,probe_mode,description) VALUES (1,'sip:192.168.30.4','calls=100',2,'FreeSWITCH-B Server');


Quit MySQL, now you can use the following commands to reload Load-balancer servers list in opensips on the fly and see the status of each FS server in opensips.

#opensipsctl fifo lb_reload
#opensipsctl fifo lb_list
This is not all done!!

FreeSWITCH SIDE CONFIGURATIONS

You need to define a gateway for OpenSIPS in your FreeSWITCH external or itnernal profile per your settings.

FreeSWITCH-A:~# cd /usr/local/freeswitch/conf/sip_profiles/external/
FreeSWITCH-A:~# vim opensips.xml
Insert these Lines in this file:
<include>
  <gateway name="OpenSIPS">
  <param name="username" value="192.168.30.3"/>
  <param name="from-user" value="192.168.30.3"/>
  <param name="password" value="2007"/>
  <param name="proxy" value="192.168.30.3"/>
  <param name="register" value="false"/>
  <param name="retry-seconds" value="10"/>
  <param name="caller-id-in-from" value="true"/>
  <param name="extension-in-contact" value="true"/>
  <param name="ping" value="25"/>
  <param name="inbound-late-negotiation" value="true"/>
  <param name="context" value="default"/>
  </gateway>
</include>
Reload Sofia files in FreeSWITCH. I did a whole sofia module reload.

freeswitch@internal> reload mod_sofia

I also had to tell FreeSWITCH to allow my LAN IPs in acl.conf.xml located at 
/usr/local/freeswitch/conf/autoload_configs/acl.conf.xml 

<list name="lan" default="allow">
      <node type="deny" cidr="192.168.30.0/24"/>
    </list>

Save changes in that file and reload acl.

freeswitch@internal> reloadacl

Now make calls and see that calls are landing in your Public context, there take control of your incoming calls and land to your own context/xml files.

Thats all folks ! Hope you guys were really bored uptill here ;)

11 comments:

  1. Great, could you please share you contact details

    ReplyDelete
  2. Please share you issues with the setup here, better if you post those on OPenSIPS user's mailing list.

    ReplyDelete
  3. Thanks for this blog. I am having issues though, and I think it all has to do with the numeric ID vs named route-blocks. In the latest Opensips it looks to use names. like in Kamailio, rather than ID's . You've added route 3. Roue[1] looks to be the RELAY named route. I don't have any other route which might correspond to a Route"2". Same confusion on the "t_on_xxx" id's in your route1. In my default opensips.cfg I have:
    t_on_branch("per_branch_ops");
    t_on_reply("handle_nat");
    t_on_failure("missed_call");
    ....
    and for those blocks I have only:
    branch_route[per_branch_ops] {
    xlog("new branch at $ru\n");
    }


    onreply_route[handle_nat] {

    xlog("incoming reply\n");
    }


    failure_route[missed_call] {
    if (t_was_cancelled()) {
    exit;
    }
    # uncomment the following lines if you want to block client
    # redirect based on 3xx replies.
    ##if (t_check_status("3[0-9][0-9]")) {
    ##t_reply("404","Not found");
    ## exit;
    ##}

    }

    Are those the equivalent of the "2" and "1" block-id values you have?

    I feel like I am missing some logic, that may be implied in your post or available elsewhere. Any assistance or examples of the correct config blocks would be very much appreciated.

    Thanks again for your blog, very helpful.

    ReplyDelete
  4. Good day to you
    Thank you very much for your blog.
    I am new to freeswitch.
    Could you advise me how can i set the 2 freeswitch servers under load balancer to share registered user, and that users can call to each other?
    Thank you for you blog,again
    best regards

    ReplyDelete
    Replies
    1. Hi,
      Its been some time I've touched this topic. Kindly ask the OpenSIPS community and FreeSwitch community if there are some newer and better answers/solutions to your questions are possible.

      I will answer you to be latest and best of my knowledge.
      In your case I'd suggest you to let OpenSIPS handle all the user registration , and then FS only acts as MediaServer for trans-coding and bridging calls.
      that was the easiest way as I imagine.

      I've also read that a shared runtime DB between FreeSwitch is possible and in which case registrations done on one FS machine are also valid for the other one. I haven't tried that myself. Kindly ask the community for a better solution.

      Delete
  5. Thanks a lot for this blog.
    Is it possible to set up load balancer module of the opensips to balance skinny to multiple freeswitches?
    Thank you for your blog,again

    ReplyDelete
  6. Hi, thank you for you blog.It is very useful.
    Could you advise me how can i set the gateway to multiple fs servers via load balancer?
    Thank you for your time.

    ReplyDelete
  7. Hi,thanks for the blog.
    Why do i have to define the gateway on FreeSwitch for OpenSIPS?

    ReplyDelete
  8. Hi, I'm using Opensips 1.7.1 and 2 media server is FS. The opensips.cfg file have config for dispatcher and now I can't config it to use load_balancer similar your post.please help me ...And opensips.cfg is https://wiki.freeswitch.org/wiki/OpenSIPS_configuration_for_2_or_more_FreeSWITCH_installs
    Thanks very much

    ReplyDelete
  9. Did you install RTPProxy in additon of using it as a module?I tried to follow your tutorial but it does not work.

    ReplyDelete
    Replies
    1. Hi dear,
      Two things:
      1 - I stated in the very first paragraph that its a replica of the other post and that automatically means I followed parts of that setup. Simple said, yes I had to installed and setup RTPproxy in this as well.

      2 - Please do share logs, configs, and any helpful hints so I know what didn't work. I'm working on putting latest configs in a github repo somewhere so the whole code becomes available for readers instead of just snippets here which gets difficult for beginners. So please bear with me and get more personal help if required to make things work.

      Delete