Matthew Jordan

| Programming, Running, and Things
Tags Asterisk 12

Houston, We Have a Problem

“21055537: the number of failed authentication attempts against a public #Asterisk 12 server since January 11th” - @joshnet

— Matt Jordan (@mattcjordan) July 16, 2014

Yeah… that’s not cool.

When Josh told me how many failed authentication attempts his public Asterisk 12 server was getting, I wouldn’t say I was surprised: it is, after all, something of the wild west still on ye olde internet. I enjoy the hilarity of having fraud-bots break themselves on a tide of 401s. Seeing the total number of perturbed electrons is a laugh. But you know what’s more fun? Real-time stats.

I wondered: would it be possible to get information about the number of failed authentication attempts in real-time? If we included the source IP address, would that yield any interesting information?

Statistics are fun, folks. And, plus, it is for security. I can justify spending time on that. These people are clearly bad. Our statistics will serve a higher purpose. This is for a good cause.

For great justice!

Stasis: Enter the message bus

We’re going to start off using our sample module. In Asterisk 12 and later versions, the Stasis message bus will publish notifications about security events to subscribed listeners. We can use the message bus to get notified any time an authentication fails.

Getting Stasis moving

I’m making the assumption that we’ve copied our res_sample_module.c file and named it res_auth_stats.c. Again, feel free to name it whatever you like.

  1. First, let’s get the right headers pulled into our module. We’ll probably want the main header for Stasis, asterisk/stasis.h. Stasis has a concept called a message router, which simplifies a lot of boiler plate code that can grow when you have to handle multiple message types that are published on a single topic. However, in our case, we only have a single message type, ast_security_event_type, that will be published to the topic we’ll subscribe to. As such, it really is overkill for this application, so we’ll just deal with managing the subscription ourselves. Said Stasis topic for security events is defined in asterisk/security_events.h, so let’s add that as well. Finally, the payload in the message that will be delivered to us is encoded in JSON, so we’ll need to add asterisk/json.h too.

    1
    2
    3
    4
    #include "asterisk/module.h"
    #include "asterisk/stasis.h"
    #include "asterisk/security_events.h"
    #include "asterisk/json.h"
  2. In load_module, we’ll subscribe to the ast_security_topic and tell it to call handle_security_event when the topic receives a message. The third parameter to the subscription function let’s us pass an object to the callback function whenever it is called; we don’t need it, so we’ll just pass it NULL. We’ll want to keep that subscription, so at the top of our module, we’ll declare a static struct stasis_subscription *.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /*! Our Stasis subscription to the security topic */
    static struct stasis_subscription *sub;

    ...

    static int load_module(void)
    {
    sub = stasis_subscribe(ast_security_topic(), handle_security_event, NULL);
    if (!sub) {
    return AST_MODULE_LOAD_FAILURE;
    }

    return AST_MODULE_LOAD_SUCCESS;
    }
  3. Since we’re subscribing to a Stasis topic when the module is loaded, we also need to unsubcribe when the module is unloaded. To do that, we can call stasis_unsubscribe_and_join - the join implying that the unsubscribe will block until all current messages being published to our subscription have been delivered. This is important, as unsubscribing does not prevent in-flight messages from being delievered; since our module is unloading, this is likely to have “unhappy” effects.

    1
    2
    3
    4
    5
    6
    7
    static int unload_module(void)
    {
    stasis_unsubscribe_and_join(sub);
    sub = NULL;

    return 0;
    }
  4. Now, we’re ready to implement the handler. Let’s get the method defined:

    1
    2
    3
    4
    5
    static void handle_security_event(void *data, struct stasis_subscription *sub,
    struct stasis_message *message)
    {

    }

    A Stasis message handler takes in three parameters:

    • A void * to a piece of data. If we had passed an ao2 object when we called stasis_subscribe, this would be pointing to our object. Since we passed NULL, this will be NULL every time our function is invoked.

    • A pointer to the stasis_subscription that caused this function to be called. You can have a single handler handle multiple subscriptions, and you can also cancel your subscription in the callback. For our purposes, we’re (a) going to always be subscribed to the topic as long as the module is loaded, and (b) we are only subscribed to a single topic. So we won’t worry about this parameter.

    • A pointer to our message. All messages published over Stasis are an instance of struct stasis_message, which is an opaque object. It’s up to us to determine if we want to handle that message or not.

  5. Let’s add some basic defensive checking in here. A topic can have many messages types published to it; of these, we know we only care about ast_security_event_type. Let’s ignore all the others:

    1
    2
    3
    4
    5
    6
    7
    8
    static void handle_security_event(void *data, struct stasis_subscription *sub,
    struct stasis_message *message)
    {
    if (stasis_message_type(message) != ast_security_event_type()) {
    return;
    }

    }
  6. Now that we know that our message type is ast_security_event_type, we can safely extract the message payload. The stasis_message_payload function extracts whatever payload was passed along with the struct stasis_message as a void *. It is incredibly important to note that by convention, message payloads passed with Stasis messages are immutable. You must not change them. Why is this the case?
    A Stasis message that is published to a topic is delivered to all of that topic’s subscribers. There could be many modules that are intrested in security information. When designing Stasis, we had two options:

    • (1) Do a deep copy of the message payload for each message that is delivered. This would incur a pretty steep penalty on all consumers of Stasis, even if they did not need to modify the message data. Publishers would also have to implement a copy callback for each message payload.

    • (2) Pretend that the message payload is immutable and can’t be modified (this is C after all, if you want to shoot yourself in the foot, you’re more than welcome to). If a subscriber needs to modify the message data, it has to copy the payload itself.

      For performance reasons, we chose option #2. In practice, this has worked out well: many subscribers don’t need to change the message payloads; they merely need to know that something occurred.

      Anyway, the code:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      struct ast_json_payload *payload;

      if (stasis_message_type(message) != ast_security_event_type()) {
      return;
      }

      payload = stasis_message_data(message);
      if (!payload || !payload->json) {
      return;
      }

      Note that our payload is of type struct ast_json_payload, which is a thin ao2 wrapper around a struct ast_json object. Just for safety’s sake, we make sure that both the wrapper and the underlying JSON object aren’t NULL before manipulating them.

  7. Now that we have our payload, let’s print it out. The JSON wrapper API provides a handy way of doing this via ast_json_dump_string_format. This will give us an idea of what exactly is in the payload of a security event:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    static void handle_security_event(void *data, struct stasis_subscription *sub,
    struct stasis_message *message)
    {
    struct ast_json_payload *payload;
    char *str_json;

    if (stasis_message_type(message) != ast_security_event_type()) {
    return;
    }

    payload = stasis_message_data(message);
    if (!payload || !payload->json) {
    return;
    }

    str_json = ast_json_dump_string_format(payload->json, AST_JSON_PRETTY);
    if (str_json) {
    ast_log(LOG_NOTICE, "Security! %s\n", str_json);
    }
    ast_json_free(str_json);
    }

