Sunday, July 3, 2011

Asterisk review

Now that I'm using VoIP at home I've been playing around with Asterisk, finally. It's something I've wanted to play with, but I've never really had an excuse to use it.

I have about 90% of what I want, the remaining 10% being things like ways to transfer calls and some way to get calls to route using VoIP when I'm "on the road". Here's my initial thoughts.


1. It feels obsolete

I hate to start a review on a negative, and ironically I think it could well end up being "unobsolete" in the medium term, I'll explain why later, but here I am setting up a PBX with phone numbers and extensions, for phones that are fixed to a specific location. And, well, I've been using cellphones for so long now that the entire concept just seems like a throwback to the last Century. Which is not to say it's not awesome. If you were to take 1990s phone technology to the limit, well, this is what it would look like. It's just the major reason for a landline these days, outside of an office, is for a reliable, if all else fails, phone service - most of us now use cellphones for virtually everything. And that's probably why VoIP phone service is so cheap, it's not just that the infrastructure is cheaper, it's that giving people unlimited calls for $10 a month doesn't cost much when most of your customers are unlikely to make more than a couple of hours of calls a month.

Also making it feel like a throwback to another era - and I just mean "This is how it feels" not "I disagree with the concept and want new shiny things all the time" is that the entire system is controlled and configured using text-based configuration files and a built in command line interface. There is a kind-of web interface, but it's extremely limited. Hey, I like that it has these, as I said, I'm not complaining, it's just it feels a little twenty years ago.


2. It doesn't "just work"

I seriously underestimated the amount of work needed to put into an Asterisk installation for what you'd expect to be a basic configuration. The configuration was:

