Configuring Kamailio with rtpengine to relay RTP traffic behind NAT

In a test environment, so in a fully controlled environment I would say, usually all the elements involved in a VoIP call are on the same subnet and this means that the RTP media streams flow directly between the caller and the called party. However, this never happens in the real world, where instead the scenario is much more complex and both SIP signaling messages and media streams must traverse firewalls and NATs.
In general we can think of a scenario like the one shown in the following figure, where the caller is outside our infrastructure and the called party is on a local network behind a SIP Proxy, which is behind a firewall.

Real SIP Call scenario

Furthermore, the SIP Proxy could have a single network interface located on the LAN, or it could be configured in multihomed with a public IP exposed on the Internet and a private IP on the LAN. In both cases, after the appropriate configurations to be applied to the firewall, we will be able to take advantage of the features of Kamailio configured with rtpengine.

Configuration

Before proceeding with the configuration of Kamailio you need to:

  1. rtpengine installed and configured on the server (all the details here).
  2. Apply the necessary configurations to the firewall (if present) to allow traffic from the Internet to Kamailio and vice versa.

In our case we will configure Kamailio to receive an incoming call from the Internet and route it to a server within the local network. In this scenario Kamailio will handle two call legs, one between the caller and the public interface and one between the private interface and the callee, for both SIP traffic and RTP media stream.

To activate the management of the media stream through rtpengine you need to load the rtpengine.so module with the relative configuration parameters (https://www.kamailio.org/docs/modules/5.5.x/modules/rtpengine.html).

	#!ifdef WITH_NAT
	loadmodule "nathelper.so"
	#!ifdef WITH_RTPENGINE
	loadmodule "rtpengine.so"
	#!else
	loadmodule "rtpproxy.so"
	#!endif
	#!endif
	...
	...
	#!ifdef WITH_NAT
	#!ifdef WITH_RTPENGINE
	# ----- rtpengine params -----
	modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:2223")
	#!else
	# ----- rtpproxy params -----
	modparam("rtpproxy", "rtpproxy_sock", "udp:127.0.0.1:7722")
	#!endif

We have to enable nathelper.so and rtpengine.so modules via define at the top of the configuration file (the define directive allows you to control in C-style which parts of the configuration must be executed ).

	#!KAMAILIO
	#
	# Global define to activate specific features
	#
	// Enable Debug
	##!define WITH_DEBUG
	 // Enable NAT
	#!define WITH_NAT
	// Enable RTPENGINE
	#!define WITH_RTPENGINE
	...

Restart Kamailio and if everything is configured correctly, executing the command kamcmd rtpengine.show all we will have an output like this:

	{
		 url: udp:127.0.0.1:2223
		 set: 0
		 index: 0
		 weight: 1
		 disabled: 0
		 recheck_ticks: 0
	}

Media management with rtpengine

As mentioned above, in this scenario Kamailio will not act as a simple SIP Proxy, but will be a real RTP forwarder between the caller and the called party. To do this it will rewrite the SDP by adding its IP (public or private depending on the call leg) in the connection information. What we have to do is simply activate the processing with rtpengine of the INVITE received and the 200OK sent, using the rtpengine_manage() function.

The configuration in details

This is my configuration for rtpengine (999.999.999.999 is the public IP):

	OPTIONS="--interface=pub/192.168.0.42!999.999.999.999 --interface=priv/192.168.0.42 -n 127.0.0.1:2223 -m 23000 -M 23100 -L 6 --log-facility=local1 --table=8 --delete-delay=0 --timeout=60 --silent-timeout=600 --final-timeout=7200 –offer-timeout=60 --num-threads=12 --tos=184 –no-fallback"

Kamailio rewrites the SDP of the INVITE in the route[RELAY], also being able to distinguish between the inbound direction (interface pub -> priv) and the outbound direction (interface priv -> pub):

	route[RELAY] {
		...
		...
		if (has_body("application/sdp")) {
			if ($rd == "outbound.direction") {
				rtpengine_manage("direction=priv direction=pub");
			}
			else {
				rtpengine_manage("direction=pub direction=priv");
			}
		}
		...
		...

And rewrites the SDP inside the 200OK in the onreply_route[MANAGE_REPLY]:

	onreply_route[MANAGE_REPLY] {
		xdbg("incoming reply\n");
		if(status=~"[12][0-9][0-9]") {
			route(NATMANAGE);
		}

		if (has_body("application/sdp")) {
			rtpengine_manage();
		}
	}

Moreover, for a proper NAT handling, we configure Kamailio to listen on the private interface and tell it to advertise the public IP:

	listen=udp:PRIVATE_IP4_ADDR:SIP_PORT advertise PUBLICIP4_ADDR:SIP_PORT

where PRIVATE_IP4_ADDR:SIP_PORT is the private IP, and PUBLICIP4_ADDR:SIP_PORT is the public IP.

Kamailio with rtpengine at work

In my case, 192.168.0.42 is the server with Kamailio and rtpengine while 10.100.163.4 is the called party, and two bidirectional RTP media streams are active, terminated by Kamailio who is relaying them.

kamailio-rtpengine

Going further into detail, we can see how Kamailio (rtpengine) rewrites the SDP inside the INVITE and 200OK. In the following images you can see respectively the INVITE received with public IP on the connection information and the invite forwarded internally with the local IP.

kamailio-rtpengine

kamailio-rtpengine

The same happens with the response 200OK, the incoming message from the called contains the local IP, while the outgoing message to the caller contains the public IP of Kamailio.

kamailio-rtpengine

kamailio-rtpengine