Friday, June 24, 2011

Android Gingerbread and Asterisk PBX

(This article has been heavily revised since it was originally written to include some how-to advice)

One of the nice features of Blogger is it has some monitoring software built in that allows you to learn how many people are reading your blog, and, to a certain extent, why. The "Why" is generally 'This guy searched for 'why are my underpants green?' or 'what elephants eat bamboo shoots?' - as in, someone searched for those phrases on Google, and found my site in the search results.

No sooner had I posted my article today about a particular Asterisk PBX error message, than someone landed on my site on that very article - but, alas, they were searching for information about why they were having problems getting the Android Gingerbread client to talk to their Asterisk server.

Well, that's a good topic because I'd been researching just that. Let's talk about this in more detail.

Android Gingerbread (2.3) contains a built-in, fully integrated, SIP client. It's fairly clean, configures itself properly, and - I think quite intentionally - has very few configuration features.

Asterisk supports SIP natively. If someone has a SIP client, and you let them use it, they can make calls via your Asterisk PBX.

So, if Gingerbread supports SIP, and Asterisk does, this means the two ought to work OK, right? Well, it's hit and miss.

Gingerbread, Asterisk, and NAT

Let's first of all cut to the most likely reason why you're reading this article. You've configured your Gingerbread Android phone to use your Asterisk server. You're hoping it'll work, because you want to make Voice over IP calls over the cellular data network. Or perhaps you're OK with only making calls when you have Wifi coverage, but you're not having a lot of luck making calls from, say, coffee shops.

