SIP Monitor

Cuando en un entorno de VoIP gestionas muchos usuarios es muy complicado seguir el log que muestra el CLI para ver exactamente dónde hay un error concreto. Muchas veces debes filtrar algún usuario con determinados problemas, por ejemplo de registro, y la verdad es que se hace bastante engorroso realizar un seguimiento de los paquetes. Por otro lado, también resulta un poco coñazo realizar capturas con Tcpdump para luego descargar en otro equipo con entorno gráfico y analizar posteriormente mediante Wireshark.

Para agilizar un poco este proceso he creado un script en Perl muy sencillo que permite aplicar algunos filtros de búsquedas, mostrando por pantalla únicamente aquellos paquetes que necesitemos.

sipmon

El script no es más que un sniffer que monitoriza el tráfico TCP y/o UDP en los puertos que especifiquemos y que nos mostrará los paquetes de señalización SIP que capture. Como se puede ver en la imagen, podemos realizar las capturas que más nos interesen, por ejemplo:

  • Analizar los paquetes con origen o destino hacia una determinada IP.
  • Buscar paquetes con un usuario de registro específico.
  • Monitorizar TCP y/o UDP.
  • Filtrar por método: INVITE, REGISTER, OPTIONS, etc.

Para poderlo usar necesitamos instalar algunas dependencias:

Net::PcapUtils -> http://search.cpan.org/CPAN/authors/id/T/TI/TIMPOTTER/Net-PcapUtils-0.01.tar.gz
Net::Pcap -> http://search.cpan.org/CPAN/authors/id/K/KC/KCARNUT/Net-Pcap-0.05.tar.gz – apt-get install libnet-pcap-perl
Net::SIP -> http://search.cpan.org/CPAN/authors/id/S/SU/SULLR/Net-SIP-0.68.tar.gz – apt-get install libnet-sip-perl
NetPacket::Ethernet -> http://search.cpan.org/CPAN/authors/id/A/AT/ATRAK/NetPacket-0.04.tar.gz
Net::Address::IP::Local -> http://search.cpan.org/CPAN/authors/id/J/JM/JMEHNLE/net-address-ip-local/Net-Address-IP-Local-0.1.2.tar.gz

Y el script:

#!/usr/bin/perl
# Jose Luis Verdeguer AKA Pepelux <verdeguer@zoonsuite.com>
# Twitter: @pepeluxx

use strict;
use Net::PcapUtils;
use Getopt::Long;
use Net::SIP ':all';
use NetPacket::Ethernet;
use NetPacket::IP;
use NetPacket::TCP;
use Net::Address::IP::Local;

# Do no buffering - flushing output directly
$|=1;

# Declaration of functions
sub f_probe_pcapinit;
sub f_probe_read80211b_func;
sub f_probe_ctrl_c;

# Declaration of global variables
my $g_pcap_err = '';
my $g_cap_descrip;
my ($address, $netmask, $err);
my $ip;
my $ether;
my $tcp;
my $myfilter;
my $myaddress;

# Params
my $dev = '';
my $sudp= 0;
my $stcp = 0;
my $port = '';
my $method = '';
my $user = '';
my $oip = '';
my $h = 0;

# Packet struct
my $len = 38;      # 0x26 -> srcport (2)
my $request = 42;  # 0x2a -> SIP request

