Sunday, December 11, 2011

WAN simulation in LAN for Dev-Servers

Application behaviour under different network circumstances is usually an area which is neglected untill the day application starts showing unexpected behaviours during (or around) network outage times.

Though Networking stack makes sure the reliability of the information packets relayed to application layer and hence it is assumed that network situation is ideal i.e 100% reliable mainly because applications are developed in LAN environments which are always working almost ideally.

Things gets worse when production applications start receiving malformed or de-sequenced packets sometimes with jitter or retransmissions. Though this happens rarely but I believe in making an application which is designed to handle the worse situations as well.

So the question is how to generate a production(WAN) environment to test the behaviour of application. Since we can not control the networking layer on WAN so we can not guess/wait when things gets worse on internet and we test our application.

So, WAN simulation in LAN environment is the topic and we will see how to do this using simple commands in linux servers.

First lets look at our ideal LAN dev-server setup.

Normal Ping on LAN
We've Server-A and Server-B on Linux and a Windows Client. Lets Say Server-A is a VoIP server and Server-B is its DB server.

What we do next is introduce a 120ms delay for all the egress and ingress traffic on Server-A. 

Server-A with 120ms Delay on Inbound/Outbound Traffic

We use the following sequence of commands on Server-A to do this.

# tc qdisc add dev eth0 root handle 1:0 netem delay 120msec

The above command will implement a qdisc on device eth0 using netem module (network emulator) to delay all the packets by 120ms.

Once this delay is implemented we can change the delay using this command.

# tc qdisc change dev eth0 root handle 1:0 netem delay 180msec

To remove this delay altogether from the eth0 interface use the following command.

# tc qdisc del dev eth0 root handle 1:0 netem delay 180msec

So, Putting just a delay was easy, lets start adding some jitter
# tc qdisc change dev eth0 root handle 1:0 netem delay 180ms 80ms

Here's the output from Server-B when pinging Server-A

[root@Asterisk ~]# ping 192.168.56.102
PING 192.168.56.102 (192.168.56.102) 56(84) bytes of data.
64 bytes from 192.168.56.102: icmp_seq=1 ttl=64 time=184 ms
64 bytes from 192.168.56.102: icmp_seq=2 ttl=64 time=119 ms
64 bytes from 192.168.56.102: icmp_seq=3 ttl=64 time=160 ms
64 bytes from 192.168.56.102: icmp_seq=4 ttl=64 time=198 ms
64 bytes from 192.168.56.102: icmp_seq=5 ttl=64 time=221 ms
64 bytes from 192.168.56.102: icmp_seq=6 ttl=64 time=257 ms
64 bytes from 192.168.56.102: icmp_seq=7 ttl=64 time=161 ms
64 bytes from 192.168.56.102: icmp_seq=8 ttl=64 time=102 ms
64 bytes from 192.168.56.102: icmp_seq=9 ttl=64 time=209 ms

--- 192.168.56.102 ping statistics ---
9 packets transmitted, 9 received, 0% packet loss, time 8007ms
rtt min/avg/max/mdev = 102.371/179.395/257.091/46.304 ms
[root@Asterisk ~]#

Now, We start dropping packets.
On Server-A:
# tc qdisc add dev eth0 root handle 1:0 netem delay 180ms drop 50%

And following are the ping stats on Server-B

[root@Asterisk ~]# ping 192.168.56.102
PING 192.168.56.102 (192.168.56.102) 56(84) bytes of data.
64 bytes from 192.168.56.102: icmp_seq=6 ttl=64 time=181 ms
64 bytes from 192.168.56.102: icmp_seq=7 ttl=64 time=181 ms
64 bytes from 192.168.56.102: icmp_seq=9 ttl=64 time=183 ms
64 bytes from 192.168.56.102: icmp_seq=10 ttl=64 time=182 ms
64 bytes from 192.168.56.102: icmp_seq=11 ttl=64 time=182 ms
64 bytes from 192.168.56.102: icmp_seq=16 ttl=64 time=180 ms
64 bytes from 192.168.56.102: icmp_seq=19 ttl=64 time=182 ms
64 bytes from 192.168.56.102: icmp_seq=23 ttl=64 time=181 ms

--- 192.168.56.102 ping statistics ---
24 packets transmitted, 8 received, 66% packet loss, time 23013ms
rtt min/avg/max/mdev = 180.001/182.037/183.630/1.148 ms
[root@Asterisk ~]#

We can keep on playing with this using the below netem help.

Usage: ... netem [ limit PACKETS ]
                 [ delay TIME [ JITTER [CORRELATION]]]
                 [ distribution {uniform|normal|pareto|paretonormal}]
                 [ drop PERCENT [CORRELATION]]
                 [ corrupt PERCENT [CORRELATION]]
                 [ duplicate PERCENT [CORRELATION]]
                 [ reorder PRECENT [CORRELATION][ gap DISTANCE ]]

The problem with above is that when we implement qdisc netem on any specific interface it is by default used on all the packets coming in from any other host or network.

We may want to impose these delays and packets distortions on just one particular IP or subnet by using the set of following commands.