Stasis in action

Let’s make a security event. While there’s plenty of ways to generate a security event in Asterisk, one of the easiest is to just fail an AMI login. You’re more than welcome to do any failed (or even successful) login to test out the module, just make sure it is through a channel driver/module that actually emits security events!

AMI Demo

Build and install the module, then:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ telnet 127.0.0.1 5038
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Asterisk Call Manager/2.4.0
Action: Login
Username: i_am_not_a_user
Secret: nope

Response: Error
Message: Authentication failed

Connection closed by foreign host.

In Asterisk, we should see something like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
*CLI> [Aug  3 16:00:51] NOTICE[12019]: manager.c:2959 authenticate: 127.0.0.1 tried to authenticate with nonexistent user 'i_am_not_a_user'
[Aug 3 16:00:51] NOTICE[12019]: manager.c:2996 authenticate: 127.0.0.1 failed to authenticate as 'i_am_not_a_user'
[Aug 3 16:00:51] NOTICE[12008]: res_auth_stats.c:60 handle_security_event: Security! {
"SecurityEvent": 1,
"EventVersion": "1",
"EventTV": "2014-08-03T16:00:51.052-0500",
"Service": "AMI",
"Severity": "Error",
"AccountID": "i_am_not_a_user",
"SessionID": "0x7f50ae8aebd0",
"LocalAddress": "IPV4/TCP/0.0.0.0/5038",
"RemoteAddress": "IPV4/TCP/127.0.0.1/57546",
"SessionTV": "1969-12-31T18:00:00.000-0600"
}
== Connect attempt from '127.0.0.1' unable to authenticate

Great success!

Dissecting the event

There’s a few interesting fields in the security event that we could build statistics from, and a few that we should consider carefully when writing the next portion of our module. In no particular order, here are a few thoughts to guide the next part of the development:

  • There are a number of different types of security events, which are conveyed by the SecurityEvent field. This integer value corresponds to the enum ast_security_event_type. There are a fair number of security events that we may not care about (at least not for this incarnation of the module). Since what we want to track are failed authentication attempts, we will need to filter out events based on this value.

  • The Service field tells us who raised the security event. If we felt like it, we could use that to only look at SIP attacks, or failed AMI logins, or what not. For now, I’m going to opt to not care about where the security event was raised from: the fact that we get one is sufficient.

  • The RemoteAddress is interesting: it tells us where the security issue came from. While we’re concerned with statistics - and I think keeping track of how many failed logins a particular source had is pretty interesting - for people using fail2ban, iptables, or other tools to restrict access, this is a pretty useful field. Consume, update; rinse, repeat.

STATS!

Let’s get rid of the log message and start doing something interesting. In Asterisk 12, we added a module, res_statsd, that does much what its namesake implies: it allows Asterisk to send stats to a StatsD server. StatsD is really cool: if you have a statistic you want to track, it has a way to consume it. With a number of pluggable backends, there’s also (usually) a way to display it. And it’s open source!

In the interest of full disclosure, installing statsd on my laptop hit a few … snags. Libraries and what-not. I’ll post again with how well this module works in practice. For now, let’s just hope the theory is sound.

To use this module, we’ll want to pull in Asterisk’s integration library with StatsD, statsd.h.

1
2
3
4
...
#include "asterisk/json.h"
#include "asterisk/statsd.h"
...

And we should go ahead and declare that our module depends on res_statsd:

1
2
3
4
/*** MODULEINFO
<support_level>extended</support_level>
<depend>res_statsd</depend>
***/

Since we’re not going to print out the Stasis message any more, go ahead and delete the char *str_json. Now that we know we’re getting messages, let’s filter out the ones we don’t care about:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static void handle_security_event(void *data, struct stasis_subscription *sub,
struct stasis_message *message)
{
struct ast_json_payload *payload;
int event_type;

if (stasis_message_type(message) != ast_security_event_type()) {
return;
}

payload = stasis_message_data(message);
if (!payload || !payload->json) {
return;
}

event_type = ast_json_integer_get(ast_json_object_get(payload->json, "SecurityEvent"));
switch (event_type) {
case AST_SECURITY_EVENT_INVAL_ACCT_ID:
case AST_SECURITY_EVENT_INVAL_PASSWORD:
case AST_SECURITY_EVENT_CHAL_RESP_FAILED:
break;
default:
return;
}
}

Here, after pulling out the payload from the Stasis message, we get the SecurityEvent field out and assign it to an integer, event_type. Note that we know that the value will be one of the AST_SECURITY_EVENT_* values. In my case, I only care when someone:

  • Fails to provide a valid account

  • Fails to provide a valid password

  • Fails a challenge check (rather important for SIP)

So we bail on any of the event types that aren’t one of those.

The first stat I’ll send to StatsD are the number of times a particular address trips one of those three security events. StatsD uses a period delineated message format, where each period denotes a category of statistics. The API provided by Asterisk’s StatsD module lets us send any statistic using ast_statsd_log. In this case, we want to just simply bump a count every time we get a failed message, so we’ll use the statistic type of AST_STATSD_METER.

