Friday, November 11, 2011

How To: Increasing VoIP Services Capacity


This is the second part on increasing voip services capacity. In the previous post I had a high level overview of what an SBC is and how to radically increase the call-capacity. In this post we'll proceed with the architecture setup and configurational steps required.

The very first thing anyone requires is servers, either on VMware or physical. One server is required for kamailio and RTPproxy while at least two servers for asterisk (media-servers). We require at-least two servers to test our Load-Balancer and Fail-over scenarios else one server for media is enough to verify the call-media related tests.

So Install a Ubuntu Server and then follow this blog post to install Kamailio and integrate with Asterisk servers.

Make sure we've two interfaces on the SBC server for a setup like below:



Since we have a NAT environment for SIP Servers so don't forget to define NAT settings in kamailio configuration file. 
This is how we can invoke NAT settings in the configuration file.
# *** To enable nat traversal execute:
#     - define WITH_NAT
So, Just write this line on the very top of the configuration file:

#!define WITH_NAT 


So, the top few lines of kamailio.cfg look like this.

#!KAMAILIO

#!define WITH_MYSQL
#!define WITH_AUTH
#!define WITH_USRLOCDB
#!define WITH_ASTERISK
#!define WITH_NAT


As soon as we've more than one interface on our Kamailio SBC we need to explicitly tell kamailio that we are now multi-homed.
Add this in kamailio.cfg as well 

mhomed=1

Above blog post is about setting up kamailio/RTPproxy and integration with asterisk realtime sipusers table for authentication purposes. For load-balancing and Fail-over  we require to load "dispatcher module" in Kamailio.


Following line needs to be added in the kamailio configuration file.

loadmodule "dispatcher.so"

Following parameters need to be in place for the above module.


# ------- Load-balancer params ------
modparam("dispatcher", "db_url","mysql://openser:openserrw@localhost/openser")
modparam("dispatcher", "table_name", "dispatcher")
modparam("dispatcher", "setid_col", "setid")
modparam("dispatcher", "destination_col", "destination")
modparam("dispatcher", "force_dst", 1)
modparam("dispatcher", "flags", 3)
modparam("dispatcher", "dst_avp", "$avp(i:271)")
modparam("dispatcher", "cnt_avp", "$avp(i:273)")
modparam("dispatcher", "ds_ping_from", "sip:proxy@10.1.1.1")
modparam("dispatcher", "ds_ping_interval",15)
modparam("dispatcher", "ds_probing_mode", 1)
modparam("dispatcher", "ds_ping_reply_codes", "class=2;code=403;code=404;code=484;class=3")

I've highlighted code=403;code=404 above. these are important since asterisks' reply with SIP 404 Not Found or SIP 484 if no peer information is provided in sip.conf. Adding the lines mentioned below in asterisk's sip.conf will allow the Kamailio SBC


[Kam-SBC]
type=friend
host=10.1.1.1
port=5060
disallow=all
allow=gsm
allow=g729
allow=alaw
allow=ulaw
context=SBC-Incoming
canreinvite=no
insecure=port,invite
dtmfmode=rfc2833
nat=yes
qualify=yes

Doing a "sip reload" in asterisk CLI should result ina notice like below:

chan_sip.c:19842 handle_response_peerpoke: Peer 'Kam-SBC' is now Reachable. (2ms / 2000ms)



Up-till now one should've SIP users successfully REGISTER on SBC using asterisk-sip realtime table. Also we've load-balancer module setup in SBC.Now add Media-Servers in the dispatcher module in the openser DB.

log into mysql,
# mysql -uopenser -popenserrw openser
INSERT INTO dispatcher (setid,destination,flags,priority,attrs,description) VALUES (1,"sip:10.1.1.3:5060",0,0,"weight=50","Asteriskl-I"),(1,"sip:10.1.1.4:5060",0,0,"weight=50","Asteriskl-II");

Next we need to restart kamailio to activate all the configuration changes. Changes in Dispatcher table can be made effective using the mi-fifo command.

#kamctl dispatcher reload

Status of the Media-Servers can be viewed in realtime using mi-fifo command on linux shell.
#kamctl dispatcher dump

[UPDATE] Forwarding  Calls to Asterisk Servers

Now, in our kamailio.cfg file we need to send the calls to the Asterisk server IPs which we just loaded in dispatcher table. 