If your phone is behind NAT (and if you're on a mobile network, the chances are you are), then you'll find real problems getting the audio to work. The phone will happily register, you'll even hear ringing when you phone extensions over the SIP client, but once the phone's answered, you'll not hear anything. What's going on?

OK, here's the problem: Android's implementation of SIP is excellent. It's a clean implementation that does exactly what a SIP client should do.

So the issue's with Asterisk, right? Well, no, Asterisk's implementation of SIP is excellent. It's a clean implementation that does exactly what a SIP client should do, and then some.

Well, if it's not Asterisk and it's not Android, then where's the problem? Well, it's neither: it's the Internet. The Internet sucks.

To be precise, it's today's Internet that sucks. When we finally move over to IPv6, many of these problems will be resolved. But not yet.

To understand why, let's talk a little about SIP. SIP is designed to efficiently route a voice over IP call (actually it can do more than that, but VoIP is its primary use these days.) Generally speaking, there are three parties involved in a typical SIP call, a registration server, the caller, and the callee. In some cases, the registration server and the caller or callee is the same (at least from the point of view of either), but that's a topic for another time.

Now, how it's supposed to work is this. The caller places a SIP call. They start with the address of the callee, in the form sip:account@domain (eg sip:paul@sip.harritronics.com) The caller's client looks up "domain", figures out where the SIP service is associated with it, and then sends what's called an INVITE to that server for "account".

Prior to this, the callee has registered with the server. When the server gets the INVITE message, it checks the callee ("account") is registered, and if so sends an INVITE to the callee.

The callee then either accepts or declines the call. If they accept it, what generally then should happen is the registration server should ask both callee and caller's SIP clients how to contact one another, send each other the other's information, and then back out of the way. Or, if that's awkward, the server can act as an intermediary, but either way the server will need to know how each party wants to receive audio, either to tell the other party, or to route the audio itself.

Note - I know that's complicated, so let's explain it using two examples.
You make a call to party b@bsip.com. Your SIP client contacts your SIP proxy, and says it wants to make a call to b@bsip.com, which it does by sending an INVITE message. The SIP proxy finds the SIP server associated with bsip.com, and forwards the request. bsip.com, in turn, tells b@bsip.com that it has an incoming call.
Example 1: If your proxy wants to route the call manually, it'll ask your client where to send the audio from b@bsip.com. It'll then receive audio from b@bsip.com, and forward that audio to where your client asked it to be sent.
Example 2: If your proxy, and bsip.com, doesn't want to route the call, your proxy will ask your client where to send the audio from b@bsip.com. It'll then pass that information to bsip.com, which in turn will pass that information to b@bsip.com. b@bsip.com's client will then send audio directly to where your client asked it to be sent.
And herein lies the problem. Android's Gingerbread client gets the request, looks up its own Internet address, and says "Send it to me at this address." It sends the details to the server, and the server tries to send audio to that address.

But that address is, on a mobile network, almost always an Intranet address. (Just to add insult to injury, T-Mobile - for reasons I cannot fathom - has chosen to eschew the standard Intranet blocks and use some IP addresses allocated to the Department of Defence instead. I'm not making this up.) So when the server sends the address to the other party (or tries to send audio to it itself), it gets lost in the Internet.

OK, you may well ask, well, why not ignore the IP address the client sends, and just use the address the message came from? Well, because that would break SIP. Imagine, for a moment, if one of the "parties" involved is actually forwarding their calls. Maybe they're doing it directly, perhaps paul@sip.harritronics.com maps to paul@sip.stuart-office.harritronics.com; maybe they're doing it indirectly - a company that offers VoIP phone service might forward a call through a range of servers before it gets to you.

SIP's designers took this into account. The messages involved in setting up the call are forwarded, so that in the end the two clients will still end up having a direct connection (which is reliable and has little latency) rather than one where every packet is going through a hundred intermediate servers (which is inefficient, unreliable, and raises the latency.) If the intermediate server were to deliberately ignore the address (which is of the client involved) and put in the address the request came from (which might just be another server), then that would break everything.

So it's Gingerbread's fault for doing everything as it should. And it's Asterisk's fault for doing everything it should. And it's SIP's fault for being designed to be efficient. Or, well, it's the Internet's fault for sucking.

Why does the Internet suck? Because there aren't enough IP addresses, and so most people have to use NAT to get online. And NAT is a one way protocol, you can reach people outside of NAT, but you can't reach them within.

All of this said, is there a solution (beyond us moving over to IPv6)? Well, SIP is frequently used together with a protocol called STUN. STUN helps a SIP client determine what the IP address is that it's traffic is really going out on, and using some dirty tricks that many routers support, the SIP client can determine a configuration that might work for SIP.

It's dirty, it's not the way to do things, and that's probably why Gingerbread doesn't use STUN.

Asterisk and NAT

Now, at this point you're probably wondering if there's a workaround. After all, Asterisk's developers can't be happy that their system doesn't work with NAT. Well, Asterisk has some NAT workarounds, but they're imperfect. Asterisk is reluctant to assume that an IP address it's told to send data to is wrong, and it generally assumes that the client will make some sort of effort to get around NAT itself, meeting it in the middle. So while Asterisk even supports a "nat=" option in sip.conf, both universally and for each individual client, unless the client makes some kind of effort to present proper IP addresses itself (or by happy accident it works anyway) this will not be enough to make a client work from behind NAT.

Workarounds

There are very few workarounds that'll get Gingerbread working with Asterisk over NAT. Some things you should consider when trying to solve the problem yourself.

  • Have you considered a different protocol? Asterisk supports IAX, a "dumb" protocol that sacrifices efficiency for easier routability.  IAX clients exist for Android.
  • Likewise, SIP clients that are generally better at routing around NAT, and cooperating with Asterisk, are available, although I'll be honest with you and tell you I haven't had much luck with any of them.
  • Finally, you can use VPNs to give yourself direct access to the network running your Asterisk server. My only comment on this is that VPNs are a little messy in Android, they don't stay up for very long, and you generally have to reauthenticate yourself every time you connect to one. VPNs do work, however, they can be used to route SIP.

Using your Gingerbread device on your own network with Asterisk

OK, now we have the question you wanted answered answered in a way you probably didn't want, let's ask the related question "What are the settings I need to make it work at all?"

ie:
  • You're OK with it only working when directly connected to your network
  • You understand it'll probably not work when at a coffee shop
  • You understand it'll almost certainly never work when on cellular data
  • Although... that VPN thing will make it work too.
Here's the settings.

From my own sip.conf:

ignoreregexpire=yes

[paulscellphone]
type=friend                     ; Pretty much all devices that make both incoming and outgoing calls are "friends"
secret=password123       ; This is the password
host=dynamic                 ; We're not going to care what IP address the cellphone is using, but this is an area you can lock things down with
nat=yes                           ; nat=yes virtually never breaks anything, there's no reason not to have it on
directmedia=no               ; Because we're behind NAT we want the server to take care of audio routing
callerid=Paul's Cellphone <102> ; The caller ID we want to show internally 
context=harritronics-internal   ; "harritronics-internal" is the context I use for my office.
disallow=all                    ; Default - reject codec
allow=gsm                      ; Accept the GSM codec
allow=alaw                     ; Accept G.711 aLaw
allow=ulaw                     ; Accept G.711 uLaw
caninvite=no                   ; These settings confirm we want the PBX handling the audio
canreinvite=no
qualify=yes                    

The only two settings there that are unusual for the Gingerbread client are "qualify=yes" and "ignoreregexpire=yes". They cover the fact the Android Gingerbread SIP client doesn't renew its registration when you'd expect it to, and so Asterisk thinks it's fallen offline after a few minutes. 

Conclusion


So, that's the answer. You can use Gingerbread's SIP client as long as the client (and the Asterisk server) both have real IP addresses, or are both connected to the same network, without any problems. However, if the Gingerbread device is behind NAT, and not on the same network as the Asterisk server, then you're unlikely to get anything to work.

I hope that helps someone out there too.