Using a struct ast_str to build up the message we sent to StatsD, we’d have something that looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static void handle_security_event(void *data, struct stasis_subscription *sub,
struct stasis_message *message)
{
struct ast_str *remote_msg;
struct ast_json_payload *payload;
int event_type;

if (stasis_message_type(message) != ast_security_event_type()) {
return;
}

payload = stasis_message_data(message);
if (!payload || !payload->json) {
return;
}

event_type = ast_json_integer_get(ast_json_object_get(payload->json, "SecurityEvent"));
switch (event_type) {
case AST_SECURITY_EVENT_INVAL_ACCT_ID:
case AST_SECURITY_EVENT_INVAL_PASSWORD:
case AST_SECURITY_EVENT_CHAL_RESP_FAILED:
break;
default:
return;
}

remote_msg = ast_str_create(64);
if (!remote_msg) {
return;
}

ast_str_set(&remote_msg, 0, "security.failed_auth.%s.%s",
ast_json_string_get(ast_json_object_get(payload->json, "Service")),
ast_json_string_get(ast_json_object_get(payload->json, "RemoteAddress")));
ast_statsd_log(ast_str_buffer(remote_msg), AST_STATSD_METER, 1);

ast_free(remote_msg);
}

Cool! If we get an attack from, say, 192.168.0.1 over UDP via SIP, we’d send the following message to StatsD:

1
security.failed_auth.SIP.UDP/192.168.0.1/5060

Except, we have one tiny problem… IP addresses are period delineated. Whoops.

(cleaner) STATS!

We really want the address we receive from the security event to be its own “ID”, identifying what initiated the security event. That means we really need to mark the octets in an IPv4 address with something other than a ‘.’. We also need to lose the port: if the connection is TCP based, that port is going to bounce all over the map (and we probably don’t care which port it originated from either). Since the address is delineated with a ‘/‘ character, we can just drop the last bit of information that’s returned to us in the RemoteAddress field. Let’s write a few helper functions to do that:

1
2
3
4
5
6
7
8
9
10
11
12
13
static char *sanitize_address(char *buffer)
{
char *current = buffer;

while ((current = strchr(current, '.'))) {
*current = '_';
}

current = strrchr(buffer, '/');
*current = '\0';

return buffer;
}

Note that we don’t need to return anything here, as this modifies buffer in place, but I find those semantics to be nice. Using the return value makes the modification of the buffer parameter obvious.

That should turn this:

1
IPV4/TCP/127.0.0.1/57546

Into this:

1
IPV4/TCP/127_0_0_1

Nifty.

Since we used a struct ast_str * to build our message, we’ll need to pull out the RemoteAddress into a char * to manipulate it.

REMEMBER: STASIS MESSAGES ARE IMMUTABLE.

We’re about to mutate a field in it; you cannot just muck around with this value in the message. Let’s do it safely:

1
2
3
4
5
6
char *remote_address;

...

remote_address = ast_strdupa(ast_json_string_get(ast_json_object_get(payload->json, "RemoteAddress")));
remote_address = sanitize_address(remote_address);

Better. With the rest of the code, this now looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static void handle_security_event(void *data, struct stasis_subscription *sub,
struct stasis_message *message)
{
struct ast_str *remote_msg;
struct ast_json_payload *payload;
char *remote_address;
int event_type;

if (stasis_message_type(message) != ast_security_event_type()) {
return;
}

payload = stasis_message_data(message);
if (!payload || !payload->json) {
return;
}

event_type = ast_json_integer_get(ast_json_object_get(payload->json, "SecurityEvent"));
switch (event_type) {
case AST_SECURITY_EVENT_INVAL_ACCT_ID:
case AST_SECURITY_EVENT_INVAL_PASSWORD:
case AST_SECURITY_EVENT_CHAL_RESP_FAILED:
break;
default:
return;
}

remote_msg = ast_str_create(64);
if (!remote_msg) {
return;
}

service = ast_json_string_get(ast_json_object_get(payload->json, "Service"));
remote_address = ast_strdupa(ast_json_string_get(ast_json_object_get(payload->json, "RemoteAddress")));
remote_address = sanitize_address(remote_address);

ast_str_set(&remote_msg, 0, "security.failed_auth.%s.%s", service, remote_address);
ast_statsd_log(ast_str_buffer(remote_msg), AST_STATSD_METER, 1);

ast_free(remote_msg);
}

Yay! But what else can we do with this?

MOAR (cleaner) STATS!

So, right now, we’re keeping track of each individual remote address that fails authentication. That may be a bit aggressive for some scenarios - sometimes, we may just want to know how many SIP authentication requests have failed. So let’s track that. We’ll use a new string buffer (dual purposing buffers just feels me with ewww), and populate it with a new stat:

1
2
3
4
service = ast_json_string_get(ast_json_object_get(payload->json, "Service"));

ast_str_set(&count_msg, 0, "security.failed_auth.%s.count", service);
ast_statsd_log(ast_str_buffer(count_msg), AST_STATSD_METER, 1);

Since we’re unlikely to get a remote address of count, this should work out okay for us. With the rest of the code, this looks like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static void handle_security_event(void *data, struct stasis_subscription *sub,
struct stasis_message *message)
{
struct ast_str *remote_msg;
struct ast_str *count_msg;
struct ast_json_payload *payload;
const char *service;
char *remote_address;
int event_type;

if (stasis_message_type(message) != ast_security_event_type()) {
return;
}

payload = stasis_message_data(message);
if (!payload || !payload->json) {
return;
}

event_type = ast_json_integer_get(ast_json_object_get(payload->json, "SecurityEvent"));
switch (event_type) {
case AST_SECURITY_EVENT_INVAL_ACCT_ID:
case AST_SECURITY_EVENT_INVAL_PASSWORD:
case AST_SECURITY_EVENT_CHAL_RESP_FAILED:
break;
default:
return;
}

remote_msg = ast_str_create(64);
count_msg = ast_str_create(64);
if (!remote_msg || !count_msg) {
ast_free(remote_msg);
ast_free(count_msg);
return;
}

service = ast_json_string_get(ast_json_object_get(payload->json, "Service"));

ast_str_set(&count_msg, 0, "security.failed_auth.%s.count", service);
ast_statsd_log(ast_str_buffer(count_msg), AST_STATSD_METER, 1);

remote_address = ast_strdupa(ast_json_string_get(ast_json_object_get(payload->json, "RemoteAddress")));
remote_address = sanitize_address(remote_address);

ast_str_set(&remote_msg, 0, "security.failed_auth.%s.%s", service, remote_address);
ast_statsd_log(ast_str_buffer(remote_msg), AST_STATSD_METER, 1);

ast_free(remote_msg);
ast_free(count_msg);
}

In Conclusion