Find this code in configuration:
# Send to Asterisk
route[TOASTERISK] {
 $du = "sip:" + $sel(cfg_get.asterisk.bindip) + ":"
   + $sel(cfg_get.asterisk.bindport);
 route(RELAY);
 exit;
}
 
Now, we see that we're hard-coding the $du, destination URI to use just one IP which we need to change to select some Load-balanced available Asterisk server's IP:PORT and route to.

Update the above code to something like this:


# Send to Asterisk
route[TOASTERISK] {
        ds_mark_dst("P");
        if(!ds_select_dst("1", "4")) {
                sl_send_reply("500", "Service Unavailable");
                xlog("L_INFO","[$fU@$si:$sp]{$rm} No destinations available for $rd \n");
                exit;
        }

        xlog("L_INFO","[$fU@$si:$sp]{$rm} From Outside World to Asterisk Box $du\n");
        rtpproxy_manage("cawei");

 route(RELAY);
 exit;
}
 

Similarly we need to detect if calls are coming in FROM Asterisk Boxes so in route FROMASTERISK put in some modifications to detect if call is coming from our own Asterisk servers.

Find the following code:

# Test if coming from Asterisk
route[FROMASTERISK] {
 if($si==$sel(cfg_get.asterisk.bindip)
   && $sp==$sel(cfg_get.asterisk.bindport))
  return 1;
 return -1;
}

And Modify it to something like this:

# Test if coming from Asterisk
route[FROMASTERISK] {
   if(ds_is_from_list()){
         rtpproxy_manage("cawie"); 
  xlog("L_INFO","[$fU@$si:$sp]{$rm} Call from Media-Server Cluster\n");
        return 1;
   }
 return -1;
}


With those above changes I believe a complete call-in , call-out scenario should be covered.

P.S: Do come back with your issues while following this tutorial and I will update it with fixes or your suggestions to help other people trying to go through this stage.

Special Considerations for RTPproxy

We need to engage RTPproxy for all inbound and outbound calls in Bridged mode. To start rtpproxy in bridged mode

#/usr/sbin/rtpproxy -F -s udp:127.0.0.1:7722 -l PU.BL.IC.IP/10.1.1.1 -d DBUG:LOG_LOCAL0

For all the incoming calls from Public Interface and terminating at Private-IP media-server  we need to rtpproxy_manage()  with "IE" flags like,rtpproxy_manage("ie")

For all outbound calls originating from Private IP media-server to some external destination should be using "EI" flags like, rtpproxy_manage("ei")

All of this will be done in RTPPROXY route. Just figure out the direction of the call and use the above mentioned flags and all the calls will have both-way media just like this diagram.
Ideal Signalling & Media flow.

If we don't use the IE/EI flags appropriately then we may end up in a call flow something like this.

Invalid Destinations for RTP/Media 
Tips:
1 -Use xlog() alot, This was my very first attempt on kamailio and I traced the whole configuration flow using the xlog() command and syslogs i.e.

xlog("L_NOTICE","$rm from $fu (IP:$si:$sp) Main Route before  ---NAT---\n");

xlog("L_NOTICE","$rm from $fu (IP:$si:$sp) in Route[NAT] fix_nat-register\n");
xlog("L_NOTICE","$rm from $fu (IP:$si:$sp) in route[RTPPROXY] RTPproxy with EI Flags\n");


2 -Get help from User's mailing list, don't expect an email with fully functional error-free configuration from there rather just hints and directions to look for problem solution.

3 -Read the module documentations.

4 -Use Wireshark as much as possible to look for packet flow. This will help you understand what's going on with the packets.

Physical-Dev Environment
I couldn't wait for physical servers and all the networking hassle so I used Oracle Virtual Box and created as many virtual machines as I required and setup a basic networking environment there. Once all the pings started to flow I build the above setup and tested calls - everything worked perfectly as expected.

My Dev Environment

Thats all for now, I hope this post be of some help for anyone interested in VoIP learning.

References:
http://nil.uniza.sk/sip/nat-fw/configuring-nat-traversal-using-kamailio-31-and-rtpproxy-server
http://kb.asipto.com/asterisk:realtime:kamailio-3.1.x-asterisk-1.6.2-astdb
http://www.kamailio.org/docs/modules/3.1.x/modules_k/rtpproxy.html#id2994642
http://www.kamailio.org/docs/modules/3.1.x/modules_k/nathelper.html
http://www.kamailio.org/dokuwiki/doku.php/pseudovariables:3.1.x#destination_uri
http://lists.sip-router.org/pipermail/sr-users/2011-September/070029.html
http://www.mail-archive.com/sr-users@lists.sip-router.org/msg07166.html

