Thursday, November 11, 2010

SIP Load Balancing (SLB) using Opensips

1. Load Balancing in OpenSIPS

The "load-balancing" module comes to provide traffic routing based on load. Shortly, when OpenSIPS routes calls to a set of destinations, it is able to keep the load status (as number of ongoing calls) of each destination and to choose to route to the less loaded destination (at that moment). OpenSIPS is aware of the capacity of each destination - it is preconfigured with the maximum load accepted by the destinations. To be more precise, when routing, OpenSIPS will consider the less loaded destination not the destination with the smallest number of ongoing calls, but the destination with the largest available slot.

Also, the "load-balancing" (LB) module is able to receive feedback from the destinations (if they are capable of). This mechanism is used for notifying OpenSIPS when the maximum capacity of a destination changed (like a GW with more or less E1 cards).

The "load-balancing" functionality comes to enhance the "dispatcher" one. The difference comes in having or not load information about the destinations where you are routing to:

  • Dispatcher has no load information - it just blindly forwards calls to the destinations based on a probabilistic dispersion logic. It gets no feedback about the load of the destination (like how many calls that were sent actually were established or how many are still going).
  • Load-balancer is load driven - LB routing logic is based primary on the load information. The LB module is using the DIALOG module in order to keep trace of the load (ongoing calls).

2. Load Balancing - how it works

When looking at the LB implementation in OpenSIPS, we have 3 aspects:

2.1 Destination set

A destination is defined by its address (a SIP URI) and its description as capacity.

Form the LB module perspective, the destinations are not homogeneous - they are not alike; and not only from capacity point of view, but also from what kind of services/resources they offer. For example, you may have a set of Yate/Asterisk boxes for media-related services -some of them are doing transcoding, other voicemail or conference, other simple announcement , other PSTN termination. But you may have mixed boxes - one box may do PSTN and voicemail in the same time. So each destination from the set may offer a different set of services/resources.

So, for each destination, the LB module defines the offered resources, and for each resource, it defines the capacity / maximum load as number of concurrent calls the destination can handle for that resource.

Example:

4 destinations/boxes in the LB set

1) offers 30 channels for transcoding and 32 for PSTN
2) offers 100 voicemail channels and 10 for transcoding
3) offers 50 voicemail channels and 300 for conference
4) offers 10 voicemail, 10 conference, 10 transcoding and 32 PSTN

This translated into the following setup:

+----+----------+------------------------+---------------------------------+
| id | group_id | dst_uri | resources |
+----+----------+------------------------+---------------------------------+
| 1 | 1 | sip:yate1.mycluset.net | transc=30; pstn=32 |
| 2 | 1 | sip:yate2.mycluset.net | vm=100; transc=10 |
| 3 | 1 | sip:yate3.mycluset.net | vm=50; conf=300 |
| 4 | 1 | sip:yate4.mycluset.net | vm=10;conf=10;transc=10;pstn=32 |
+----+----------+------------------------+---------------------------------+

For runtime, the LB module provides MI commands for:

  • reloading the definition of destination sets
  • changing the capacity for a resource for a destination

2.2 Invoking Load-balancing

Using the LB functionality is very simple - you just have to pass to the LB module what kind of resources the call requires.

The resource detection is done in the OpenSIPS routing script, based on whatever information is appropriated. For example, looking at the RURI (dialed number) you can see if the call must go to PSTN or if it a voicemail or conference number; also, by looking at the codecs advertised in the SDP, you can figure out if transcoding is or not also required.

  if (!load_balance("1","transc;pstn")) {

sl_send_reply("500","Service full");
exit;
}

The first parameter of the function identifies the LB set to be used (see the group_id column in the above DB snapshot). Second parameter is list of the required resource for the call.

The load_balance() will automatically create the dialog state for the call (in order to monitor it) and will also allocate the requested resources for it (from the selected box).
The function will set as destination URI ($du) the address of the selected destination/box.

The resources will be automatically released when the call terminates.

The LB module provides an MI function that allows the admin to inspect the current load over the destinations.

2.3 The LB logic

The logic used by the LB module to select the destination is:

  1. gets the destination set based on the group_id (first parameter of the load_balance() function)
  2. selects from the set only the destinations that are able to provide the requested resources (second parameter of the load_balance() function)
  3. for the selected destinations, it evaluated the current load for each requested resource
  4. the winning destination is the one with the biggest value for the minimum available load per resources.