And there we go! Statistics of those trying to h4x0r your PBX, delivered to your StatsD server. In this particular case, getting this information off of Stasis was probably the most direct method, since we want to programmatically pass this information off to StatsD. On the other hand, since security events are now passed over AMI, we could do this in another language as well. If I wanted to update iptables or fail2ban, I’d probably use the AMI route - it’s generally easier to do things in Python or JavaScript than C (sorry C afficianados). On the other hand: this also makes for a much more interesting blog post and an Asterisk module!

We released Asterisk 12 on Friday. So, that’s a thing.

There’s a lot to talk about in Asterisk 12 - as mentioned when we released the alpha, there’s a lot in Asterisk 12 that makes it unlike just about any prior release of Asterisk. A new SIP channel driver, all new bridging architecture (which is pretty much the whole “make a call” part of Asterisk), redone CDR engine (borne out of necessity, not choice), CEL refactoring, a new internal publish/subscribe message bus (Stasis), AMI v2.0.0 (build on Stasis), the Asterisk REST Interface (ARI, also built on Stasis), not to mention the death of chan_agent - replaced by AppAgentPool, completely refactored Parking, adhoc multi-party bridging, rebuilt features, the list goes on and on. Phew. It’s a lot of new code.

Lots of blog posts to write on all that content.

I’ve been thinking a lot about how we got here. The SIP channel driver is easiest: I’ll start there.

Ever since I was fortunate enough to find myself working on Asterisk - now two years, five months ago (where does the time go!) - it was clear chan_sip was a problem. Some people would probably have called it “the problem”. You can see why - output of sloccount below:

  • 1.4 - 15,596

  • 1.6.2 - 19,804

  • 1.8 - 23,730

  • 11 - 25,823

  • 12 - 25,674

Now, I’m not one for measuring much by SLOC. Say what you will about Bill Gates, but he got it right when he compared measuring software progress by SLOC to measuring aircraft building progress by weight. That aside, no matter who you are, I think you can make two statements from those numbers:

  1. The numbers go up - which means we just kept on piling more crap into chan_sip

  2. The SLOC is too damned high

I don’t care who you are, 15000 lines of code in a single file is hard to wrap your head around. 25000 is just silly. To be honest, it’s laughable. (As an aside: when I first started working on Asterisk and saw chan_sip (and app_voicemail, but that’s another story), I thought - “oh hell, what did I get myself into”. And that wasn’t a good “oh hell”.)

It isn’t like people haven’t tried to kill it off. Numerous folks in the Asterisk community have taken a shot at it - sometimes starting from scratch, sometimes trying to refactor it. For various reasons, the efforts failed. That isn’t an insult to anyone who attempted it: writing a SIP channel driver from scratch or refactoring chan_sip is an enormous task. It’s flat out daunting. It isn’t just writing a SIP stack - it’s integrating it all into Asterisk. And, giving credit to chan_sip, there’s a lot of functionality bundled into that 15000/25000 lines of code. Forget calling - everything from a SIP registrar, SIP presence agent (kinda, anyway), events, T.38 fax state, a bajillion configuration parameters is implemented in there. The 10% rule applies especially to this situation - you need to implement 90% of the functionality in chan_sip to have a useful SIP stack, and the 10% that you don’t implement is going to be the 10% everyone cares about. And none of them will agree on what that 10% is.

As early as Asterisk 11, a number of folks that I worked with had starting thinking about what it would take to rewrite chan_sip. At the time, I was a sceptic. I’m a firm believer in not writing things from scratch: 90% of new software projects fail (thanks Richard Kulisz for the link to that - I knew the statistic from somewhere, but couldn’t find it.) (And there’s a lot of these 90/10 rules, aren’t there? I wonder if there’s a reason for that?). Since so many new software projects fail - and writing a new SIP channel driver would certainly be one - I figured refactoring the existing chan_sip would be a better course. But I was wrong.

Refactoring chan_sip would be the better course if it had more structure, but the truth is: it doesn’t. Messages come in and get roughly routed to SIP message type handlers; that’s about it. There’s code that gets re-used for different types of messages, and change their behaviour based on the type of message. But a lot of that is just bad implementation and bad organizational design; worse is the bad end user design. You can’t fix users.conf; you can’t fix peers/friends/users in sip.conf. (And no, the rules for peers versus friends isn’t consistent.) Those are things that even if you had a perfect implementation you’d still have to live with, and that’s not something I think any one wants to support forever.

But, I don’t believe that not liking something is justification for replacing it. After all, chan_sip makes a lot of phone calls. And that’s not nothing. So, why write a new SIP channel driver?

As someone who has to act as the Scrum master during sprint planning and our daily scrums, I know we spend a lot of time on chan_sip. Running the statistics, about 25% of the bugs in Asterisk are against chan_sip; anecdotally, half of the time we spend is on chan_sip. When we’re in full out bug fix mode - which is a lot of the time - that’s half of an eight man development team doing nothing but fixing bugs in one file. Given its structure, even with a lot of testing, we still find ourselves introducing bugs as we fix new ones. Each regression means we spent more than twice the cost on the original bug: the cost to fix it, the cost to deal with triaging and diagnosing the resulting regression report, the cost to actually fix the regression, the impact to whatever issue doesn’t get fixed because we’re now fixing the regression, the cost to write the test to ensure we don’t regress again, etc. What’s more, all of the time spent patching those bugs is time that isn’t spent on new features, new patches from the community, improving the Asterisk architecture, and generally moving the project forward.

Trying to maintain chan_sip is like running in place: you’re doing a lot, but you aren’t going anywhere.

A few weeks before AstriDevCon in 2012, we were convinced that we should do something. There wasn’t any one meeting, but over the months leading up to it, the thought of rewriting chan_sip crossed a number of people’s minds. There were a few factors converging that motivated us prior to the developer conference:

  • In my mind, the knowledge that we were spending half of the team’s energy on merely maintaining a thing was motivation enough to do something about it.

  • The shelving of Asterisk SCF. Regardless of the how and the why that occurred, the result was that we had learned a lot about how to write a SIP stack that was not chan_sip. Knowing that it could be done was a powerful motivator to do it in Asterisk.

  • Asterisk 11. We had spent a lot of time and energy making that as good of an LTS as we thought we could: so if we were going to do something major in Asterisk, the time was definitely now.

  • As we went into AstriDevCon, the foremost question was, “would the developer community be behind it? Would they want to go along with us?”

