Asterisk as a transcoder for Kamailio

Using Asterisk as a SBC or transcoder may not be the right choice, especially if you follow the saying “use the right tool for the job”, and Asterisk is not precisely the right tool on these cases.

When you have options like FreeSwitch and SEMS, Asterisk seems to be disproportional and awkward to use, but it is so widely known that sacrificing flexibility to avoid investing time in learning a new technology (in case you don’t know FS or SEMS already) seems like a good alternative.

Having said that, let’s assume the following rules for our setup:

  • Our local network only accepts G722 codec between users, because, we enforce HD quality calls and we have lots of bandwidth at our disposal.
  •  We offer a local numbering plan and we can call between users of the network.
  • We also offer external calls to PSTN. For this purpose, we have a gateway that supports only PCMA/PCMU and G729. We don’t have the licence to do G729 in all of our softphones reason why we use PCM to reach our gateway from the softphones.
  • Asterisk will act as a transcoder, translating from G722 to PCMA/PCMU and backwards.
  • Asterisk will only take part of the SIP conversation when Kamailio detects that we are dialing to a number that does not belong to our internal numbering plan.

And from the SIP perspective

  • Kamailio is listening on port 5075 and serving on the net 192.168.2.0/24, using the IP 192.168.2.97.
  • Asterisk is listening on port 5080.
  • The PSTN gateway is located at 192.168.2.20.
  • Kamailio is accepting every registration request without any kind of authentication.
  • Username format is not being enforced, so I would recommend that you use something similar to 1000, 1100, etc.
  • I’m from Paraguay, and locally we dial “+5959” to access the mobile network. Change this pattern to your dialing prefix so that it makes sense to your gateway.

Basically, the magic on the Kamailio side happens approximately here:

kamailio.cfg

	
request_route {
# per request initial checks
	route(REQINIT);

	# NAT detection
	route(NATDETECT);

	# CANCEL processing
	if (is_method("CANCEL")) {
		if (t_check_trans()) {
			route(RELAY);
		}
		exit;
	}

	# handle requests within SIP dialogs
	route(WITHINDLG);

	### only initial requests (no To tag)

	t_check_trans();

	if (!is_method("REGISTER|INVITE|ACK|BYE|CANCEL|PRACK")) {
		sl_send_reply("405", "Method not allowed");
                exit;
	}

	# authentication
	route(AUTH);

	# record routing for dialog forming requests (in case they are routed)
	# - remove preloaded route headers
	remove_hf("Route");
	if (is_method("INVITE|SUBSCRIBE"))
		record_route();

	# handle registrations
	route(REGISTRAR);

	if ($rU==$null) {
		# request with no Username in RURI
		sl_send_reply("484","Address Incomplete");
		exit;
	}

	if (!is_method("INVITE")) {
		route(RELAY);
		exit;
	}

	if ($rU =~ "^5959") {
		if (route(IS_FROM_SBC))
			route(FROM_SBC);
		else
			route(TO_SBC);

		exit;
	}

	route(ENFORCE_CODEC_POLICY);
	route(LOCATION);
}

route[ENFORCE_CODEC_POLICY] {
	# enforce g722 codec in all calls
	if (!sdp_with_codecs_by_name("G722")) {
		sl_send_reply("488", "Use G722!");
		exit;
	}

	# make sure g722 is the only code offered
	sdp_keep_codecs_by_name("G722");
}

route[IS_FROM_SBC] {
	if ($si =~ SBC_IP && $sp == SBC_PORT) 
		return 1;
	else
		return 0;
}

route[TO_SBC] {

	xlog("L_INFO", "Going to SBC");

	# set the destination URI to our SBC
	$du = "sip:" + SBC_IP + ":" + SBC_PORT; 

	# We store the original request URI.
	append_hf("X-RURI: $ru\r\n");

	route(RELAY);
}

route[FROM_SBC] {

	# At this point, the SDP offer should be fixed with the right codec information
	xlog("L_INFO", "Coming from SBC");

	if (is_present_hf("X-RURI")) {
		$ru = $hdr(X-RURI);
		xlog("L_INFO", "New URI is [$ru]");
	}
	else {
		xlog("Weird, I couldn't find X-RURI hdr");
	}

	rewritehost(PSTN_GW_IP);
	rewriteport(PSTN_GW_PORT);

	route(RELAY);
}