Monday, November 7, 2011

An Asterisk Deployment [Small-Office]

Last post was a very advanced topic, so before posting the part-II of that post I thought to post something with less complexity.

We've already covered Installing Linux, Asterisk, Setting up very basic IVR and making calls within one server's domain.

In this post we'll discuss a small to medium environment deployment of VoIP server. [See attached Image]. This sort of deployments - with a variation of ±( 10-20) end points - is not very common nor successful as we'll see the pros and cons.


Deployment Scenario

Features:

Feature such as customized IVR, Call recordings, CDR reports ,and restricted dialing are most demanded in addition to the core use of Internal PBX.

Advantages:

The advantages of deploying VoIP with above features are mainly :

-Relative afford-ability :
Equivalent Proprietary equipment with these features are far far far more expensive with typically Panasonic and Nortel topping the list. China Analogue PBXs are easily available too but they don't offer all the features and flexibility.

-Cost effectiveness:
Even if China made PBXs or Panasonic Hybrid equipment may be used in place but the expansion factor in these environments cost way much more than options available in VoIP. Consider buying a 16 or 24 port card only to accommodate two new users whereas in VoIP all you need is buy two IP phones at max or minimum install a free Softphone.

-Customizations:
Isn't it nice to change IVR prompts or options according to products or even change voicemail greetings without engaging any technical guys or vendors of PBX! though they still call me but every time I do it on phone or by remote desktop sessions in minutes. Now the only cost is of a cell phone call at max. Complex IVR designing and techniques are not commonly required in such deployments but there are cases when client's total emphasis is on a great IVR and then anything else. Anyways customizability is definitely a major attraction.
These are all the advantages of using OpenSource VoIP: The Big question is , are there any disadvantages of using VoIP in this scheme? My answer is YES, But these are all because of environment and people using it. So what are these?

Disadvantages:

-Multiple Single Point of Failures:

Since this type of deployments are majory based on cost effective factor with maximum cost cutting possible so they forget the factor of SPOF. As we can see that mostly they are using single unmanaged LAN switch which is often in everyone's reach and the network often experiences downtime, which brings down VoIP as well. Poor equipment handling is mostly the cause. I remember BAO JEE "If it can fail, it WILL fail" no matter how !! Single cable to Server, DSL modem and ATA (Analogue Telephony Adapter) with single power cable each, one Server, one FXO (Foreign eXchange Office) card are all Big Players in this area.

-Complex Management:
Since these environments are not mostly well educated and IT guys so you'll mostly hear 'em complain that it is complex to manage, and they can't or don't want to afford an IT specialist to handle this all.

-Call quality issues:
This is the most crucial part of, not just this but all, VoIP. Call quality in VoIP is major depending upon the underlying network equipment. Users want to download latest HD movies along with a perfect quality Call on the same network within the same bandwidth pipe. Also since the client is inclined towards poor call quality on VoIP so any issues with far end cell phone/network belong to 100% VoIP services now !! if PSTN lines are down - its all VoIP !!.

Those were some major disadvantages, some other like external dependencies, security, and maintenance cost are also in the list.

In continuation of this post I'll try come up with medium-large scale deployments as per my experience.

Sunday, November 6, 2011

Increasing VoIP services capacity



This is a very advanced VoIP topic, but I'm sharing it here before I forget the hurdles which I faced while setting it up.


Introduction:
Setting up just one or two Media-Servers(Asterisk/FreeSWITCH/SEMS) isn't much of a headache but just one server is not enough once we start having more and more incoming call volume. Specially in larger deployments just one heavy duty server with multiple asterisks and freeSWITCH instances is never enough to handle everything efficiently and in fail-safe mode.

Current Media-Servers and their call capacity:
Remember a single instance of Asterisk on one regular server can handle 150~180 calls without any QoS issue. Running two or more instances on such a regular server may increase this capacity to around 300~380 concurrent calls but the fear of Single Point Of Failure is there all the time.

FreeSWITCH on the other hand claims to handle around 700~1200 concurrent calls on the same spec machines(rough figure to be on safe side, they claim more than this :P), but FreeSWITCH is a fairly complicated application to master for most of us.