As it turned out: yes. In fact, I wasn’t the first one to bring it up at AstriDevCon - Jared Smith (of BlueHost) was. And once the ball got rolling, there wasn’t any stopping it.

The rest, as they say, is history. And so, on Friday, the successor to chan_sip was released to the world.

There’s a long ways to go still. Say what you will about chan_sip (and you can say a lot), it is interoperable with a lot of devices, equipment, ITSPs, and all sorts of other random SIP stacks. It does “just” work. And chan_pjsip - despite all of our testing and the knowledge that it is built on a proven SIP stack, PJSIP - has not been deployed. It will take time to make it as interoperable as chan_sip is. But we’ll get there, and the first biggest step has been taken.

The best part: all of the above was but one project in Asterisk 12! Two equally large projects were undertaken at the same time - the core refactoring and Stasis/ARI - because if you’re going to re-architect your project, you might as well do as much as you can. But more on that another time.

So, like a lot of people, as soon as I could I got my hands on a Raspberry Pi (I got mine from Adafruit and was very happy with the experience). If you don’t know what a Raspberry Pi is, it’s the greatest thing since sliced bread - a small single board computer that runs Linux for only thirty-five bucks. Along with a case, a good power supply, and a few other odds and ends, it’s still a steal, coming in somewhere under seventy-five bucks. They’re ridiculously flexible. They can be used for a whole host of microcontroller projects, but are also powerful enough to act as mini home “servers”. For awhile now, I’ve planned to set mine up as a home Asterisk system. In particular, my poor wife - who happens to work from home - has had to make do without much in the way of good business communications (there’s a story in there somewhere about a cobbler and his kids not having any shoes). Unfortunately, just about every waking moment for the past six months has been spent on getting Asterisk 12 developed and released, so my poor Pi has sat on a desk collecting dust.

Today, however, no more. I got the Pi out, plugged it in, connected it to my home network, and got tinkering. The goal: get Asterisk 12 running on a Raspberry Pi. By running, I mean just running - configuration is a bit beyond the scope of today (or this blog post) - but if I can get myself to the CLI prompt, that will do. As an aside, this is realistic: the Raspberry Pi does not compile quickly. I ended up running a lot of errands between steps, so this whole project was stretched out quite a bit over today.

It’s not often that a developer gets to take a step back and look at things from a user’s perspective, so this should be a lot of fun.

As another aside, I think like most folks using a Raspberry Pi for the first time, I found the experience to be quite a pleasure. While I would never claim that I’m a Linux guru (I did work in Microsoft shops for quite a long time; I still find myself moving back and forth between the two worlds), I can get around just fine in the major Linux distros and have no problems setting up a Linux system. Even still, I was still pleasantly surprised at the configuration tools that come with the Pi. They really nailed their target audience, and even for those of us who use Linux daily, they still made life nice and easy.

Moving on!

I spent the first bit just configuring the Pi with the tools. I set it up with a static IP address behind my home router, updated Wheezy, changed the default user’s password to something secure, and changed the hostname to ‘mjordan-pi’ (terribly original, I know. I’m not good at naming things - I leave that to Mr. Joshua Colp on our team)

At this point, I figured I should be good to go on the actual task of getting Asterisk downloaded, installed, and configured. While the folks at Asterisk for Raspberry Pi have done a spectacular job of documenting and making it easy to install Asterisk on a Pi, I’m going to depart from their guidelines here. While I love FreePBX, I’m going to eschew a GUI as well as MySQL. I really want a relatively stream-lined install of Asterisk 12, with .conf files for configuration, access through the CLI, and everything done by hand. When we get to configuration, you’ll note that I’m going to take a pretty slow and conservative approach to configuration - but that will let me look at each module and step and really digest what I’m doing. We’re going old school here. Should be fun!

Step 1: Get Asterisk

Since I’m working through ssh from my laptop, everything is going to be done through the shell. That means downloading Asterisk using wget from downloads.asterisk.org.

1
2
3
4
5
6
7
8
9
pi@mjordan-pi ~ $ wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-12-current.tar.gz
--2013-09-01 16:27:28-- http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-12-current.tar.gz
Resolving downloads.asterisk.org (downloads.asterisk.org)... 76.164.171.238, 2001:470:e0d4::ee
Connecting to downloads.asterisk.org (downloads.asterisk.org)|76.164.171.238|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 56720157 (54M) [application/x-gzip]
Saving to: `asterisk-12-current.tar.gz'
100%[====================================================================================================>] 56,720,157 982K/s in 61s
2013-09-01 16:28:29 (905 KB/s) - `asterisk-12-current.tar.gz' saved [56720157/56720157]

Hooray! We got it. Now to untar it:

1
pi@mjordan-pi ~ $ tar -zvxf asterisk-12-current.tar.gz

As you may notice, Asterisk 12 is a bit large (what can I say, we did a lot of work). Some of that heft comes from the exported documentation from the Asterisk wiki, in the form of the Asterisk Administrators Guide. Since I don’t really need that on my Pi, I’m going to go ahead and get rid of it, as well as the tarball now that I’ve extracted it.

1
2
3
pi@mjordan-pi ~ $ rm asterisk-12-current.tar.gz
pi@mjordan-pi ~ $ rm asterisk-12.0.0-alpha1/Asterisk-Admin-Guide-12.html.zip
pi@mjordan-pi ~ $ rm asterisk-12.0.0-alpha1/doc/Asterisk-Admin-Guide-12.pdf

Random note here. You might be wondering why the PDF is in the doc subfolder, while the zip of the HTML documentation is in the base directory. When we were making the Alpha tarball, we had a number of problems crop up in the scripts that make the release. There’s a lot of moving parts in that process, in particular with making the release summaries and pulling the documentation from Confluence. We had some connectivity issues going back from the release build virtual machine to the issue tracker, such that the connection would drop - or have some random fit at least - while it was trying to obtain information about Asterisk issues. After the fourth or fifth exception thrown by the script (various socket errors, HTTP timeouts, and other random badness), we had managed to pull all of the data but - for various reasons - not in the correct locations in the directory that was destined to become the tarball. So the location of the zip file is a goof on my part - I had to move it manually, and accidentally moved it into the wrong location. All of this went to show that it’s a well known fact that whatever can go wrong in your systems will go wrong just as you’re trying to push something live, particularly if you’re supposed to meet with your colleagues for a celebratory beer in five minutes. Every. Freaking. Time.

