#! /usr/bin/perl # Program to reformat tcpdump output into siptrace format. # Note: When running tcpdump, use the options '-s 1500 -x -tttt'. # '-s 1500 -x' gives a hex dump of the entire packet. (Use a larger number # if your interface supports longer packets.) # '-tttt' prints the timestamps in GMT to high precision. # Currently, does not handle fragmented packets. $debug = 0; $TCP = 1; $UDP = 2; $UNKNOWN = 3; $frame_counter = 0; print "\n"; print "\n"; # Read the input file. for ($_ = <>, chomp; $_; ) { # The current line should be a packet description line. split(' ', $_); print STDERR $_, "\n" if $debug; # Extract the fixed fields. $date = $_[0]; $time = $_[1]; # Skip non-IP packets. if ($_[2] ne 'IP') { # Skip this line and any following data lines. do { $_ = <>; } while ($_ =~ /^\t/); chomp; next; } # Check the date and time have the right format, and the 3rd field is "IP". if (!($date =~ /^\d\d\d\d-\d\d-\d\d$/ && $time =~ /^\d\d:\d\d:\d\d/)) { print STDERR "Uninterpretable input line where packet header expected: $_\n"; # Skip this line and any following data lines. do { $_ = <>; } while ($_ =~ /^\t/); chomp; next; } # Assemble GMT. $Z = $date . 'T' . $time . 'Z'; # Get the source and destination. $_[5] =~ s/:$//; $_[3] =~ s/\.([^.]+)$/:$1/; $_[5] =~ s/\.([^.]+)$/:$1/; if ($_[4] eq '>') { $source = $_[3]; $dest = $_[5]; } elsif ($_[4] eq '>') { $source = $_[5]; $dest = $_[4]; } else { print STDERR "Uninterpretable direction indicator in packet header: $_\n"; $_ = <>; chomp; next; } # Get the packet type. if ($_[6] eq 'UDP,') { $type = $UDP; # $length is the reported number of bytes of user data. $length = $_[8]; } elsif ($_[6] =~ /^([SFPRWE]+|\.)$/ && $_[7] =~ /^(\d+:\d+\(\d+\)$|[a-z][a-z][a-z]$|<)/) { $type = $TCP; # $length is the reported number of bytes of user data, or 0 if none. $length = ($_[7] =~ /^\d+:\d+\((\d+)\)/) ? $1 : 0; } else { # Don't report. $type = $UNKNOWN; } # Get the data lines. # Only examine the first 49 characters of a line, in case it has been # hex-dump'ed, which adds ASCII data after the hex data. $data = ''; for ($_ = <>, chomp; $hex = substr($_, 0, 49), $hex =~ /^\t0x[0-9a-f]+:[ 0-9a-f]*$/; $_ = <>, chomp) { $hex =~ s/^\t0x[0-9a-f]+://; $hex =~ s/ //g; $data .= $hex; } # Pack the packet into a string. $data = pack('H*', $data); # Extract the user data, based on the packet type. if ($type == $UNKNOWN) { # Unknown. # Skip the packet. } elsif ($type == $UDP) { # UDP if (!((vec($data, 0, 8) & 0xF0) == 0x40 && vec($data, 9, 8) == 17)) { print STDERR "UDP packet has wrong version or protocol.\n"; next; } # Remove the IP header based on the IHL field, and the 8 byte # UDP header. $data = substr($data, (vec($data, 0, 8) & 0x0F) * 4 + 8); if (length($data) != $length) { print STDERR "Truncated packet?\n"; } # If the packet is SIP, process the packet. if (&SIP_message($data)) { &input_message($Z, $source, $dest, $data); } } else { # TCP print STDERR "TCP packet.\n"; } } print "\n"; exit 0; # Test whether a string is a SIP message. sub SIP_message { my($message) = @_; # Check that the message is a valid SIP message. # Must start with a request line or a response line. if (!($message =~ m%^([a-z]+) \S+ SIP/2\.0\r\n%i || $message =~ m%^SIP/2\.0 (\d\d\d) (.*)\r\n%)) { return 0; } # Each following line must end with CR-LF, and the headers must end # with CR-LF-CR-LF. if (!($message =~ m%^((.*\r\n)+\r\n)%)) { return 0; } $headers = $1; if (!($headers =~ m%\ncontent-length:\s*(\d+)%i)) { return 0; } $length = $1; if (!($length == length($message) - length($headers))) { return 0; } return 1; } # Process a SIP message. sub input_message { my($Z, $source, $dest, $message) = @_; print STDERR "\&input_message($message)\n" if $debug; # Get the branch IDs. @branches = ($message =~ m/\nVia:.*;branch=([^;\r\n]+)/gi); # Assemble the transaction ID. ($cseq) = ($message =~ m/\ncseq:\s*(\d+)/i); ($callid) = ($message =~ m/\ncall-id:\s*([^;\r\n]+)/i); ($fromtag) = ($message =~ m/\nfrom:.*tag=([^;\r\n]+)/i); ($totag) = ($message =~ m/\nto:.*tag=([^;\r\n]+)/i); ($transid) = ("$cseq,$callid,$fromtag,$totag"); print "\t\n"; print "\t\t\n"; foreach $b (@branches) { print "\t\t\t$b\n"; } print "\t\t\n"; print "\t\t\n"; print "\t\t$source\n"; print "\t\t$dest\n"; print "\t\t$source\n"; print "\t\t$dest\n"; print "\t\t$transid\n"; if ($message =~ m%^([a-z]+) \S+ SIP/2\.0\r%i) { print "\t\t$1\n"; } if ($message =~ m%^SIP/2\.0 (\d\d\d) (.*)\r%) { print "\t\t$1\n"; print "\t\t$2\n"; } print "\t\t", $frame_counter++, "\n"; print "\t\t" sequences in the message. $message =~ s/]]>/]]]]>/g; print $message; print "]]>\n"; print "\t\n"; }