sub init() {
	# check params
	my $result = GetOptions ("i=s" => \$dev,
				 "p=s" => \$port,
				 "m=s" => \$method,
				 "udp+" => \$sudp,
				 "tcp+" => \$stcp,
				 "user=s" => \$user,
				 "ip=s" => \$oip,
				 "help+" => \$h,
				 "h+" => \$h);

	help() if ($h eq 1);

	my $login = (getpwuid $>);
	die "must run as root" if $login ne 'root';

	$myaddress = Net::Address::IP::Local->public;

	my $proto = '';
	my $port1 = '';
	my $port2 = '';
	my $addr1 = '';
	my $addr2 = '';

	if ($sudp eq 0 && $stcp eq 0) {
		$sudp = 1;
		$stcp = 1;
		$proto = "(udp || tcp)";
		$port1 = "(dst port 5060 || dst port 5061)" if ($port eq "");
		$port2 = "(src port 5060 || src port 5061)" if ($port eq "");
	}
	else {
		if ($sudp eq 1) {
			$proto = "udp";
			$port1 = "dst port 5060" if ($port eq "");
			$port2 = "src port 5060" if ($port eq "");
		}
		else {
			$proto = "tcp";
			$port1 = "dst port 5061" if ($port eq "");
			$port2 = "src port 5061" if ($port eq "");
		}
	}

	if ($port ne "") {
		$port1 = "dst port $port";
		$port2 = "src port $port";
	}

	if ($oip eq "") {
		$addr1 = "dst $myaddress && $port1";
		$addr2 = "src $myaddress && $port2";
	}
	else {
		$addr1 = "(dst $myaddress && $port1 && src $oip)";
		$addr2 = "(src $myaddress && $port2 && dst $oip)";
	}

	$dev = Net::Pcap::lookupdev(\$err) if ($dev eq "");

	if (Net::Pcap::lookupnet($dev, \$address, \$netmask, \$err)) {
	    die 'Unable to look up device information for ', $dev, ' - ', $err;
	}

	$myfilter = "$proto && (($addr1) || ($addr2))";

	print "Interface: $dev ($myaddress)\n";
	print "Filter: $myfilter\n";

	if ($method eq "") { print "Method: ALL\n"; }
	else { print "Method: " . uc($method) . "\n"; }

        print "\n";

	f_probe_pcapinit;
}

sub f_probe_pcapinit {
	$g_cap_descrip = Net::Pcap::open_live($dev, 1500, 0, 0, \$err);

	unless (defined $g_cap_descrip) {
	    die 'Unable to create packet capture on device ', $dev, ' - ', $err;
        }

        # Create filter.
	my $filter;
	Net::Pcap::compile($g_cap_descrip, \$filter, $myfilter, 0, $netmask) && die 'Unable to compile packet capture filter';
	Net::Pcap::setfilter($g_cap_descrip, $filter) && die 'Unable to set packet capture filter';

	# Initiate endless packet gathering.
	Net::Pcap::loop($g_cap_descrip, -1, \&f_probe_read80211b_func , '' );
};

sub f_probe_read80211b_func {
	my($data, $header, $packet) = @_;
	$data = unpack('H*',$packet);

	$ether = NetPacket::Ethernet::strip($packet);
	$ip = NetPacket::IP->decode($ether);
	$tcp = NetPacket::TCP->decode($ip->{'data'});

	if (len($data) > 12) {
		my $sipdata = siprequest($data);
		log_data($sipdata) if (ascii2hex(substr($sipdata, 0, 1)) ne "00");
	}
};

sub siprequest {
	my $data = shift;
	$data = substr($data, $request*2);

	return hex2ascii($data);
};

sub len {
	my $data = shift;
	$data = substr($data, $len*2, 4);

	return hex($data);
};

sub log_data {
	my $sip_pkt = Net::SIP::Packet->new_from_string(@_);
	my $req = Net::SIP::Packet->new_from_string($sip_pkt->as_string);

	my $from = $sip_pkt->get_header('from');
	{
		my @values = split(';', $from);
		$from = $values[0];
	}

	my $to = $sip_pkt->get_header('to');
	{
		my @values = split(';', $to);
		$to = $values[0];
	}

	if ($method eq "" || $req->method eq uc($method)) {
		if ($user eq '' || $from =~ /$user/i || $to =~ /$user/i) {
			print $ip->{'src_ip'}.':'.$tcp->{'src_port'}.' => '.$ip->{'dest_ip'}.':'.$tcp->{'dest_port'}."\t(".$req->method.") ";
			if($sip_pkt->is_response()) { print "\tResponse: ".$req->code; }
			else { print "\tRequest"; }
			print "\n";
			print $sip_pkt->as_string."\n\n";
		}
	}
}

sub ascii2hex($) {
	(my $str = shift) =~ s/(.|\n)/sprintf("%02lx", ord $1)/eg;
	return $str;
};