SEMS claims to handle around 1800~2300 concurrent calls BUT..BUT SEMS is one of the hardest tool to configure and manage. Not to mention that it isn't promising any applications or services as Asterisk or FreeSWITCH are offering i.e Queues or Conferences, one may need to setup asterisk or FreeSWITCH behind SEMS. SEMS is an excellent choice for IVRs and announcements.

Problem at hand:
No matter how good you are in setting up Linux-HA, the biggest challenge of increasing VoIP media services capacity remains a big ? another big hurdle is assignment of Public IPs for each individual cluster of HA-Media Servers. Even if we manage somehow to handle capacity without thinking outside the Traditional-Asterisk-Solutions, reserving Public IPs still remains annoying problem.

Consider you've to buy Public IPs each time you need to increase VoIP services capacity along with headache of communicating with your services provider to add new IP into their call distribution list or provision traffic for new server.

Solution: 
So the solution lies in setting up a SIP-Proxy which acts as gateway distributor for all the incoming calls. This SIP-Proxy encapsulates a huge farm of Media-Servers. World only knows to communicate with you using just one Public IP belonging to the SIP-proxy.
All the Media-Servers are on Private LAN. Without any struggle anyone can put in 253 Media-Servers of all types in the Media-Servers zone. Simple and Easy ! isn't it.

RTP & SIP Handling on SBC
Using SIP-proxy as Load-distributor enables us to group all the media-servers in different resource types i.e 10 servers only handling IVRs, 13 serving Voicemails, 60 for Handling conferences only. 9 Servers as PSTN gateways etc.

SIP-proxy load-balancer will make sure each of these resources only get the requests for their service types only and all the load is distributed evenly(could be any other algorithm as well)

SIP-proxy will be sending Keep-Alive to all the Media-Servers and if any servers stops responding it marks it as Probing and continues sending KAs but not routing any calls to it until it becomes active again.

Whats NEXT:
In next post I'll try to show where to start from for setting up such an environment, what will be required, what problems I faced and their solution.

Special Thanks:
Following GURUs of VoIP from user's mailing list helped me alot in understanding and overcoming the problems I faced while setting this up.

Alex Balashov
davy van de moere
Klaus Darilion
Muhammad Danish Moosa
Muhammad Shahzad


Community list are definitely the best place to ask for minor help and understanding the problems. 

Wednesday, November 2, 2011

Call-Back Service for IP-Telephony users Part-II


Continuing with the Auto-Call-Back service. In the previous part of this post we collected Callers numbers in a Call Back Queue for each IP-PBX user. Once we've List saved for each user, next thing would be to enable the service users to call to the service listen to their Callers Numbers and if Service user wants to call-back then dial it else move onto next caller in the list.

See the Flow-diagram on right for the above.

What we'll learn:

1- Dial-plan function DB_EXIST
2- Dial-plan Application SayDigits, and Read.
3- Misc: See some more scenarios of using previously used functions and applications.

Call-Back Service Dial-Plan code:


exten => 747,1,NOOP(PBX User ${CALLERID(num)} Reading Call-Back Data)
same => n,Answer()
same => n,GOTOIF(${DB_EXISTS(call-back/${CALLERID(num)})}?:hangup)
same => n,SET(CBQ=${DB(call-back/${CALLERID(num)})})
same => n,Playback(vm-youhave)
same => n,SayDigits(${FIELDQTY(CBQ,-)})
same => n,Playback(vm-messages)
same => n,SET(i=${FIELDQTY(CBQ,-)})
same => n,WHILE($["${i}" >= "1"])
same => n,Playback(vm-from-phonenumber)
same => n,SayDigits(${CUT(CBQ,,${i})})
same => n,Read(INPUT,vm-dialout,1)
same => n,SET(DIALME=${CUT(CBQ,,${i})})
same => n,GOTOIF($["${INPUT}" == "1"]?yes)
same => n,EXECIF($["${SaveCBQ}" == ""]?SET(SaveCBQ=${CUT(CBQ,,${i})}):SET(SaveCBQ=${CUT(CBQ,,${i})}-${SaveCBQ}))
same => n,SET(i=$[${i} - 1])
same => n,EndWhile()
same => n,hangup()
same => n(yes),SET(ARRAY(CBQ,j)=${CUT(CBQ,,1-$[${i} - 1])}-${SaveCBQ},${i})
same => n,NOOP([CBQ:1]= ${CBQ:1} :: [CBQ:0:-1]=${CBQ:0:-1} :: [CBQ:-1]=${CBQ:-1})
same => n,EXECIF($["${CBQ:-1}" == "-"]?SET(CBQ=${CBQ:0:-1}))
same => n,EXECIF($["${CBQ:0:1}" == "-"]?SET(CBQ=${CBQ:1}))
same => n,SET(DB(call-back/${CALLERID(num)})=${CBQ})
same => n,Dial(SIP/${DIALME})
same => n(hangup),Hangup()