# tc qdisc add dev eth0 root handle 1: prio
# tc qdisc add dev eth0 parent 1:3 handle 30: netem delay 180ms
# tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip dst 192.168.56.2/32 flowid 1:3

We created a qdisc handle, put a delay of  180ms on one of the handle's branch and created a filter for that branch that if an ip 192.168.56.2 is found force it to the 180ms branch else let the packets go as usual.

Here's what it looked like.

Server-A with 180ms delay for Server-B only

So see its easy to create a WAN simulation environment within your LAN. In addition to implemented the netem functions on one particular port of the Server-A as well. That requires using IPtables.

This was the quickest blog I could write despite being very busy at home work.


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()


Thursday, October 27, 2011

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




While trying to think of a bit complex dial plan example for VoIP-students I came up with the idea of a call-back service.
The idea is whenever an external call comes into IP-Telephony server for some user, if that user is unavailable to take call, the call should get routed to voice mail OR should be enlisted in a list. This list will be collection of all the callers to that user who wanted to be called back automatically whenever the user likes.
From IP-telephony user's perspective its a missed-calls register sort of list which is dialled back automatically once triggered.

Not the smartest idea but from Asterisk Dial-plan P.O.V it has lots of things to be learned.

Since this is abit complex to understand dial-plan project so I think I'll break it into two parts. First one is going to deal with Incoming call from outside and saving the number in the call-back register.
Second part will focus on the actual service where an IP-PBX user dials into the service number and go through his call-back list.

What we'll Learn:

1- Setting up voicemail service for users.
2- Using Asterisk built-in DB i.e AstDB to save and retrieve User lists.
3- Use of Macros in Dial-plan
4- Asterisk dialplan applications SayDigits, ExecIf, While/EndWhile, Voicemail
5- Asterisk dialplan functions DB, DB_EXISTS, FIELDQTY, CUT, ARRAY
6- Using Labels in dial-plan to transfer control between priorities

Setting up Voicemail:
Setting up voicemail is fairly simple task. Just edit the file /etc/asterisk/voicemail.conf and insert following lines

[new-vmail-group]
101 => 12345678,Dean Winchester,dean@supernatural.net

where,
101 is User's voicemailbox number - I chose it to be the same as of User's Telephony extension.
12345678 is users's voicemail access pin.
Next two fields are user name and user email respectively.

To make voicemail box configurations active don't forget to execute "voicemail reload" in asterisk command line interface.

Dial-Plan Code:
once Voicemail is configured we need to call the voicemail application if user is unavailable. As you can see from code below that voicemail is called after Dial() application. Dial application is configured to timeout after 10 seconds of ringing to the destination.

[from-external-world]
exten => _X.,1,NOOP( 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()


Once the dial application is timed out or the user in unavailable or busy incomming caller will listen the voicemail prompt. "Please record your message after the tone, when done press # or hangup".
Meanwhile listening to this message user can press one-digit key from his cell-phone and the call control will be transferred to "register-callback" context. thanks to the option "d" in voicemail application.

Hint: execute "core show application voicemail" in asterisk CLI to know more.

[register-callback]
exten => s,1,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)

I know, the above code isn't easy to even look at. thats why I've highlighted important things to learn here.

1- Labels:
Anything that is enclosed in () next to priority field (i.e "n" in my case) becomes a label and control can be transferred from anywhere to these labels. "first","sec","jump" are labels in the above context.

2- Asterisk DB operations:
DB(call-back/${DEST}) is used to GET or SET values from AstDB. In this case "call-back" is family name (DB name) ; Value of ${DEST} will be the Key to access one unique Value/Result set from the AstDB.

If DB() function is enclosed in ${} then asterisk will GET/fetch the value from the DB otherwise asterisk will SET the value in AstDB(given anything appears in right hand operand with operator =)

Example:
Fetch the value from AstDB and Save in variable "CBQ"

same => n,SET(CBQ=${DB(call-back/${DEST})})
Set value from "${CALLERID(num)}-${CBQ}" into the AstDB.
same => n,Set(DB(call-back/${DEST})=${CALLERID(num)}-${CBQ})
3- Using Macros

Macros are like functions which accepts arguments. Just like regular contexts now-a-days. See this link for detailed information.

same => n(sec),Macro(duplicate-check,${CALLERID(num)})
I don't recommend using macros but unfortunately these are essential part of asterisk dial-plan learning. In above dial-plan code ${CALLERID(num)} becomes argument-1 to "macro-duplicate-check"

As soon as the above line is executed call-control jumps to context "[macro-duplicate-check]"