sub hex2ascii($) {
	(my $str = shift) =~ s/([a-fA-F0-9]{2})/chr(hex $1)/eg;
	return $str;
};

sub help {
	print qq {
Usage:  $0 [options]

    == Options ==
      -i <iface>       = interface (default try to get automatically)
      -p <port>        = port (default 5060/udp and 5061/tcp)
      -m <method>      = scan only a method (default show all: REGISTER, INVITE, ...)
      -udp             = scan only UDP (default UDP + TCP)
      -tcp             = scan only TCP (default UDP + TCP)
      -ip <addr>       = filter remote IP (default show all)
      -user <data>     = filter user in 'To' or 'From' (default show all)
      -h, -help        = This help

    == Examples ==
      $0 -udp -ip "80.80.80.0/24" -p 5080
      $0 -i eth0 -user "mysipuser"
      $0 -ip "80.80.80.0/24" -user "mysipuser"

};

	exit 1;
};

init();

Supongamos el caso de que queremos monitorizar el registro de un usuario ‘pruebas’. Podemos hacer lo siguiente:


root@sip:~# perl sipmon.pl -m REGISTER -user "pruebas"
Interface: eth0 (X.X.224.7)
Filter: (udp || tcp) && ((dst X.X.224.7 && (dst port 5060 || dst port 5061)) || (src X.X.224.7 && (src port 5060 || src port 5061)))
Method: REGISTER

X.X.194.138:5080 => X.X.224.7:5060 (REGISTER) Request
REGISTER sip:sip.myserver.com SIP/2.0
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bK85544896f396b9b8
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>
Contact: <sip:pruebas204@192.168.200.2:5080;transport=udp>
Supported: path
Authorization: Digest username="pruebas204", realm="sip", algorithm=MD5, uri="sip:sip.myserver.com", nonce="31dff2d2", response="a4a80fd271da7fae67b2d7f070d99674"
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47423 REGISTER
Expires: 120
User-Agent: Grandstream GXP2000 1.2.5.3
Max-Forwards: 70
Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE,UPDATE,PRACK,MESSAGE
Content-Length: 0

X.X.224.7:5060 => X.X.194.138:5080 (REGISTER) Response: 401
SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bK85544896f396b9b8;received=X.X.194.138;rport=5080
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>;tag=as39c9e1bd
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47423 REGISTER
Server: sip
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
WWW-Authenticate: Digest algorithm=MD5, realm="sip", nonce="2f022db4"
Content-Length: 0

X.X.194.138:5080 => X.X.224.7:5060 (REGISTER) Request
REGISTER sip:sip.myserver.com SIP/2.0
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bKa5b835310120bb10
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>
Contact: <sip:pruebas204@192.168.200.2:5080;transport=udp>
Supported: path
Authorization: Digest username="pruebas204", realm="sip", algorithm=MD5, uri="sip:sip.myserver.com", nonce="2f022db4", response="7cc357c57b0198d9bdbcad0cf2870710"
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47424 REGISTER
Expires: 120
User-Agent: Grandstream GXP2000 1.2.5.3
Max-Forwards: 70
Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE,UPDATE,PRACK,MESSAGE
Content-Length: 0

X.X.224.7:5060 => X.X.194.138:5080 (REGISTER) Response: 200
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bKa5b835310120bb10;received=X.X.194.138;rport=5080
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>;tag=as39c9e1bd
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47424 REGISTER
Server: sip
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
Expires: 120
Contact: <sip:pruebas204@192.168.200.2:5080;transport=udp>;expires=120
Date: Thu, 04 Apr 2013 20:11:14 GMT
Content-Length: 0

Como podemos ver en el resultado, únicamente nos aparece el tráfico de la señalización SIP correspondiente a ese usuario, algo bastante útil si tenemos cientos de clientes conectados. En este caso:

Cliente -> Servidor (REGISTER)
Servidor -> Cliente (401 Unauthorized)
Cliente -> Servidor (REGISTER)
Servidor -> Cliente (200 OK)