Showing posts with label Asterisk. Show all posts
Showing posts with label Asterisk. Show all posts

Thursday, June 6, 2013

Asterisk Dialplan and Redis Integration

I came across this very strange task that I need to have the asterisk get/set data from Redis. My initial thought was this is easy, just going to plug in a perl AGI, use redis connector and everything will be super cool. BUT the condition was I've to stay within the extensions.conf. Yes, I did try convince everyone around but to no avail. NO AGIs, use anything else however I want.

So what I did was create a small shell script which behaves as an API for Redis, and use the Asterisk System() application to GET/SET my desired memcached values.

root@asterisk1:~# vim /etc/asterisk/redis.pl

Insert the following lines in there, add more commands to it see this link: http://search.cpan.org/dist/Redis/lib/Redis.pm

#!/usr/bin/perl

my $redis_db = $ARGV[0];
my $command = $ARGV[1];
my $keyname = $ARGV[2];
my $value = $ARGV[3];

# PERL MODULE
require Redis;
my $redis = Redis->new(server => '127.0.0.1:6379');

if($redis_db > 0 && $redis_db < 16){
        $redis->select("$redis_db");
} else {
        print "using Default Redis DB\n";
}
if($command eq "GET"){
        $val=$redis->get("$keyname");
        print "$val";
}
if($command eq "SET" && $value != ""){
        $redis->set("$keyname" => "$value");
}
if($command eq "INCR"){
 $redis->incr("$keyname");
}

if($command eq "DECR"){
 $redis->decr("$keyname");
}

if($command eq "DECRBY"){
 $redis->decrby("$keyname",$value);
}
if($command eq "DEL"){
$redis->del("$keyname" ) || warn "key doesn't exist";
}
$redis->quit;

Save and Exit;

give permissions to this script.

root@asterisk1:~# chmod 755 /etc/asterisk/redis.pl

Now in Dialplan I call System() Application like this.

exten => _XXX,1,SET(CALLS=${SHELL(/etc/asterisk/redis.pl 1 GET ${CALLERID(num)})})
same => n,NOOP(The Caller:${CALLERID(num)} has ${CALLS} calls in the system)
same => n,Answer()
same => n,System(/etc/asterisk/redis.pl 1 INCR ${CALLERID(num)})
same => n,NOOP(Do some dialplan actions as you want)
...
...
exten => h,1,System(/etc/asterisk/redis.pl 1 DECR ${CALLERID(num)})
exten => h,n,Hangup()

Thats all. Now I can keep realtime track of the active calls for any user/trunk/carrier etc etc from my dialplan. The real beauty of using this shared Redis Memcache store is that I've like 8 asterisk servers, all of them using the same Redis store and all of them are aware of the current number of calls from/to a particular user.

I've also used it to share the Statuses of Queues and Conference rooms residing on special servers with normal IVR servers. Without using this shared store I can only think of using a Database used by all and I believe that doesn't perform well when multiple users are accessing and modifying the same field concurrently especially when the usage is heavy.

Friday, March 16, 2012

Asterisk Text-To-Speech Cepstral Setup


Text To Speech Integration
Cepstral + Asterisk

I've been holding onto this post for sometime, wanted to post it with some cool experiment like an IVR based story line which narrates the story and changes the story line according to choices made by listener(DTMF based and then later on Speech Recognition based).

I've now realized that due to extremely busy schedule at job and home I may not get any extra time to make a sample story; insert it into the MySQL DB; make a dialplan for it; create its web end to manage/edit stories and present it here. So instead of delivering it all in one piece its better to take a start from giving our Asterisk server a voice by installing Cepstral TTS engine.

Following are the commands to get going.

Download and Install Cepstral TTS Engine.

#wget http://downloads.cepstral.com/cepstral/i386-linux/Cepstral_Callie-8kHz_i386-linux_5.1.0.tar.gz

#tar zxvf Cepstral_Callie-8kHz_i386-linux_5.1.0.tar.gz

#cd Cepstral_Callie-8kHz_i386-linux_5.1.0

#./install.sh

Download and Install App-Swift.
App_swift is the connector between the asterisk and Cepstral engine.Swift runs from inside asterisk as an application and uses the Cepstral TTS engine running in Linux.

#cd /usr/src/

#wget http://pbxinaflash.net/source/app_swift/app_swift-1.6.2.tar.gz

#tar zxvf app_swift-1.6.2.tar.gz

#cd app_swift-1.6.2/

#make

#make install