So, once UserK calls into CBS number 747, we'll follow the flow-chart and try establish call between the userK and the callers.


All Dial-Plan Code:

[default]
exten => _X.,1,NOOP(Incoming Call from ${CALLERID(num)} to ${EXTEN})
same => n,Answer()
same => n,SET(DEST=${EXTEN})
same => n,Dial(SIP/${EXTEN},10)
same => n,Voicemail(${EXTEN}@default,d(register-callback))
same => n,Hangup()


exten => 747,1,NOOP(PBX User ${CALLERID(num)} Reading Call-Back Data)
same => n,Answer()
same => n,GOTOIF(${DB_EXISTS(call-back/${CALLERID(num)})}?:hangup)
same => n,SET(CBQ=${DB(call-back/${CALLERID(num)})})
same => n,Playback(vm-youhave)
same => n,SayDigits(${FIELDQTY(CBQ,-)})
same => n,Playback(vm-messages)
same => n,SET(i=${FIELDQTY(CBQ,-)})
same => n,WHILE($["${i}" >= "1"])
same => n,Playback(vm-from-phonenumber)
same => n,SayDigits(${CUT(CBQ,,${i})})
same => n,Read(INPUT,vm-dialout,1)
same => n,SET(DIALME=${CUT(CBQ,,${i})})
same => n,GOTOIF($["${INPUT}" == "1"]?yes)
same => n,EXECIF($["${SaveCBQ}" == ""]?SET(SaveCBQ=${CUT(CBQ,,${i})}):SET(SaveCBQ=${CUT(CBQ,,${i})}-${SaveCBQ}))
same => n,SET(i=$[${i} - 1])
same => n,EndWhile()
same => n,hangup()
same => n(yes),SET(ARRAY(CBQ,j)=${CUT(CBQ,,1-$[${i} - 1])}-${SaveCBQ},${i})
same => n,NOOP([CBQ:1]= ${CBQ:1} :: [CBQ:0:-1]=${CBQ:0:-1} :: [CBQ:-1]=${CBQ:-1})
same => n,EXECIF($["${CBQ:-1}" == "-"]?SET(CBQ=${CBQ:0:-1}))
same => n,EXECIF($["${CBQ:0:1}" == "-"]?SET(CBQ=${CBQ:1}))
same => n,SET(DB(call-back/${CALLERID(num)})=${CBQ})
same => n,Dial(SIP/${DIALME})
same => n(hangup),Hangup()


[register-callback]
exten => s,1,SET(CALLERID(num)=${RAND(100,110)})
same => n,NOOP(Caller ${CALLERID(num)} Registering Call-Back for User ${DEST})
same => n,NOOP(do some call-back tricks here)
same => n,SET(CBQ=${DB(call-back/${DEST})})
same => n,GOTOIF($["${CBQ}" == ""]?first:sec)
same => n(first),Set(DB(call-back/${DEST})=${CALLERID(num)})
same => n,GOTO(jump)
same => n(sec),Macro(duplicate-check,${CALLERID(num)})
same => n,GOTOIF($["${RESULT}" == "1"]?jump)
same => n,Set(DB(call-back/${DEST})=${CALLERID(num)}-${CBQ})
same => n(jump),NOOP(Playback(thanks-willb-called-shortly))
same => n,Hangup()

exten => i,1,GOTO(s,1)

[macro-duplicate-check]
exten => s,1,NOOP(${CBQ} Checked for Duplicate ${ARG1})
exten => s,n,SET(COUNT=${FIELDQTY(CBQ,-)})
exten => s,n,SET(ARRAY(i,RESULT)=1,0)
exten => s,n,WHILE($["${i}" <= "${COUNT}"])
exten => s,n,NOOP(${CUT(CBQ,,${i})} == ${ARG1})
exten => s,n,EXECIF($["${CUT(CBQ,,${i})}" == "${ARG1}"]?GOTO(found):SET(i=$[${i} + 1]))
exten => s,n,Endwhile()
exten => s,n,MacroExit()
exten => s,n(found),SET(RESULT=1)
exten => s,n,MacroExit()