Example:


4 destinations/boxes in the LB set
1) offers 30 channels for transcoding and 32 for PSTN
2) offers 100 voicemail channels and 10 for transcoding
3) offers 50 voicemail channels and 300 for conference
4) offers 10 voicemail, 10 conference, 10 transcoding and 32 PSTN

when calling load_balance("1","transc;pstn") ->

1) only boxes (1) and (4) will be selected at as they offer both transcoding and pstn

2) evaluating the load :
(1) transcoding - 10 channels used; PSTN - 18 used
(4) transcoding - 9 channels used; PSTN - 16 used

evaluating available load (capacity-load) :
(1) transcoding - 20 channels used; PSTN - 14 used
(4) transcoding - 1 channels used; PSTN - 16 used

3) for each box, the minimum available load (through all resources)
(1) 14 (PSTN)
(2) 1 (transcoding)

4) final selected box in (1) as it has the the biggest (=14) available load for the most loaded resource.

The selection algorithm tries to avoid the intensive usage of a resource per box.


3. Study Case: routing the media gateways

Here is the full configuration and script for performing LB between media peers.

3.1 Configuration

Let's consider the case previously described:

4 destinations/boxes in the LB set

1) offers 30 channels for transcoding and 32 for PSTN
2) offers 100 voicemail channels and 10 for transcoding
3) offers 50 voicemail channels and 300 for conference
4) offers 10 voicemail, 10 conference, 10 transcoding and 32 PSTN

This translated into the following setup:

+----+----------+------------------------+---------------------------------+
| id | group_id | dst_uri | resources |
+----+----------+------------------------+---------------------------------+
| 1 | 1 | sip:yate1.mycluset.net | transc=30; pstn=32 |
| 2 | 1 | sip:yate2.mycluset.net | vm=100; transc=10 |
| 3 | 1 | sip:yate3.mycluset.net | vm=50; conf=300 |
| 4 | 1 | sip:yate4.mycluset.net | vm=10;conf=10;transc=10;pstn=32 |
+----+----------+------------------------+---------------------------------+

3.2 OpenSIPS Scripting

debug=1

memlog=1

fork=yes
children=2
log_stderror=no
log_facility=LOG_LOCAL0

disable_tcp=yes
disable_dns_blacklist = yes

auto_aliases=no

check_via=no
dns=off
rev_dns=off

listen=udp:xxx.xxx.xxx.xxx:5060



loadmodule "modules/maxfwd/maxfwd.so"
loadmodule "modules/sl/sl.so"
loadmodule "modules/db_mysql/db_mysql.so"
loadmodule "modules/tm/tm.so"
loadmodule "modules/xlog/xlog.so"
loadmodule "modules/uri/uri.so"
loadmodule "modules/rr/rr.so"
loadmodule "modules/dialog/dialog.so"
loadmodule "modules/mi_fifo/mi_fifo.so"
loadmodule "modules/mi_xmlrpc/mi_xmlrpc.so"
loadmodule "modules/signaling/signaling.so"
loadmodule "modules/textops/textops.so"
loadmodule "modules/load_balancer/load_balancer.so"



modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")

modparam("dialog", "dlg_flag", 13)
modparam("dialog", "db_mode", 1)
modparam("dialog", "db_url", "mysql://opensips:opensipsrw@localhost/opensips")

modparam("rr","enable_double_rr",1)
modparam("rr","append_fromtag",1)

modparam("load_balancer", "db_url","mysql://opensips:opensipsrw@localhost/opensips")