Anyway, let’s see how far we get running configure:

1
2
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ ./configure
configure: error: *** termcap support not found (on modern systems, this typically means the ncurses development package is missing)

That’s not terribly surprising. In fact, raspberry-asterisk.org has a good list of dependencies you’ll need if your’e installing Asterisk from source. As it is, I don’t really want all of the libraries listed in the FAQ. For example, I know I won’t be integrating Asterisk with MySQL, as I am - for the time - eschewing any Realtime configuration. But most of the rest are needed, and, as the Raspberry Pi is slow, it’s good to think things through and get things right the first time.

Step 2: Install Asterisk Dependencies

Let’s get what we do know:

1
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ apt-get install build-essential libsqlite3-dev libxml2-dev libncurses5-dev libncursesw5-dev libiksemel-dev libssl-dev libeditline-dev libedit-dev curl libcurl4-gnutls-dev

Since this is Asterisk 12, however, I’ll want a few other things as well. The CHANGES and UPGRADE.txt file tell you the additional dependencies - let’s got those as well:

1
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ apt-get install libjansson4 libjansson-dev libuuid1 uuid-dev libxslt1-dev liburiparser-dev liburiparser1

We could, at this point, configure and build Asterisk - but there’s one more thing I want. Since this is Asterisk 12, we have the brand new SIP stack to try out, and I’m definitely going to be using it over the legacy chan_sip channel driver. But, to get it working, we’ll need PJSIP.

Step 3: Get PJSIP

As of today (and I’m hopeful this isn’t a permanent situation), Asterisk needs a particular flavor of pjproject (I’m going to use the terms pjproject/PJSIP interchangeably here. There is a difference, but let’s just pretend there isn’t for the purposes of this blog post). There’s instructions on the wiki for how to obtain, configure, and install pjproject for use with Asterisk:

https://wiki.asterisk.org/wiki/display/AST/Installing+pjproject

Following along with the instructions, we first need git. Let’s get that:

1
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ apt-get install git

And then we can clone pjproject from our repo on github:

1
2
pi@mjordan-pi $ git clone https://github.com/asterisk/pjproject pjproject
pi@mjordan-pi $ cd pjproject

Now here’s the tricky part. What parameters should we pass to the configure script?

At a minimum, from the wiki article we know we need to tell it to install in /usr, and to enable shared objects with –enable-shared. But pjproject embeds a lot of third party libraries, which will conflict if we want to use them in Asterisk (or at least, will generally not play nicely). It is very important to know what you have on your system what installing pjproject, and what you want to use in Asterisk.

In my case, I don’t have libsrtp installed and I’m not going to use SRTP in Asterisk, so I can ignore that. I also don’t have libspeex installed, and I don’t really care about using libspeex in Asterisk either. The same goes for the GSM library. So, in the end I ended up with the following:

1
pi@mjordan-pi ~/pjproject $ ./configure --prefix=/usr --enable-shared --disable-sound --disable-video --disable-resample

After a little bit, we get the configuration success message:

1
2
3
4
5
Configurations for current target have been written to 'build.mak', and 'os-auto.mak' in various build directories, and pjlib/include/pj/compat/os_auto.h.
Further customizations can be put in:
- 'user.mak'
- 'pjlib/include/pj/config_site.h'
The next step now is to run 'make dep' and 'make'.

I went ahead and skipped ‘make dep’, since we don’t really need to do that step. Thus… the fateful command was typed:

1
pi@mjordan-pi ~/pjproject $ make

This took a long time.

1
2
3
4
5
if test ! -d ../bin/samples/armv6l-unknown-linux-gnu; then mkdir -p ../bin/samples/armv6l-unknown-linux-gnu; fi
gcc -o ../bin/samples/armv6l-unknown-linux-gnu/vid_streamutil \
output/sample-armv6l-unknown-linux-gnu/vid_streamutil.o -L/home/pi/pjproject/pjlib/lib -L/home/pi/pjproject/pjlib-util/lib -L/home/pi/pjproject/pjnath/lib -L/home/pi/pjproject/pjmedia/lib -L/home/pi/pjproject/pjsip/lib -L/home/pi/pjproject/third_party/lib -lpjsua -lpjsip-ua -lpjsip-simple -lpjsip -lpjmedia-codec -lpjmedia -lpjmedia-videodev -lpjmedia-audiodev -lpjnath -lpjlib-util -lmilenage -lsrtp -lgsmcodec -lspeex -lilbccodec -lg7221codec -lpj -lm -luuid -lm -lnsl -lrt -lpthread -lcrypto -lssl
make[2]: Leaving directory `/home/pi/pjproject/pjsip-apps/build'
make[1]: Leaving directory `/home/pi/pjproject/pjsip-apps/build'

Huzzah! Let’s get this bad boy installed.

1
pi@mjordan-pi ~/pjproject $ sudo make install

And after a bit:

