Using SIP proxy server, SIPD , build an application level gateway (ALG) that can work with network address translation (NAT) and firewalls on Linux.
Consider the above diagram on how NAT works. Network address translation (RFC 1631) is a technique for translating one set of IP addresses known in one network to another set of IP addresses known in another. Typically, an organization maps its local inside private addresses to one or more global external public IP addresses. There are pre-defined private IP address spaces, e.g., 10.0.0.0 to 10.255.255.255 is one such range. These IP addresses do not have any global routing significance in the public internet. The source IP address in the outgoing IP packet is translated from private to public, and the destination IP address in the incoming packets from public to private. NAT conserves the global IP address space by prividing independent islands of private IP address networks. Above figure shows an example NAT processing. Usually the mapping is established when a new session (e.g., a TCP connection or a UDP packet) is established from a node in the the private network to a node in the public network. The mapping exists as long as the session is active.
Network address and port translation (NAPT) allows use of the same external public IP address for more than one internal private nodes by using TCP/UDP port number for multiplexing multiple sessions. We use NAPT in our architecture. As shown in above figure, when host A, with private address 10.0.1.23, sends a TCP SYN connection establishment packet to an external node B with public address 128.59.16.149, the packet is intercepted by the NAT router (10.0.0.1). This NAT box with external IP 135.180.132.24, creates a mapping from 10.0.1.23 port 1987 to its external IP 135.180.132.24 and port 1734. The packet is forwarded to node B, as if it was originated from the NAT box, by changing the source IP and port to 135.180.132.24 and 1734 respectively. NAT intercepts incoming packets, and changes the destination to 10.0.1.23 at port 1987. Node A thinks that it is connected to node B's IP, whereas node B thinks that it is connected to NAT's IP.
NAT devices break the application level protocols like RTSP, FTP and SIP that use the host and port information in the application payload for signaling. For example, the RTSP client in private address space may ask the server in public address space to stream the video to 10.0.1.23. However, this IP address is not visible for the public address node B. This is achieved using application level gateways on the NAT box, that can modify the application specific signaling messages for FTP or RTSP.
Compile sipd and test with the CGI script. Modify sipd to support global CGI scripts for all usersOriginally, SIP has allowed each user can upload their own script on their profile and then the server will invoke the scripts once the messages come. For building an application level gateway, we need to have a global script at proxy server so that the script will parse the media (audio) IP address and port from the SIP INVITE request and 200 OK response messages, change the IP address and port number in the message before forwarding.
This global script should be create at a default user, where server always look at whever a message coming. The script was written in perl and splited into three parts, depended on what kind of message coming. The link to this SCRIPT file is here
if(defined $ENV{REQUEST_METHOD}){
if($ENV{REQUEST_METHOD} =~ /INVITE/i){
Then parsing though the SDP message, modifying the IP and port number field before proxying the invite request
while($line=){ #if the line start with letter "c" if(lc(substr($line,0,1)) eq lc("c")){ # Change the remote IP address to local IP address # ... } #if the line start with letter "m" elsif(lc(substr($line,0,1)) eq lc("m")){ # Change the remote port number to the local port number # ... } else{ # just copy what SDP has }
The next thing to do is proxying the message to callee:
#if proxy server knows registered IP addresses of this user
#proxying request to all IP addresses
if (defined($ENV{REGISTRATIONS})) {
foreach $reg (get_regs()) {
#proxy the request to the destination
print "CGI-PROXY-REQUEST $reg SIP/2.0\n";
# define the request_token token to match with response messge later
print "CGI-Request-Token : request_token\n";
# Sending modified content message
print_message @message;
# Call continue_transaction() function to do CGI-AGAIN message
continue_transaction()
}
}
#If Proxy server does not know IP address of this user
#Proxying request using user ID
if(defined $ENV{SIP_TO}){
print "CGI-PROXY-REQUEST $ENV{SIP_TO} SIP/2.0\n";
print "CGI-Request-Token : request_token\n";
# Sending modified content message
print_message @message;
continue_transaction();
}
if(defined $ENV{RESPONSE_STATUS} && $ENV{RESPONSE_STATUS} == 200){
#if($ENV{RESPONSE_STATUS} == 200){
Then do the same thing to modify IP/port from SDP message before forward the response. The difference is that we have to check for the coresponding request_token.
if($ENV{REQUEST_TOKEN} eq "request_token"){
print "CGI-FORWARD-RESPONSE $ENV{RESPONSE_TOKEN} SIP/2.0\n";
print "Content-Type: application/sdp\n";
print "Content-Length: $len\n\n";
print_message @message;
continue_transaction();
}
After having all request/reponse IP/port info, create a child process, and invoke Forwarder program.
#Fork the child process and save the process pid
$pid = fork;
#Invoke UDP based Forwarder application in java
@RTP = ("/usr/java1.4/bin/java", "Forwarder", $localPort, $requestIP, $requestPort, $responseIP, $responsePort, "&");
system(@RTP) == 0 or die "system@RTP failed: $?";
elsif(defined $ENV{REQUEST_METHOD}){
if($ENV{REQUEST_METHOD} =~ /INVITE/i){
Then kill the child process created from response part above
kill $pid;
The next step is modifying the CGI script handling in sipd such that it can allow global CGI scripts for all users, instead of having per-user CGI scripts. This was done by creating a Default user, and adding in DoUnique() function at method.c file to check for script of user Default if that is present then execute that instead of checking for per user script. The method.c file is here. The fragment code for doing so as below:
#ifdef IMPLEMENT_SIPALG
if (register_find("default@cs.columbia.edu", reg) &&
HasExecutableScript(®->scriptset) &&
GetScriptByDisposition(®->scriptset,
disposition_sip_cgi) != NULL) {
debug(DEBUG_MISC, "DoUnique",
"Request 0x%p: status=%d, default_user.scriptset.count=%d, "
"reg->contacts=%ld\n",
r, *status, reg->scriptset.count, (long)reg->contacts);
r->transaction_policy = &incoming_policy;
r->user_policy = &cgi_policy;
}
else
#endif
At this point sipd was able to recieve a SIP message and modify the message body (SDP) content before proxying the message.
Modify the SIP messages to incorporate the application level gateway functions. This includes changing the IP and port in the SIP messages in both directions.
Enhance the previous part such that the CGI script invokes a packet forwarder application to forward RTP/RTCP packets. It was done by extend the global script from part 2 in using system() function to call Forwarder object in Java
The packet forwarder is a simple UDP based application that forwards packets from one port to another. This is done for both RTP and RTCP packets. The input to the application is the local address, the request IP/port and response IP/port to which the packets have to be forwarded. Here is the full implementaion of Forwarder.java
The first thing Forwarder does is opening local file port.table to check the next port available from 10000 (just in case having more than one phone at the same time), writing down the file the pick up ports at local host and create 4 Forward_helper objects to run 4 thread of UD programs. 2 for forwarding RTP and RTCP to the caller and 2 for forwarding RTP and RTCP to the callee.
/* Open 4 threads of Forward_helper, 2 for each side (RTP+RTCP) */ Forward_helper requestRTP = new Forward_helper(listen_rtp_req_p, request_addr, rtp_req_p); Forward_helper requestRTCP = new Forward_helper(listen_rtcp_req_p, request_addr, rtcp_req_p); Forward_helper responseRTP = new Forward_helper(listen_rtp_rep_p, response_addr, rtp_rep_p); Forward_helper responseRTCP = new Forward_helper(listen_rtcp_rep_p, response_addr, rtcp_rep_p); requestRTP.start(); requestRTCP.start(); responseRTP.start(); responseRTCP.start();
public void run(){ byte[] sendData = new byte[1024]; byte[] receiveData = new byte[1024]; try{ DatagramSocket forward = new DatagramSocket(); DatagramSocket listen = new DatagramSocket(listen_port); while(true){ DatagramPacket revPacket = new DatagramPacket(receiveData,receiveData.length); listen.receive(revPacket); sendData = revPacket.getData(); DatagramPacket sendPacket = new DatagramPacket(sendData,sendData.length,forward_addr,forward_port); forward.send(sendPacket); } } catch (IOException e){ System.err.println(e); //rely on e's toString() System.err.println("???"); } } }
At the end of this milestone, the program was able to correctly allow audio communication between an internal node and and external node using a SIP user agent like sipua or sipc. Please click here for viewing screen dump from testing.
Configure a Linux router (with two interfaces cards) as a NAT box. Allocate private addresses in the range 10.x.x.x. Use the Linux netfilter/iptables modules for NAT and filter functions.
The machine I have supposed to use for doing NAT box is metropolitano at IRT lab. Following are steps that I should do to configure the Linux router as a NAT device:
$ rpm -q iptables ipchains
$ chkconfig --list | grep ipchains
The IP address of metropolitano.cs.columbia.edu is 128.59.19.13. The Linux NAT box must be configured for the private internal network and public network.
Use the ifconfig command to configure the private network. i.e. (as root) and public network.
$/sbin/ifconfig eth0 128.59.19.13 netmask 255.255.255.0 broadcast 128.59.19.255 - External network $/sbin/ifconfig eth1 10.0.0.01 netmask 255.255.255.0 broadcast 10.0.0.255 - Internal private network
$iptables --flush - Flush all the rules in filter and nat tables $iptables --table nat --flush $iptables --delete-chain - Delete all chains that are not in default filter and nat table $iptables --table nat --delete-chain
$iptables --table nat --append POSTROUTING --out-interface eth0 -j MASQUERADE $iptables --append FORWARD --in-interface eth1 -j ACCEPT
Use the following command to allow the Linux kernel to forward IP packets:
$ echo 1 > /proc/sys/net/ipv4/ip_forward
Another method is to alter the Linux kernel config file: /etc/sysctl.conf. Set the following value:
net.ipv4.ip_forward = 1
An alternate method is to alter the network script: /etc/sysconfig/network, Change the default "false" to "true".
FORWARD_IPV4=true
All methods will result in a proc file value of "1".
$ cat /proc/sys/net/ipv4/ip_forward $ 1
$ route add -net 10.0.0.1 netmask 255.255.255.0 gw 128.59.19.13 dev eth1
search cs.columbia.edu - Name of cs domain nameserver 128.59.19.13 - IP address of primary name server nameserver 128.59.19.13 - IP address of secondary name server
The above configuring in iptables can also be done with ipchains where eth0 is connected to the internet and eth1 is connected to a private LAN:
#!/bin/sh # Flush Rules ipchains -F forward - Flush rules ipchains -F output ipchains -F input # Set default to deny all ipchains -P input DENY ipchains -P output DENY ipchains -P forward DENY - Default set to deny packet forwarding # Add Rules # Accept packets from itself (localhost) (s)ource to itself (d)estination # Keeps system logging, X-Windows or any socket based service working. ipchains -A input -j ACCEPT -p all -s localhost -d localhost -i lo ipchains -A output -j ACCEPT -p all -s localhost -d localhost -i lo # Deny and log (option -l) spoofed packets from external network (eth0) which mimic internal IP addresses ipchains -A input -j REJECT -p all -s 10.0.0.1/24 -i eth0 -l # Accept requests/responses from/to your own firewall machine ipchains -A input -j ACCEPT -p all -d 128.59.19.13 -i eth0 ipchains -A output -j ACCEPT -p all -s 128.59.19.13 -i eth0 # Allow outgoing packets source (s) to destination (d) ipchains -A input -j ACCEPT -p all -s 10.0.0.1/24 -i eth1 ipchains -A output -j ACCEPT -p all -s 10.0.0.1/24 -i eth1 # Deny and log (option -l) outside packets from internet which claim to be from your loopback interface ipchains -A input -j REJECT -p all -s localhost -i eth0 -l ipchains -A forward -s 10.0.0.1/24 -j MASQ - Use IP address of gateway for private network ipchains -A forward -i eth1 -j MASQ - Sets up external internet connection # Enable packet forwarding echo 1 > /proc/sys/net/ipv4/ip_forward
Modify the script to also support firewall functionality
This is similar to the previous part except that we have to also invoke the iptables functions to create holes in the linux firewall. This was done by call iptables command from the script in part 2 by using system() call
#create a hole in the firewall
system "iptables -I INPUT -p udp --destination-port $localport -j ACCEPT";
#close up the hole in the firewall
system "iptables -D INPUT -p udp --destination-port $remoteport -j ACCEPT";