Saturday, March 3, 2007

Propagating Asterisk MWI Across Multiple Servers

My experience with any type of technology is to get one thing working at a time. If you're relatively new to Asterisk I would suggest taking that approach with centralizing voicemail services. Get centralized voicemail working, then tackle MWI. If you're inexperienced with these concepts it could create a troubleshooting nightmare.

If you followed my last posting you've got centralized voicemail working. People can transfer messages across the organization regardless of what server their phone is registered to. Sooner or later (probably right away) you're going to want the Message Waiting Indicator (MWI) to start working.

Since Asterisk doesn't natively provide the ability to centralize voicemail it makes sense that it doesn't offer the ability to deploy MWI across multiple servers. Now, you could get in the source code and start hacking around, but that is overkill. With a better-than-newbie understanding of how voicemail and MWI services in Asterisk work, you can put together a solution to set the MWI on any phone in your organization, regardless of what server it is registered to.

There are a couple of things to understand about Asterisk voicemail and MWI:
  • MWI is turned on or off by the presence of a msgxxx.txt file in the INBOX directory for a given voicemail box (see http://asterisk.mdaniel.net/?p=14, and http://www.voip-info.org/wiki/view/Asterisk+MWI)
  • The "externnotify=" option in voicemail.conf allows you to run a program or script whenever a voicemail is received and also when someone exits the VoiceMailMain() application
  • When "externnotify" is processed it passes the context, extension and number of messages to the program or script you specify
In order to propagate MWI across all of my servers I pretty much follow the same strategy as other people who have done this before. However, I use a different tactic for the server to server communication. Instead of using ssh to pass the mailbox variables, I use a simple call over IAX. I really like this solution because I only have to maintain one type of connection and I don't have to setup ssh keys. If you happen to trunk your servers with SIP you could use that as well. The method I use is pretty much protocol agnostic.

I figured that I need to pass those three parameters to each of my non-voicemail servers: context, extension, number of messages. I googled around to see if I could set variables in IAX and pass them in a call. I found a lot of people requesting that ability, but didn't see the ability to actually do it. I thought about switching to SIP and setting custom headers, but that meant I would have to have an IAX trunk and a SIP trunk between each of my servers. I considered cramming all of the variables into the extension and parsing them out on the other side. Then it occurred to me that there is a way I could send three separate pieces of information without having to parse them on the other end. Here is what I ended up with:

Extension = Extension (aka voicemail box)

CallerIDName = Context
CallerIDNum = Number of Messages

It sounds like an ugly hack, but to me all that matters is that it works. All you need to transfer the status of a voicemail box between servers is a split second phone call.

Connecting the Servers


Since we are going to use a call from the voicemail server to the phone registration server to send the MWI status, you will need to create trunks that allow calls from your voicemail server to your phone registration servers. I prefer to associate these trunks with a seperate context called "setmwi". As I said before, creating an IAX trunk has been tutorialed to death, so I'm not going to waste the bits. Here is a diagram of the way I would do it.



Voicemail Server Configuration

Lets start with the externnotify script. When this script kicks off I want it to do the following

  • gather the extension, context, and number of messages variables
  • set those variables into the extension, calleridname and calleridnum of an IAX call to my phone registration servers
  • kick off the calls to my phone registration servers
My externnotify script creates .call files that accomplish the above objectives. For more info on .call files see the "Asterisk autodial out" page at the voip-info wiki. The script, which I've named notify_mwi.sh, is as follows:

#!/bin/bash

CONTEXT=$1
EXTEN=$2
NUMVMS=$3

cat < /var/spool/asterisk/1$EXTEN
Channel:IAX2/toDenver/$EXTEN
CallerID: $CONTEXT<$NUMVMS>
MaxRetries: 0
Extension: send
Context: notifymwi
Priority: 1
EOF
mv /var/spool/asterisk/1$EXTEN /var/spool/asterisk/outgoing

cat < /var/spool/asterisk/2$EXTEN
Channel:IAX2/toCSprings/$EXTEN
CallerID: $CONTEXT<$NUMVMS>
MaxRetries: 0
Extension: send
Context: notifymwi
Priority: 1
EOF
mv /var/spool/asterisk/2$EXTEN /var/spool/asterisk/outgoing


Create as many .call files as you have servers to notify. You'll notice that if the call connects successfully it passes it off to the local extension "send" in the context "notifymwi" on the voicemail server. This is really just a dummy extension on the voicemail server so that the call can be successfully connected. In extensions.conf the dummy extension looks like this:

[notifymwi]
exten => send,1,Answer
exten => send,2,wait(3) ;the other end will hangup fast anyway
exten => send,3,Hangup


Phone Registration Servers Configurations

I use the exact script to create and remove msgxxx.txt files that is posted at http://asterisk.mdaniel.net/?p=14. I don't want to take any credit I don't deserve!

Based upon the variables sent, the script creates the appropriate folders and msgxxx.txt files. If there is no voicemail, the script deletes any existing msgxxx.txt files. It looks like this:

#!/bin/bash

CONTEXT=$1
EXTEN=$2
NUMVMS=$3

NOCMD="rm -f /var/spool/asterisk/voicemail/$CONTEXT/$EXTEN/INBOX/*"
CREATECMD="mkdir -p /var/spool/asterisk/voicemail/$CONTEXT/$EXTEN/INBOX"

if [[ $NUMVMS = "0" ]]; then
$NOCMD
else
$NOCMD
$CREATECMD
for ((count = 1; count <= $NUMVMS; count++)); do
TOUCHCMD="touch /var/spool/asterisk/voicemail/$CONTEXT/$EXTEN/INBOX/msg$count.txt"
$TOUCHCMD
done
fi


Now we have to run the script when the voicemail server calls the phone registration servers. If configured properly, the IAX trunk is associated with the "setmwi" context. Utilizing the System() command, I can call the set_mwi.sh script when a call comes in to the "setmwi" context. Here is the entry in extensions.conf. All of the voicemail boxes in my scenario are four digit extensions so you may have to tweak this a bit depending on your specific application.

[setmwi]
exten => _XXXX,1,Answer
exten => _XXXX,2,System(/usr/local/bin/set_mwi.sh ${CALLERIDNAME} ${EXTEN} ${CALLERIDNUM})
exten => _XXXX,3,Hangup

Conclusion

That should be it. Leave a few voicemails and watch the "magic" happen. If you wanted to get fancy you could figure out a way to have the voicemail server only call the proper phone registration servers. To me it wasn't worth the complexity since the msgxxx.txt files don't take up much space and a half a second call doesn't take much bandwidth. Also, we have the same extension on multiple servers sometimes so it wouldn't be worth the administrative work to keep track of everything.

If you have any suggestions or refinements I would love to hear the feedback. Thanks!

*I should note that I have heard of some people using NFS to mount the voicemail directory from the voicemail server onto the voicemail directory of the phone registration servers. This allows the phone registration servers to see when a voicemail is left and the MWI gets set as it would normally. Depending on your environment, this may be a viable solution as well. Gotta love flexibility.

Thursday, March 1, 2007

Centralizing Asterisk Voicemail Services

* This posting assumes you are somewhat familiar with Asterisk. You should be able to setup Asterisk, register a few phones, and make calls inbetween them. You also should be familiar with interconnecting two or more Asterisk servers via SIP or IAX.

One of the challenges of deploying Asterisk in an enterprise with multiple locations is voicemail. If the local Voicemail() application is used at each site mailboxes will scattered across multiple servers. Users won't be able transfer voicemail to users outside of their server, several numbers will be needed to call in to retrieve voicemail, and if a web interface to retrieve voicemail is to be used several web addresses will need to be maintained. Additionally, if the WAN links are low bandwidth the backup of voicemail data to the main site could be very slow.

Centralizing voicemail services can help solve these problems. There is no configuration setting to have Asterisk utilize another server for voicemail services, but it can be easily configured through the dialplan. This question comes up from time to time on the listserv (once asked by me), so I figured I would document the solution I used.

The first step is to create connections from the various servers that need voicemail services to the server that will act as the centralized voicemail server. For a company with locations in Denver and Colorado Springs it will look something like the diagram below. It doesn't matter if you use IAX or SIP. There are plenty of "How To's" on how to trunk multiple servers together so I'm not going to re-invent that tutorial wheel.


Ok, now that you have your phone servers connected to the voicemail server it's time to tell the phone server to send unanswered or busy calls to the voicemail server. This is accomplished by making two small changes to the stdexten macro in extensions.conf.

Change this:

exten => s-NOANSWER,1,Voicemail(u${ARG1})
exten => s-NOANSWER,2,Goto(default,s,1)
exten => s-BUSY,1,Voicemail(b${ARG1})
exten => s-BUSY,2,Goto(default,s,1)


To this (I've named the IAX connection to the voicemail server "toVMail"):

exten => s-NOANSWER,1,Dial(IAX2/toVMail/u${ARG1})
exten => s-NOANSWER,2,Goto(default,s,1)
exten => s-BUSY,1,Dial(IAX2/toVMail/b${ARG1})
exten => s-BUSY,2,Goto(default,s,1)

We're now sending our busy and unanswered calls to the central voicemail server, but they have nothing to do once they get there. We need to create some extensions so we can
send the incoming calls to the Voicemail application. Put the following lines in extensions.conf of the voicemail server. Be sure to put them under the context that your server to server trunks are located in. I'm using a context called "incoming". Also note that I'm only using four digit extensions. Adjust for your application as needed.

[incoming]
exten => _bXXXX,1,Voicemail(${EXTEN}@default)
exten => _uXXXX,1,Voicemail(${EXTEN}@default)


Next an entry to allow people to call in and retrieve voicemail. I also put this under the "incoming" context and have designated the extension as 4000.

exten => 4000,1,VoiceMailMain(@default)

Be sure to setup the mailboxes you wish to host in voicemail.conf or by using the realtime architecture.

Lastly, don't for
get to setup your phone servers to send extension 4000 to the voicemail sever. Make some test calls to be sure everything is working.

Please note that the Message Waiting Indicator, aka MWI, will not work on your phones. Turning MWI on and off works between the phone and the server it is registered to. Never fear though, there are some tricks to make this happen that I'll go over in a later posting when I'm not so tired.

Learning is so much fun

The whole open source movement has been intriguing me since about 1997. I had a linux PC in my house that I used to browse the web, firewall my Cable modem, and perform NAT (before the days of a Linksys Router in every home), but thats as far as it went. It was for curiosity, not for anything mission critical.

A few years ago I saw the light that many OSS solutions were a true business grade solution and not just for hobbyists. I might have been late to the party, but I'm here now.

Anyway, as a newbie it is frustrating to struggle through things and figure them out. So, as a way of giving back (and so I don't forget my own solutions) I decided to throw up this blog. Who knows, maybe I'll put up two things and that will be it or maybe it will continue to grow. Either way it should serve some purpose.

Please feel free to leave comments and send questions. If I've done something that isn't the optimal way, I'd love the feedback!