- A bunch of SIP accounts representing the phones in my home
- A link to a SIP account to register with to use for inbound/outbound "trunk" calling (ie calls to and from the PSTN, ie the VoIP provider's credentials.)
- A mapping between extension numbers and SIP account names.

This information is distributed across multiple files, and there's no simple "Here are my accounts, and these are the extensions they map onto" thing. You can play around with LDAP and various databases if you want to attempt to do the same thing, but that adds another layer of complexity.


What I did


In practice there are two configuration files that contain different parts of the same entities that need to be programmed with the logic of your configuration. One is the SIP configuration file, sip.conf, which contains most of the authentication and IP routing information (not call routing, but things like what ports to use, how/when to route around NAT, etc.) The other is the dialplan file, extensions.conf, that contains the whole "If the user dialed 100, call the living room phone" logic. There are shortcuts you can take, but the logic can get quite complex after a while. After trying to modify the existing example files I ended up starting from scratch with the dialplan.

Here's what it looks like:

[general]
static=yes
writeprotect=no
clearglobalvars=no
userscontext=default

[globals]
CONSOLE=Console/dsp ; Console interface for demo
TRUNK=SIP/voipo ; Trunk interface
TRUNKMSD=1 ; MSD digits to strip (usually 1 or 0)
__TRANSFER_CONTEXT=home

[default]
include => home
exten => s,1,Goto(incoming,s,1)

[internal]
include => trunk
include => home
include => gvoice-out
exten => dialtone,1,DISA(no-password,internal)

[no-such-extension]
exten => i,1,NoOp(No such extension)
exten => i,n,Playback(custom/ic_sit)
exten => i,n,Playback(ss-noservice)
exten => i,n,Goto(${SIPPEER(${SIPCHANINFO(peername)},context)}-no-such-extension,${INVALID-EXTEN},1)

[internal-no-such-extension]
exten => i,1,DISA(no-password,home)

[trunk]
exten => _NXXXXXX!,1,GoTo(trunk-voipo,${EXTEN},1)
exten => 123,1,GoTo(trunk-voipo,${EXTEN},1)
exten => _N11,1,GoTo(trunk-voipo,${EXTEN},1)
exten => _1NXXXXXXXXX!,1,GoTo(trunk-voipo,${EXTEN:1},1)
exten => _+1NXXXXXXXXX!,1,GoTo(trunk-voipo,${EXTEN:2},1)
exten => _+NX.,1,GoTo(trunk-gvoice,${EXTEN},1)
exten => _011XX.,1,GoTo(trunk-gvoice,+${EXTEN:3},1)

[trunk-voipo]
exten => _..,1,NoOp(Routing via VOIPO)
exten => _..,n,Dial(SIP/${EXTEN}@voipo,99,TK)
exten => h,1,NoOp(Call ended)

[trunk-gvoice]
exten => _..,1,NoOp(Routing via VOIPO)
exten => _..,n,Dial(gtalk/homebusiness/${EXTEN}@voice.google.com,99,TK)
exten => h,1,NoOp(Call ended)

[gvoice-out]
exten => _**1NXXXXXXXXX!,1,Dial(gtalk/homebusiness/+${EXTEN:2}@voice.google.com)
exten => _**N.,1,Dial(gtalk/homebusiness/+${EXTEN:2}@voice.google.com)

[home]
exten => _10XX!,1,NoOp("Regular extension called")
exten => _11XX!,1,NoOp("Regular extension called")
exten => _200X!,1,NoOp("Conference line called")
exten => _600X!,1,NoOp("Parked call called")
exten => _[a-z].,1,NoOp("Extension called by name")
exten => _..,2,Goto(home-main,${EXTEN},1)

[home-main]
exten => 1000,1,Goto(incoming,s,1)
exten => 1101,1,Goto(home,livingroom,1)
exten => 1103,1,Goto(home,mstrbdrm,1)
exten => 1151,1,Goto(home,loft,1)
exten => 1158,1,Goto(home,office,1)
exten => 1172,1,Goto(home,mylaptop,1)
exten => 1182,1,Goto(home,mycell,1)
exten => 1183,1,Goto(home,wifecell,1)
exten => 200X,1,MeetMe(${EXTEN})
exten => 600X,1,Goto(features-parked,${EXTEN},1)
include => home-regexten
exten => _[a-z].,2,Dial(SIP/${EXTEN}, 20, tk)
exten => 1199,1,Ringing
exten => 1199,n,Wait(2)
exten => 1199,n,Answer
exten => 1199,n,Wait(1)
exten => 1199,n,NoOp(${HANGUPCAUSE})
exten => 1199,n,NoOp(${DIALSTATUS})
exten => 1199,n,NoOp(CHANNEL(state))
exten => 1199,n,Playback(beep)
exten => 1199,n,Playback(hang-on-a-second)
exten => 1199,n,Playback(beep)
exten => 1199,n,Playback(hang-on-a-second-angry)
exten => 1199,n,Playback(beep)
exten => 1199,n,Playback(you-seem-impatient)
exten => 1199,n,Playback(pls-try-call-later)
exten => 1199,n,Playback(custom/ic_sit)
exten => 1199,n,Hangup
exten => i,1,NoOp(Redirecting for ${INVALID_EXTEN})
exten => i,n,Goto(home-alternates,${INVALID_EXTEN},1)
exten => i,n,NoOp("Didn't work")
exten => s,1,Goto(${ARG1},1)
exten => h,1,Hangup

[home-alternates]
exten => mycell,1,Set(DESTINATION=7721234567)
exten => wifecell,1,Set(DESTINATION=7722345678)
exten => i,1,Goto(no-such-extension,$(INVALID_EXTEN},1)
exten => _[a-z].,2,Ringing
exten => _[a-z].,n,Wait(2)
exten => _[a-z].,n,Answer
exten => _[a-z].,n,Wait(2)
exten => _[a-z].,n,Playback(followme/pls-hold-while-try)
exten => _[a-z].,n,Dial(SIP/${DESTINATION}@voipo, 20, tk)

[homebusinessgtalk]
exten => s,1,NoOp( Call from Gtalk to homebusiness )
exten => s,n,Set(crazygooglecid=${CALLERID(name)})
exten => s,n,Set(stripcrazysuffix=${CUT(crazygooglecid,@,1)})
exten => s,n,Set(CALLERID(num)=${stripcrazysuffix})
exten => s,n,Set(CALLERID(name)="From Google Talk for homebusiness")
exten => s,n,Dial(SIP/office, 20, tkD(:1))

[incoming]
exten => s,1,NoOp(Call from VoIP ${CALLERID(name)} ${CALLERID(number)})
exten => s,n,Dial(SIP/livingroom&SIP/laptop&SIP/mycell&SIP/wifecell&SIP/office, 30, tk)
exten => s,n,NoOp(Call timeout)

The two major contexts are "internal" and "incoming": "incoming" is used for calls coming from outside, and "internal" is used for calls within the network. VOIPO allows you to use your own devices, but they restrict international calls, so I configured international calls to be routed via Google Voice. The GV account is actually my "work" phone number (that is, my private LLC's) so at some point I should probably create a context that routes calls from SIP/office via Google Voice if it's not an internal extension.

There are some quirks I had to work around. For example, I wanted logic that would easily deal with invalid extensions. There's an "i" extension that's supposed to deal with this, but for reasons I can't fathom it only works if you "Goto" the invalid extension in your logic, if there's been no "Goto", it'll never get called and Asterisk will simply issue an error. For that reason, the [home] context above jumps into another context to do the work using "Goto".

Also you may notice references to a context I haven't defined above, called [home-regexten]. I'm going to cover that below. You may also notice that the logic above seems to rely upon extensions like "office" and "livingroom" being defined (see exten => 1101,1,Goto(home,livingroom,1) above?) That's also going to be explained below. It's all part of the same thing.

sip.conf is a little more complicated, and I'm not going to repost all of it here, especially as there's a lot of garbage in it. Here are some edited highlights:

[general]
context=incoming
allowguest=yes
allowoverlap=no ; Disable overlap dialing support. (Default is yes)
realm=sip.myhomedomain.org
bindport=9001 ; UDP Port to bind to (SIP standard port is 5060)
bindaddr=::
tcpenable=yes
transport=udp,tcp
srvlookup=yes ; Enable DNS SRV lookups on outbound calls
domain=sip.myhomedomain.org
domain=sip.squiggleslash.internal
defaultexpiry=600
disallow=all ; First disallow all codecs
allow=ulaw,alaw ; Allow codecs in order of preference
regcontext=home-regexten
recordhistory=yes ; Record SIP history by default
dumphistory=yes ; Dump SIP history at end of SIP dialogue
register => 77229876543:password@voipo
externhost = squiggleslash.dyndnsprovider.com
nat=yes
canreinvite=no
domain=sip.myhomedomain.org,home
domain=sip.squiggleslash.internal,home
allowexternaldomains=yes
fromdomain=sip.myhomedomain.org

I'm pretty sure an experienced Asterisk admin will see problems here too, but I spent a lot of time trying to optimize the configuration, and then restoring settings that I thought were unnecessary trying to fix what I had broken. For example, those "domain" entries.

Highlights:
  • bindport=9001 (There's nothing that says you need to bind to port 5060, and I wanted to clear a block of a couple of hundred UDP ports so I could route them to the Asterisk server for both SIP and RTP. SIP sets up calls, RTP does the actual "passing voice from one server to another" thingie.)
  • bindaddr=:: (This turns on IPv6 support. Despite what it looks like, it doesn't turn off IPv4 support, which is good.)
  • externhost = squiggleslash.dyndnsprovider.com (Tells Asterisk what its Internet address is. Useful to use a DynDNS service for this.)
  • regcontext=home-regexten - registers all extensions in this context with a dummy extry. I'll explain that in a moment.
  • canreinvite=no (I had to put this in multiple places to avoid problems with NAT. Basically, it makes Asterisk forward the audio to/from the Internet on behalf of each handset, rather than tell the handsets to attempt (and fail) to communicate directly with the other party. When we all switch over to IPv6, this kind of inefficient crap will become unnecessary.) From what I could figure out all the "nat=" settings actually only cover the SIP portion of the call, not the audio.
  • allowexternaldomains=yes is a hack that doesn't do what it looks like. Allexternaldomains should really be labeled "assumeanydomainismydomain", and it deals with the fact that just because you told Asterisk (multiple times!) to register itself as "sip.myhomedomain.org" doesn't mean you'll not get entirely legitimate calls to "s@" from your VoIP provider.
  • register => 77229876543:password@voipo (Registers with the VoIP provider, turning Asterisk into a client as far as the VoIP provider is concerned.)
  • realm=sip.myhomedomain.org - deals with an authentication issue. When a SIP client registers, it gets sent a realm to authenticate against. Some clients assume that if they're trying to register as squiggleslash@sip.myhomedomain.org, if they're not asked to authenticate against sip.myhomedomain.org that something's gone wrong and they've been configured to point at the wrong server. By default, Asterisk sends a realm of "Asterisk", which is wrong however you look at it.
For the VoIP provider, I added these:

[voipo-in]
type=peer
qualify=yes
insecure=port,invite
disallow=all
allow=ulaw
context=incoming

[voipo]
type=peer
secret=password
username=7729876543
fromuser=7729876543
fromdomain=sip.voipwelcome.com
host=sip.voipwelcome.com
outboundproxy=sip.voipwelcome.com
nat=yes ; Appears to be required for outgoing audio
context=incoming
caninvite=no ; Appears to be required for outgoing audio
canreinvite=no ; Appears to be required for outgoing audio
disallow=all
allow=ulaw,alaw
insecure=port,invite

I'll be quite honest, I don't know what's going on above - well, I kinda know, and kinda don't. I haven't figured out if the [voipo-in] section is necessary, given the [voipo] one allows "insecure" incoming connections.

Finally, the handsets all have entries like this:

[office]
type=friend
secret=password
regexten=office
host=dynamic
nat=yes
callerid=Office <1158>
context=internal

There are no major highlights to go through here. The "regexten" thing is unnecessary, as it turns out, as long as regcontext is defined. The phone number has to be specified in the callerid line as if it's to work properly in SIP's caller ID system. And because this is an internal extension, it gets a context=internal setting so that when it tries to make a call, the context in the dialplan it starts out at is [internal].

OK, I said I'd explain the regcontext thing above. When that's defined, when a device registers with Asterisk, an entry gets made in the context like:

exten => office,1,NoOp(This is a valid extension)

What happens is you're supposed to put something in the dialplan along the lines of this:

[maincontext]
include => registrationscontextname
exten => _..,2,Dial(SIP/${EXTEN})

The first line includes the context these are all registered in. The second line has a wild card of "match all", and means "For any extension, regardless of what it is, attempt to call a SIP account with the same name." However, the second line has a "priority" (better named sequence number I guess) of 2, which means that it will not get called unless there's a match with a sequence number of 1.

Confused, we'll go by example:

Let's suppose [office] is registered, but [livingroom] isn't, that means the dialplan looks like this:

[registrationscontextname]
exten => office,1,NoOp(...)

[maincontext]
include => registrationscontextname
exten => _..,2,Dial(SIP/${EXTEN})

Because [maincontext] includes [registrationcontextname], it actually looks like this:

[maincontext]
exten => office,1,NoOp(...) ; <---- from registrationscontextname
exten => _..,2,Dial(SIP/${EXTEN}) ; <---- direct

Now, a call comes in for "livingroom", from an account configured to start off in context "maincontext":

Asterisk looks for exten => 1,livingroom in [maincontext]. It fails. So it falls over and sends an error back to the caller.

A call comes in for "office", from the same device

Asterisk looks for exten => office,1 in [maincontext]. Success! It executes NoOp(...).
Because it was successful, Asterisk then looks for exten => office,2 in [maincontext], it finds _..2,Dial(SIP/${EXTEN}) which matches (because the wildcard matches "office" just fine), so Asterisk executes it, executing "Dial(SIP/office)"

Makes sense?

Other configuration changes I made were to the gtalk.conf and jabber.conf files, to support my Google Voice via Google Talk account, and to rtp.conf to use ports in a small range I'd configured my router to forward.


SIP and NAT

My cellphone only works with this system when I'm home and it's on Wifi, otherwise there's no NAT configuration I can make work. It might be I've been unlucky with the particular NAT systems I've been behind. One workaround that kinda works is setting up a VPN, which kinda makes my Android phone work, but that assumes the VPN is itself not blocked (one out of two of the networks I was behind blocked VPNs. D'oh!) and, even worse, I couldn't get the VPN to stay up for more than about five minutes before Android decides to unilaterally drop it.


Versions

I started off using Asterisk 1.4, but wanted Google Voice via Google Talk support, so I manually compiled and installed Asterisk 1.8, which turned out to be fairly easy, it has relatively few dependencies and even the ancient version of Ubuntu I was testing this on (8.04) had everything in the repositories to support a version that would work with Google Talk/Voice and LDAP (which ultimately I'd like to play with.)


Recommendation

If the above looked complicated, well, it was, and my wife started to actually get annoyed at the amount of time I was spending playing with this. I'd recommend an alternative but the SIP PBXes I was playing with prior to this simply didn't work, and I installed Asterisk in large part as a desperate "If all else fails" measure. That doesn't mean you can't get the other tools to work, and there were plenty, such as SER and its derivatives, I didn't even try to work on.

There are also a bunch of systems that package up Asterisk with a UI.

So basically, I did it wrong.

Well, kinda. Actually there are a bunch of aspects of the current configuration I rather like. One is that it's extremely efficient with memory. It's using a little over 18 megabytes right now with the above configuration. I was actually thinking it might work well if installed on a small tablet device like one of those $80 Archos 28 things, though something with Ethernet instead of Wifi would be preferable.

I've also barely scratched the surface of the functionality Asterisk has to offer. There are no features, voice mail isn't set up (though that's largely because I don't need it. The only thing I can think of would be to implement something Google Voice like for incoming calls, but I don't have experience of any voice transcription software for that job.) And it's kind of overpowered (to put it mildly) for what I want to do.

If you're going to play with Asterisk, 1.8 is a good start. You can use Google Voice to get a free VoIP account with free calling to the entire US, and that currently works great with Asterisk, even if it doesn't work with many other PBXes (because GTalk isn't SIP based.) Otherwise, well, try something simpler.

1 comment:

  1. The pain you've endured is the reason the Asterisk aggregations came into being. PBX in a Flash, for example, will do all the heavy lifting for you including a turnkey Google Voice installer in under an hour. If you really want the kitchen sink with extensions, trunks, IVRs, ring groups, and dozens of apps then Incredible PBX can do it in under 5 minutes. The real problem with the do-it-yourself method is security. Unless you've worked with Asterisk for a very long time, the odds of covering all of the security vulnerabilities without an expensive phone bill are pretty slim. Good luck!

    ReplyDelete

Replies are welcome, but be aware comments are moderated. Be friendly, on-topic, and all of the things I'm not!