And, on the Asterisk side we have got two things to modify:

  1. sip.conf: we have to add our Kamailio instance as a trusted peer, with no authentication and with the right codec definition
  2. extensions.ael: yes, I use AEL instead of the common extensions.conf, mostly because I hate extensions.conf syntax. If you chose to use .conf, no problem. It should work using whatever you choose.

sip.conf

[general]
context=public                  ; Default context for incoming calls. Defaults to 'default'

bindaddr=0.0.0.0
bindport=5080

[kam_transcoder]
type=friend
host=192.168.2.97
port=5075
disallow=all
allow=g722
allow=ulaw
allow=alaw
context=transcoder
canreinvite=no
insecure=invite

extensions.ael

context transcoder {

        _.      => {

                Set(SIP_CODEC=alaw);
                Set(RURI=${SIP_HEADER(X-RURI)});

                NoOp(${RURI});
                if ("${EXTEN}" != "h") {
                        SipAddHeader(X-RURI: ${RURI});
                        Dial(SIP/${EXTEN}@192.168.2.97:5075,40,rt);
                }
        }
}

The full list of files are available on my github, here.

Installing mediaproxy-ng

This is a small guide on how to install mediaproxy-ng from sources on RPM based systems like CentOS.

Naming convention

All these “ng” stuff are really confusing and sometimes it’s hard to distinguish the pieces among them.

  • rtpproxy: it’s a media relaying software for RTP packets. It runs as a service and needs to be controlled and instructed using it’s own protocol.
  • rtpproxy module: it’s the Kamailio module that controls the rtpproxy service, from the configuration scripts.
  • rtpproxy-ng module: it’s the next generation (ng) module for Kamailio, based on the rtpproxy module. It serves as a replacement for that old module on newer systems.
  • mediaproxy: it’s another media relaying software, similar to rtpproxy, but older.
  • mediaproxy-ng: despite of its name, it is not the next-generation mediaproxy, but instead, the next generation of the rtpproxy (the service, not the module). From Kamailio, it can be controlled from both the rtpproxy and the rtpproxy-ng modules.

Before you begin: I’m using a x86 virtual machine. This means that you have to change the package names from i686 to x86_64 when necessary.

1. Kernel upgrade: you may not need to do this, so I recommend skipping this step unless you end up with an error like “undefined symbol” when trying to load the kernel module in the last step. In my particular case, I had to do it with my CentOS 6.0 VM.

yum upgrade kernel -y && reboot

2. Install development tools: several tools are needed to perform this step. Instead of manually listing them one by one, I would rather save some typing time by just installing the whole package 😉

yum groupinstall 'Development Tools'

3. Download the sources: from github

git clone https://github.com/sipwise/mediaproxy-ng.git

4. Install kernel development headers: this is going to be a kernel module, we need them.

yum install kernel-devel-$(uname -r)

5. A few other dependencies

yum install glib2-devel.i686 pcre-devel.i686 libcurl-devel.i686 libcurl.i686 xmlrpc-c.i686 zlib-devel.i686 iptables-devel.i686

6. Install mediaproxy-ng

cd /usr/src/mediaproxy-ng/daemon
make

7. Copy the resulting binary to the PATH: the Makefile doesn’t provide a rule for installing the binary.

cp mediaproxy-ng /usr/bin/

8. Compile and install the iptables extension

cd ../iptables-extension
make
cp libxt_MEDIAPROXY.so /lib/xtables/

9. Compile and install the kernel module

cd ../kernel-module/
make
cp xt_MEDIAPROXY.ko /lib/modules/$(uname -r)/updates/
depmod -a
modprobe xt_MEDIAPROXY

10. Finally, if everything went fine, you should be able to see something like this:

# lsmod | grep -i mediaproxy
xt_MEDIAPROXY 16512 0
ipv6 264702 39 xt_MEDIAPROXY,ip6t_REJECT,nf_conntrack_ipv6