[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()


Again, important things have been highlighted. This macro is just an exceptional handling that the same number don't get inserted into the call-back list more than once.

Everyone knows about While-looping the only difference here is that an EndWhile() defines where the While-Block ends.


EXECIF is just another application to compress 6+ Lines of dial-plan into just one line. It execute one application if the condition is true or execute the second application otherwise.


exten => s,n,EXECIF($["${CUT(CBQ,,${i})}" == "${ARG1}"]?GOTO(found):SET(i=$[${i} + 1]))

I find it easy to compress 6+ lines of GOTOIFs and applications into just one line.


Here we are done with the first part of the service.

So far we've just done what the flow-chart said in this post; Receive incoming call, and skip voicemail if user wants to register for call-back, check if the number already exists in Call-back list. Finally play message to caller that he'll be called back shortly.

Wednesday, October 26, 2011

If you think you're consultant lvl-82

Once upon a time there was a shepherd looking after his sheep on the side of a deserted road. Suddenly a brand new Porsche screeches to a halt. The driver, a man dressed in an Armani suit, Cerutti shoes, Ray-Ban sunglasses, TAG-Heuer wrist-watch, and a Versace tie, gets out and asks the Shepherd:

Man: “If I can tell you how many sheep you have, will you give me one of them?”

The shepherd looks at the young man, and then looks at the large flock of grazing sheep and replies:

Shepherd: “Okay.”

The young man parks the car, connects his laptop to the mobile-fax, enters a NASA Webster, scans the ground using his GPS, opens a database and 60 Excel tables filled with logarithms and pivot tables, then prints out a 150 page report on his high-tech mini-printer. He turns to the shepherd and says,

Man: “You have exactly 1,586 sheep here.”

The shepherd cheers,

Shepherd: “That’s correct, you can have your sheep.”

The young man makes his pick and puts it in the back of his Porsche. The shepherd looks at him and asks,

Shepherd: “If I guess your profession, will you return my animal to me?”

The young man answers;

Man: “Yes, why not?”

Shepherd: "You are an IT consultant."

Man: “How did you know?”

Shepherd: “Very simple. First, you came here without being called. Second, you charged me a fee to tell me something I already knew, and third, you don’t understand anything about my business…Now can I have my DOG back?"

Monday, October 24, 2011

Asterisk Dial-plan exercise [Speed-Dial]

Creating a Speed-Dial Functionality :
This was a test exercise I gave in one my teaching classes here last year. The idea is to develop a Speed-Dial functionality context for PBX users.

What we'll learn:
1- Connect to MySQL DB from Dial-plan.
2- Use of Asterisk Dial-plan functions CUT, FIELDQTY.
3- Use of GOTOIF Dial-plan application.
4- Use of restricted extension pattern matching by use of [ ].

Problem Statement:
The users should be able to edit their own speed dial-lists via web-interface. And whenever a SIP user dials single digit extension ranging from 0-9 should be able to speed-dial to their contacts saved in DB.

(The Php web-interface for editing contacts isn't included here, it shouldn't be difficult to make a simple page for this.)

Solution Algorithm:


Final Dial-Plan Code:
Corresponding Dial-plan for the above Algorithm is as follows.A few exceptions were handled where if no user speed-dial lists exists or no user data existed.

[Speed-Dial]
exten => _[0-9],1,NOOP(Starting Speed-Dial functionality)
same => n,MYSQL(Connect connid localhost asteriskuser ast_pass user_prefs)
same => n,MYSQL(Query resultid ${connid} SELECT contact_list FROM speed_dial_list WHERE user='${CALLERID(num)}')
same => n,MYSQL(Fetch fetchid ${resultid} UserList)
same => n,MYSQL(Clear ${resultid})
same => n,MYSQL(Disconnect ${connid})
same => n,GOTOIF($["${fetchid}" < "1"]?not_found:found)
same => n(not_found),Playback(No-User-List-Found)
same => n,Goto(h,1)
same => n(found),NOOP(User Speed-Dial List Found: ${UserList})
same => n,NOOP(Find if user Dialed Speed-Dial index exists)
same => n,SET(List_Length=${FIELDQTY(UserList,-)})
same => n,GOTOIF($["${EXTEN}" >= "${List_Length}"]?not_found:lengthok)
same => n(lengthok),NOOP(Now User Contact for Speed-Dialed exten ${EXTEN} Exists for Sure)
same => n,NOOP(Fetch the Speed-Dial-Contact from ${UserList})
same => n,SET(UserContact=${CUT(UserList,,${EXTEN})})
same => n,NOOP(Found user Contact ${UserContact} Corresponding to ${EXTEN})
same => n,DIAL(SIP/${UserContact})
same => n,Hangup()

Now Database turn.
Connect to MySQL and Create table "speed_dial_list" and push some dummy values corresponding to PBX users

#mysql
mysql>use asterisk
mysql>CREATE TABLE `speed_dial_list` (`user` varchar(80) NOT NULL default '',`contact_list` varchar(555) NOT NULL default '');
mysql>INSERT into speed_dial_list(user,contact_list) VALUES ('100','110-132-400-161-101-123'),('200','100-201-115-101'),('110','201-200-119'),('210','150-161-110-199-923452167923');
mysql>GRANT all on asterisk.* to 'asteriskuser'@'localhost' Identified by 'ast_pass';
mysql>flush privileges;

Sample output on Asterisk CLI when SIP User 100 dialled Speed-Dial Extension '3'



So, we made a Speed-Dial context here, include this context in your [default] dial-plan context and all one-digit extensions will be matched in Speed-Dial context and perform the functionality if anything matches else it'll be skipped.