Issue this command to let Cepstral recognize the Swift module and let it use its engine.
#echo /opt/swift/lib > /etc/ld.so.conf.d/cepstral.conf

#ldconfig

Thats it and now your TTS engine and Asterisk app_Swift connector should be ready to rock

Test Asterisk for Swift Application.
Now test Asterisk for swift application; On CLI execute the following command
Asterisk*CLI>core show application swift

You should get some long output like below:

-= Info about application 'Swift' =-

[Synopsis]
Speak text through Swift text-to-speech engine.

[Description]
Swift(text) Speaks the given text through the Swift TTS engine.
Returns -1 on hangup or 0 otherwise. User can exit by pressing any key.


Make Dialplan to speak something for you. 

exten => 1234,1,NOOP(--Starting Text-To-Speech Test--)
same => n,Answer()
same => n,Swift(Hello World This is an Asterisk server)
same => n,NoOp(Key pressed: ${SWIFT_DTMF})
same => n,Swift(You pressed ${SWIFT_DTMF}. Goodbye.)
same => n,Hangup()

exten => h,1,Hangup()


Thats all, now make your own dialplan and enjoy talking to your asterisk server. Best thing for Forever Alone guys ;)


References:
http://nerdvittles.com/?p=202 



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

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.

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.

Wednesday, October 19, 2011

Asterisk Stress testing (with Media) Using SIPp


Today I was required to stress test a client setup with media.
Signalling only can make us feel that our server is capable of handle call volume greater than even 2000 calls per asterisk server. This may not be the case when Media is involved in the call.

Using SIPp with some media file was causing me way too much troubling setting up. So what I did is, use Asterisk Servers as source for Media stream and trigger Bulk of Calls using SIPp. This Asterisk server then start a new call to Client's SBC.

The Dialplan of the originator Asterisk Servers was written such that whenever the call is answered by remote end it transfer the call flow control to another macro "play-media" where I waited for some time and then SendDTMF (required by the remote end) as Extension where I need call to be forwarded and then Start Playing MusicOnHold there.

This simple setup enabled me to load-test the deployment within minutes.

Following are the configurations and steps I took on Originator Asterisk Servers.

1- Install SIPp

#cd /usr/src
#wget http://sourceforge.net/projects/sipp/files/sipp/3.2/sipp.svn.tar.gz
#tar zxvf sipp.svn.tar.gz
#cd sipp.svn
#make

This installed SIPP on my machine. then I made its link to sbin directory so sipp be used anywhere from the shell.

#ln -s /usr/src/sipp.svn/sipp /usr/sbin

2- Add sipp user in sip.conf

#vim /etc/asterisk/sip.conf
Add these
[sipp]
type=friend
context=sipp
host=dynamic
port=7000
user=sipp
canreinvite=no
disallow=all
allow=ulaw

[CLIENTSBCPEER]
host=XX.XX.XX.XX
type=friend
port=5060
disallow=all
allow=alaw
allow=ulaw
allow=g729
dtmfmode=info
context=incoming
nat=yes
insecure=port,invite
qualify=yes
canreinvite=no

Save and quit.

2- Add Dialplan for use with sipp.

#vim /etc/asterisk/extensions.conf

Add these lines.
[sipp]
;include => default

exten => service,1,SET(count=1)
same => n,SET(CALLEER=${RAND(0,9999)})
same => n,SET(CALLERID(num)=$[4130000000 + ${CALLEER}])
same => n,DIAL(SIP/5609871234@CLIENTSBCPEER,,M(play-media))
same => n,hangup()

[macro-play-media]
exten => s,1,Wait(2)
same => n,SendDTMF(879123455232#) ; Destination Extension with indefinite MusicOnHold
same => n,Musiconhold(); Transmit Music from my side to remote server/extension
same => n,Hangup()

Save and exit to linux console.

3- Start sending Calls from sipps - I know its a long command

#sipp -pause_msg_ign -trace_err -users 1 -t u1 -watchdog_minor_maxtriggers 12000 -watchdog_major_maxtriggers 1000 -i [current.asterisk.server.ip] -p 7000 -aa -default_behaviors pingreply -sleep 1 -mp 10000 [current.asterisk.server.ip]:[asterisk_sip_port]


Finally you'll see calls starting in Originator Asterisk console and soon will be reaching destination machine.
Make sure your destination machine knows your Call attack is coming and the destination where you need calls be terminating could be an infinite MusiconHold() command so all the calls stay established while you keep in increasing the call volume.