1
2
3
4
5
6
7
8
9
10
for d in pjlib pjlib-util pjnath pjmedia pjsip; do \
cp -RLf $d/include/* /usr/include/; \
done
mkdir -p /usr/lib/pkgconfig
sed -e "s!@PREFIX@!/usr!" libpjproject.pc.in | \
sed -e "s!@INCLUDEDIR@!/usr/include!" | \
sed -e "s!@LIBDIR@!/usr/lib!" | \
sed -e "s/@PJ_VERSION@/2.1.0/" | \
` sed -e "s!@PJ_LDLIBS@!-lpjsua -lpjsip-ua -lpjsip-simple -lpjsip -lpjmedia-codec -lpjmedia -lpjmedia-videodev -lpjmedia-audiodev -lpjnath -lpjlib-util -lmilenage -lsrtp -lgsmcodec -lspeex -lilbccodec -lg7221codec -lpj -lm -luuid -lm -lnsl -lrt -lpthread -lcrypto -lssl!" | \
sed -e "s!@PJ_INSTALL_CFLAGS@!-I/usr/include -DPJ_AUTOCONF=1 -O2 -DPJ_IS_BIG_ENDIAN=0 -DPJ_IS_LITTLE_ENDIAN=1 -fPIC!" > //usr/lib/pkgconfig/libpjproject.pc

Let’s verify we got it.

1
2
3
pi@mjordan-pi ~/pjproject $ pkg-config --list-all | grep pjproject
libpjproject libpjproject - Multimedia communication library
pi@mjordan-pi ~/pjproject $

Yay! As an aside, Asterisk uses pkg-config to locate libpjproject - so if this step fails, something has gone horribly wrong and Asterisk is not going to find libpjproject either. So this is always a useful step to perform, regardless of the platform you’re installing Asterisk on.

Step 4: Build and Install Asterisk

Back to Asterisk!

1
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ ./configure --with-pjproject

I specified --with-pjproject, because I really wanted to know if it failed. It takes a bit to compile, and this way the configure script will tell me. Otherwise, the first time I’ll find out whether or not I have the dependencies for the new SIP stack is when I fire up menuselect.

1
2
3
4
5
6
7
8
configure: Menuselect build configuration successfully completed
$$$$$$$$$$$$$$$$.
configure: Package configured for:
configure: OS type : linux-gnueabihf
configure: Host CPU : armv6l
configure: build-cpu:vendor:os: armv6l : unknown : linux-gnueabihf :
configure: host-cpu:vendor:os: armv6l : unknown : linux-gnueabihf :
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $

And now for some configuration via menuselect.

1
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ make menuselect

I’m going to be a bit conservative here. There’s a lot of things I just won’t need for this Asterisk install. Rather than build everything and exclude them all via modules.conf, I’m going to disable them here. That way my overall installation will just be smaller (space is a premium on a pi), and I’ll be less likely to leave something lying around in Asterisk that doesn’t need to be there.

The following are what I disabled in menuselect:

  • Compiler flags:
    • DONT_OPTIMIZE - this may seem odd, but since I’m running an alpha of Asterisk 12, if I have a problem, I want to be able to fix it. This will make backtraces a bit more usable.
  • Applications:
    • app_agent_pool - I’m not going to be using Queues or Agents.
    • app_authenticate - I’m not going to need to Authenticate people myself.
    • app_gelgenuserevent - I’m not using CEL.
    • app_forkcdr - I hate this application, but that’s probably because I had to rewrite the CDR engine in Asterisk 12. While there’s one or two valid reasons to fork a CDR, most of what ForkCDR has traditionally done is silly.
    • app_macro - Use GoSubs everyone!
    • app_milliwatt - I’ll use something else to annoy people.
    • app_page - I don’t see myself setting up a home paging system right now. I’m only planning on connecting one or two SIP phones.
    • app_privacy - Nope!
    • app_queue - If I need a tech support queue for my house, I’ve got bigger problems.
    • app_sendtext - Nope.
    • app_speech_utils - Maybe someday, but probably not. I’d rather play around with calendaring or conferencing than speech manipulation of any kind.
    • app_system - Until I have a use for it, I view this as one big security vulnerability.
    • Everything in extended, except for app_zapateller. It may be fun to try and zap a telemarketer.
  • CDR:
    • Everything but cdr_custom. I’ll probably end up logging call records using this, since we don’t tend to have more than a handful of calls a day.
  • CEL:
    • Disable everything.
  • Channel Drivers:
    • chan_iax2 - I’m not going to set up an IAX2 trunk, and all my phones are SIP.
    • chan_multicast_rtp - Since I’m not doing any paging, I don’t need this channel driver.
    • chan_sip - I’m committing to chan_pjsip!
    • I left chan_motif to play around with some day. This may be useful for some soft phones or XMPP text message integration.
    • Everything in the extended/deprecated sections was disabled.
  • Codecs:
    • I left all of the codecs enabled.
  • Formats:
    • format_jpeg - I’m not going to do any fax at this point.
    • format_vox - I shouldn’t need this either.
  • Functions:
    I decided to leave most functions. Most function modules are rather small and don’t take up much space, and I find that you never know when you’re going to need one.
    • func_frame_trace - This is only ever used for development (and isn’t used often then)
    • func_pitchshift - Funny, but not super useful.
    • func_sysinfo - Interesting, but I shouldn’t need it.
    • func_env - I shouldn’t need to muck around with environment variables from the dialplan.
  • PBX:
    • pbx_ael - Classic dialplan all they way for me.
    • pbx_dundi - I don’t think I’ll be needing anything as complex as DUNDi for my little Pi.
    • pbx_realtime - Definitely not. I’m not a fan of realtime dialplan, for a variety of reasons.
  • Resources:
    I’m going to leave a number of resources that I may end up using someday, such as res_calendar. The ones I know for sure I won’t use I’ll disable.
    • res_adsi - Nope. I’m not sure you can even find ADSI devices still.
    • res_config_curl - Nope, not doing Realtime. I’ll come back and enable it if I decide to do Realtime some day.
    • res_config_sqlite3 - Nope, for the same reason as res_config_curl.
    • res_fax - Ew, fax. Not going to mess with fax at home.
    • res_pjsip_info_dtmf - I shouldn’t need DTMF over INFO.
    • res_pjsip_endpoint_identifier_anonymous - I don’t want anonymous access.
    • res_pjsip_one_touch_record_info - The SIP devices I’m using won’t need this.
    • res_pjsip_t38 - Fax: just say no.
    • res_rtp_multicast - Since I’m not doing any paging, I don’t need res_rtp_multicast.
    • res_smdi - Same reason as res_adsi.
    • res_speech - Nope, for the same reason as the speech utilities.
    • Everything in extended, except: res_http_websocket. That should be fun to play with at some point, and ARI needs it.
  • MoH/Sounds:
    • I enabled g.722 sounds/MoH. Wideband audio!

Phew. Time to save changes and finally compile.

1
2
3
4
5
6
7
8
9
10
11
12
menuselect changes saved!
make[1]: Leaving directory `/home/pi/asterisk-12.0.0-alpha1'
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ make
...
Building Documentation For: channels pbx apps codecs formats cdr cel bridges funcs tests main res addons
+--------- Asterisk Build Complete ---------+
+ Asterisk has successfully been built, and +
+ can be installed by running: +
+ +
+ make install +
+-------------------------------------------+
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $

Woot! We’re compiled. Time to install:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ sudo make install

+---- Asterisk Installation Complete -------+
+ +
+ YOU MUST READ THE SECURITY DOCUMENT +
+ +
+ Asterisk has successfully been installed. +
+ If you would like to install the sample +
+ configuration files (overwriting any +
+ existing config files), run: +
+ +
+ make samples +
+ +
+----------------- or ---------------------+
+ +
+ You can go ahead and install the asterisk +
+ program documentation now or later run: +
+ +
+ make progdocs +
+ +
+ **Note** This requires that you have +
+ doxygen installed on your local system +
+-------------------------------------------+
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $

And we’re installed!

I’m going to set up the bare minimum Asterisk configuration files to get Asterisk up and running. I’m not going to do anything more than get the CLI prompt up for now - I’ll save getting a phone configured and howler monkeys playing back for another time.

Step 5: Configure Asterisk

asterisk.conf

1
2
3
4
5
6
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ sudo nano /etc/asterisk/asterisk.conf

[options]
verbose = 5
debug = 0
systemname = mjordan-pi

As you can see, a pretty simple asterisk.conf. Not much else is needed for this, at least for now.

modules.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ sudo nano /etc/asterisk/modules.conf

[modules]
autoload = no

; -- Bridges --

load => bridge_builtin_features.so
load => bridge_builtin_interval_features.so
load => bridge_holding.so
load => bridge_native_rtp.so
load => bridge_simple.so
load => bridge_softmix.so

; -- Formats --

load => format_g719.so
load => format_g723.so
load => format_g726.so
load => format_g729.so
load => format_gsm.so
load => format_h263.so
load => format_h264.so
load => format_ilbc.so
load => format_pcm.so
load => format_siren14.so
load => format_siren7.so
load => format_sln.so
load => format_wav_gsm.so
load => format_wav.so

; -- Codecs --

load => codec_adpcm.so
load => codec_alaw.so
load => codec_a_mu.so
load => codec_g722.so
load => codec_g726.so
load => codec_gsm.so
load => codec_ilbc.so
load => codec_lpc10.so
load => codec_resample.so
load => codec_ulaw.so

; -- PBX --

load => pbx_config.so
load => pbx_loopback.so
load => pbx_spool.so

Rather than go with autoloading, I’ve chosen to explicitly load modules. For now, I’ve only specified the “basics” - codecs and formats, the bridge modules (now used a lot in Asterisk 12), and the ancillary PBX modules. This will let me catch configuration errors easier - if you autoload, I find most people tend to ignore the warnings and errors.

extensions.conf

1
2
3
4
5
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ sudo nano /etc/asterisk/extensions.conf

[general]

[default]

Nothing needed in there right now!

Step 6: Profit

And now, for the moment of truth:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pi@mjordan-pi ~/asterisk-12.0.0-alpha1 $ sudo asterisk -cvvvvg

...

Loading codec_adpcm.so.
== Registered translator 'adpcmtolin' from format adpcm to slin, table cost, 900000, computational cost 1
== Registered translator 'lintoadpcm' from format slin to adpcm, table cost, 600000, computational cost 1
codec_adpcm.so => (Adaptive Differential PCM Coder/Decoder)
Loading codec_g726.so.
== Registered translator 'g726tolin' from format g726 to slin, table cost, 900000, computational cost 60000
== Registered translator 'lintog726' from format slin to g726, table cost, 600000, computational cost 120000
== Registered translator 'g726aal2tolin' from format g726aal2 to slin, table cost, 900000, computational cost 60000
== Registered translator 'lintog726aal2' from format slin to g726aal2, table cost, 600000, computational cost 120000
codec_g726.so => (ITU G.726-32kbps G726 Transcoder)
Asterisk Ready.
*CLI>

Woohoo!

Last Friday (August 30th), we released Asterisk 12.0.0-alpha1.

This was an incredibly ambitious project. We started work on it directly after AstriDevCon last year (October 2012), so we’ve been hard at work on it for about ten months. Really, I would say that it hasn’t even really been that long - the project probably didn’t take its full shape until January of this year. So, in reality, over only the past eight months we’ve:

  • Completely re-architected bridging in Asterisk. Every phone call you make through Asterisk - where you actually talk to someone else - is probably going through the new Bridging API. The old bridging code was easily some of the most complex code in Asterisk as well - and while the new API is certainly very complex, it actually provides an abstraction of the bridging operations that the old code (which wasn’t really didn’t provide any abstraction, or an API) couldn’t ever do. Also, it has a completely different threading model that let us nearly completely annihilate masquerades.

  • Wrote a completely new SIP stack. The old SIP channel driver is 35000 lines of code. The new SIP stack and channel driver has nearly the same feature parity. That’s pretty crazy.

  • Wrote a new API - ARI (REST! JSON! WebSockets!) that exposes the communications objects in Asterisk to the outside world. You can, for really the first time, write your own communications applications in the language of your choosing, without having to mix and match AMI/AGI. You can also do things through ARI that you could never really do through AMI/AGI (asynchronous control of media, fine grained bridging operations, etc.)

And that doesn’t take into account the ancillary changes: re-writing all core bridging features, CDRs, CEL, every AMI event. It doesn’t take into account the infrastructure to make it all possible - the Stasis message bus, threadpools, and the Sorcery data access layer. And the list goes on.

How we got here wasn’t straight forward. At AstriDevCon last year, we took away only two objectives: write a new SIP channel driver, and stop exposing internal Asterisk implementation details through the APIs. We didn’t know we were going to have to gut Asterisk’s bridging code. We didn’t know we would end up having to re-write Parking, all Transfers (core and externally initiated), CDRs or CEL. We did not know we would need a more flexible publish/subscribe message bus in Asterisk, one that would end up obsoleting the existing event subsystem.

The moral of the story is: when you start a project, you have to have a goal. But the path to that goal is not a straight line. I’ll have to think for awhile how that line curved to where we find ourselves today.

I won’t say I’m not a little proud of this release. This was an incredibly ambitious project, and the team at Digium really rocked it out. We aren’t a very large team either - besides myself, there are only seven other developers. For us to get this done, with shipped code, is easily the greatest success of my professional career. That isn’t to say there isn’t a ton left to do: it is, after all, only in an alpha state. But I’m damned proud of what we’ve accomplished so far.

1
Weightless Theme
Rocking Basscss