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.
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)
Genial tu script amigo, felicidades por este gran blog
Gracias Jose!
un gran aporte. gracias
Perfecto. Justo lo que estaba buscando.
Gracias por el aporte.