route{
if (!mf_process_maxfwd_header("3")) {
sl_send_reply("483","looping");
exit;
}


if (!has_totag()) {
# initial request
record_route();
} else {
# sequential request -> obey Route indication
loose_route();
t_relay();
exit;
}

# handle cancel and re-transmissions
if ( is_method("CANCEL") ) {
if ( t_check_trans() )
t_relay();
exit;
}


# from now on we have only the initial requests
if (!is_method("INVITE")) {
send_reply("405","Method Not Allowed");
exit;
}

# detect resources and do balancing
if ($rU=~"^1") {
# looks like a Conference call
load_balance("1","conf");
} else if ($rU=~"^2") {
# looks like a VoiceMail call
load_balance("1","vm");
} else {
# PSTN call, but the GWs supports only G711
# for calls without G711, transcoding will be used on the GW
if ( !search_body("G711") ) {
load_balance("1","transc;pstn");
} else {
load_balance("1","pstn");
}
}

# LB function returns negative if no suitable destination (for requested resources) is found,
# or if all destinations are full
if ($retcode<0)>

OpenSIPS Installation notes

1. First install the following dependencies that will be needed to compile OpenSIPS sources:

- bison
- flex
- mysql
- mysql-devel
- subversion (if you intend to use subversion to get its sources

*you can install the said sources via tarballs or rpms or via yum which I did :-)

2. Get the latest source package of OpenSIPs (currently it's 1.5.x series)

wget http://opensips.org/pub/opensips/1.5.0/src/opensips-1.5.0-tls_src.tar.gz

3. Unzip and extract the files

tar xvzf opensips-1.5.0-tls_src.tar.gz

4. cd to the source directory

cd opensips-1.5.0-tls

5. Do following for compilation and installation

make clean
make all
make install ; it seems to be simplified ah ;-) by default everything will be installed at /usr/local

6. Now edit first openserctlrc

vi /usr/local/etc/opensips/opensipsctlrc

uncomment the line that says

DBENGINE=MYSQL

7. Now we can create the database for the OpenSIPs

/usr/local/sbin/opensipsdbctl create

; this will prompt for mysql root password

*Note that upon execution of this command it will create 2 users for OpenSIPs

; by default the mysql database user created is:

username: opensips
password: opensipsrw

8. Now edit opensips.cfg

vi /usr/local/etc/opensips/opensips.cfg

uncomment all lines in configuration file opensips.cfg which are related to
authentication:
- loadmodule "/usr/lib/opensips/modules/db_mysql.so"
- loadmodule "/usr/lib/opensips/modules/auth.so"
- loadmodule "/usr/lib/opensips/modules/auth_db.so"
- modparam("usrloc", "db_mode", 2)
- modparam("auth", "calculate_ha1", yes)
- modparam("auth_db", "password_column", "password")
- if (!www_authorize("sip.org", "subscriber")) {
www_challenge("sip.org", "0");
break;

Make sure you change the two sip.org instances in the above config, to your
domain, or realm. In my case, I changed this to 192.168.2.235 which was the
IP address of this box.

Also, be sure to comment out the following line:

modparam("usrloc", "db_mode", 0)

9. Now let us copy the init script so as to start OpenSIPs as service ( such as starting it at boot). Copy the openser.init which can be found on the base directory of the OpenSIPs source. In my case it is at /usr/src

cp /usr/src/opensips-1.5.0-tls/packaging/rpm/opensips.init /etc/init.d/openser

chmod 755 /etc/init.d/opensips
/sbin/chkconfig --add opensips

also before you start the openser correct the path of its service execution

vi /etc/init.d/opensips

change the following line

osips=/usr/sbin/opensips

with this one since we installed the binaries on path /usr/local

osips=/usr/local/sbin/opensips

10. Now you can start OpenSIPs service

/sbin/service opensips start

To check if it is running issue

ps aux|grep opensips

you should see something like this

root 4210 0.3 0.6 43048 3548 ? S 21:21 0:00 /usr/local/sbin/opensips -P /var/run/opensips.pid
root 4214 0.0 0.2 43048 1216 ? S 21:21 0:00 /usr/local/sbin/opensips -P /var/run/opensips.pid
root 4217 0.0 0.2 43048 1140 ? S 21:21 0:00 /usr/local/sbin/opensips -P /var/run/opensips.pid

11. To test we will create user / SIP account to test our set up. We can do this by using the opensipsctl tool

/usr/local/sbin/opensipsctl add mytest 'abc123!@#'

*Note you might encounter problem once you issued the command above and complaining for the SIP_DOMAiN. To fix that,
issue the command as follows:

export SIP_DOMAIN=192.168.2.235

Now re issue the command for adding new user / SIP account

12. Test it by registering a softphone and filling up the phone's registration info's. You should be able to register already.