#!/usr/bin/env perl # -*- Perl -*- # # Copyright 1996 Massachusetts Institute of Technology # # Permission to use, copy, modify, and distribute this software and # its documentation for any purpose and without fee is hereby # granted, provided that both the above copyright notice and this # permission notice appear in all copies, that both the above # copyright notice and this permission notice appear in all # supporting documentation, and that the name of M.I.T. not be used # in advertising or publicity pertaining to distribution of the # software without specific, written prior permission. M.I.T. makes # no representations about the suitability of this software for any # purpose. It is provided "as is" without express or implied # warranty. # # THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS # ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT # SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # defaults: $Plotwindow = 1; $PlotTemplate = '$from."-".$to.".xplot"'; $Tcpdump = 'STDIN'; $Usage_first = 1; $BreakOnSyns = 0; $EndOnFins = 0; $Quiet = 0; $Cumulative = 0; $TimeConvert = 0; $ForceRelative = 0; $FinThreshold = 1; # seconds $GzipOutput = 0; # other initializations #$Packets; #$Bytes; #$StartTime = 0; #$LastTime; #$State; # 'synSent', 'synRecvd', 'synackSent', 'synackRecvd', 'finSent', 'finRecvd', 'finackSent', 'finackRecvd' #$ListFile; sub usage { return if (!$Usage_first); $Usage_first = 0; print <<"END_OF_USAGE"; Usage: $0 [-w] [-s] [-c] [-plot[filename]] [-list[filename]] [-?] [-help] -w: plot window. -s: break up conversations on syns. -f: ignore socket activity after a fin (until socket is re-used) -c: cumulative - adds all data coming from a server -plot[filename]: plot packets in . The may be built out of $from (host and port), $fromHost, $fromPort, $from[0..n] for the segments of the domain name. '$from.".xplot"' would be abc.def.com:1234. The corresponding fields exist for the to field. default: '$from[0].":".$fromPort."-".$to[0].":".$toPort.".xplot"'. -list[filename]: output the list of generated plot files to filename. -r: relative sequence numbers. -t: time convert - insure that time is in decimal number of seconds. -q: quiet - no visible output. -?/-help: this message. END_OF_USAGE } sub ReadArg { local($arg) = substr(@_[0], 1); return 0 if ($arg eq 'e'); if ($arg eq '?' || $arg eq 'help') { &usage; exit(0); } elsif ($arg eq 'w') { $Plotwindow = 1; } elsif ($arg eq 's') { $BreakOnSyns = 1; } elsif ($arg =~ /^f(\d*)/) { $EndOnFins = 1; $FinThreshold = $1 if ($1 ne ''); } elsif ($arg eq 'c') { $Cumulative = 1; } elsif (substr($arg, 0, 4) eq 'plot') { $PlotTemplate = substr($arg, 4); } elsif (substr($arg, 0, 4) eq 'list') { $ListFile = substr($arg, 4); open($ListFile, ">$ListFile") || die "error opening \"$ListFile\" for writing: $!\n"; } elsif ($arg eq 't') { $TimeConvert = 1; } elsif ($arg eq 'r') { $ForceRelative = 1; } elsif ($arg eq 'q') { $Quiet = 1; } elsif ($arg eq 'z') { $GzipOutput = 1; } else { &usage(); print "unknown argument \"$arg\".\n"; } return 1; } sub justSeconds { local($time) = @_; return $time if (!($time =~ /:/)); local($hr, $mn, $sec) = split(':', $time, 3); return $hr*3600 + $mn*60 + $sec; } sub subTimes { local($big, $little) = @_; $big = &justSeconds($big); $little = &justSeconds($little); return ($big - $little); } sub newConversation { local($from, $to, $time) = @_; # create variables for PlotTemplate $from =~ /(.*)\.(\d+)$/; local($fromHost, $fromPort) = ($1, $2); local(@from) = split('\.', $fromHost); $to =~ /(.*)\.(\d+)$/; local($toHost, $toPort) = ($1, $2); local(@to) = split('\.', $toHost); # create Xplot file from template $XplotName{$from} = eval($PlotTemplate); if ($GzipOutput) { open($from, "|gzip >$XplotName{$from}.gz") || die "error opening gzip pipe to \"$from\".gz for writing: $!\n"; } else { open($from, ">$XplotName{$from}") || die "error opening \"$from\" for writing: $!\n"; } print $from "timeval signed\ntitle\n$from-->$to\n"; # initialize conversation $To{$from} = $to; $LastSendSeq{$from.'-'.$to} = -1; $StartTime{$from} = $time; push(@Froms, $from); $AckOffset{$from} = $SeqOffset{$from} = 0; $Ignored{$from.'-'.$to} = 0; } sub closeOut { local($from) = @_; print $from "go\n"; $TotalPackets += $Packets{$from}; $TotalBytes += $Bytes{$from}; print "$from: $Packets{$from} packets $Bytes{$from} bytes took ", &subTimes($LastTime{$from}, $StartTime{$from}), "\n" if (!Quiet); $Mode{$from} = 'client' if (!defined($Mode{$from})); # added to handle bogus NT netmon dumps print $ListFile "$from $To{$from} $Mode{$from} $XplotName{$from} $Packets{$from} $Bytes{$from} ", &subTimes($LastTime{$from}, $StartTime{$from}), ' ', $Bytes{$from}/($Bytes{$from} + $Packets{$from}*40), "\n" if $ListFile; $MaxLast = $LastTime{$from} if (!defined($MaxLast) || $LastTime{$from} > $MaxLast); $MinFirst = $StartTime{$from} if (!defined($MinFirst) || $StartTime{$from} < $MinFirst); } sub clearOut { local($from, $to) = @_; # these attributes are specific to a conversation delete $LastSendSeq{$from.'-'.$to}; delete $FirstSeq{$from.'-'.$to}; delete $LastAckTime{$from.'-'.$to}; delete $LastAckSeq{$from.'-'.$to}; delete $LastWind{$from.'-'.$to}; delete $WScale{$from.'-'.$to}; # these stats are aggregated for all converstaions on a server delete $Packets{$from}; delete $Bytes{$from}; delete $LastTime{$from}; delete $StartTime{$from}; delete $Mode{$from}; delete $Served{$from}; # not needed now because server is never cleared delete $To{$from}; delete $XplotName{$from}; delete $Ignored{$from.'-'.$to}; } sub removeFrom { local($from) = @_; local($i); foreach $i ($[ .. $#Froms) { if ($Froms[$i] eq $from) { splice(@Froms, $i, 1); return 1; } } return 0; } while (@ARGV[0] =~ /^-/ && &ReadArg(shift(@ARGV))) {} if (($inputFile = shift(@ARGV))) { if ($inputFile =~ /.*\.gz$/) { open (TCPDUMP, "zcat $inputFile|") || die "error opening \"$inputFile\" for reading: $!\n"; } else { open (TCPDUMP, "<$inputFile") || die "error opening \"$inputFile\" for reading: $!\n"; } $Tcpdump = 'TCPDUMP'; } for ($lineNo = 1; <$Tcpdump>; $lineNo++) { local($i); local($ackIsZero) = (0); local($opts); local(@opts); local(%opts); chop; $opts = $_; # save this for later split(/ /); $time = $_[0]; $from = $_[1]; $to = $_[3]; chop($to); # strip off colon $flags = $_[4]; last if (/\d+\s+packets/); if ($_[2] ne '>') { print stderr "tcpdump2xplot: Malformed entry in dump file $inputFile:$lineNo \"$_\"\n"; next; } $time = &justSeconds($time) if ($TimeConvert); &newConversation($from, $to, $time) if (!(defined($StartTime{$from}))); $opts =~ s/^.*.*//; @opts = split(/,/,$opts); for ($i = 0; $i <= $#opts; $i++) { local(@opt); $opts[$i] =~ s/^ sack/sack/; # some tcpdumps put a stray space at the beginning of "sack" @opt = split(/ /,$opts[$i]); if ($opt[0] =~ /nop/ || $opt[0] =~ /eol/) { next; } $opts{$opt[0]} = join(' ', @opt[ 1 .. $#opt ]); } if ($flags =~ /S/) { if ($Mode{$from} eq 'client') { # re-used client socket's syn if ($BreakOnSyns) { &closeOut($from); &removeFrom($from); &clearOut($from, $to); &newConversation($from, $to, $time); $Mode{$from} = 'client'; } } elsif ($Mode{$from} eq 'server') { # server's syn $Served{$from}++; } else { # client socket's syn $Mode{$from} = 'client'; $Mode{$to} = 'server'; } if (exists $opts{'wscale'}) { local($val) = $opts{'wscale'}; if ($val < 15 && $val >= 0) { $WScale{$from.'-'.$to} = $val; print stderr "tcpdump2xplot: noticed wscale value for $from.-.$to of "; print stderr $WScale{$from.'-'.$to}; print stderr ".\n"; } else { print stderr "tcpdump2xplot: ignoring wild wscale value \"$val\" in $inputFile:$lineNo\n"; } } } if ($flags =~ /F/ && $Mode{$from} eq 'client' && $EndOnFins && !$Ignored{$from.'-'.$to}) { local ($dif) = $time - $LastTime{$from}; if ($dif > $FinThreshold) { print stderr "tcpdump2xplot: delayed F ($dif seconds) in dump file $inputFile:$lineNo \"$_\"\n"; } $Ignored{$from.'-'.$to} = 1; } next if ($Ignored{$from.'-'.$to} || $Ignored{$to.'-'.$from}); $LastTime{$from} = $time; $Packets{$from}++; if (/$flags ([0-9]*):([0-9]*)\(([0-9]*)\) /) { local($f); local($t); local($n); $f = $1; $t = $2; $n = $3; if ($flags =~ /S/) { $t++; $n++; } if ($flags =~ /F/) { $t++; $n++; } $sendseq = $f; $LastSendSeq{$from.'-'.$to} = $sendseqlast = $t; $datalength = $n; $Bytes{$from} += $n; if (!defined $FirstSeq{$from.'-'.$to}) { if ($ForceRelative) { $sendseq = 0; $LastSendSeq{$from.'-'.$to} = $sendseqlast = $t - $f; $FirstSeq{$from.'-'.$to} = 0; $ackIsZero = 1; } else { $FirstSeq{$from.'-'.$to} = $f; } } } else { $sendseq = $LastSendSeq{$from.'-'.$to}; $sendseqlast = $LastSendSeq{$from.'-'.$to}; } if (/win ([0-9]*)/) { # remove space in "]*) /" to handle win - EGP $window = $1; if (exists $WScale{$from.'-'.$to} && !($flags =~ /S/)) { $window <<= $WScale{$from.'-'.$to}; } } else { $window = -1; } if (/ack ([0-9]*) /) { $ack = $ackIsZero ? 0 : $1; } else { $ack = -1; } if ($Cumulative) { # adjust sequence number to be relative to last ack. $sendseqlast -= $sendseq - $SeqOffset{$from}; $sendseq = $SeqOffset{$from}; $SeqOffset{$from} = $sendseqlast; } else { # adjust sequence number to be relative to start of conversation. $sendseq -= $FirstSeq{$from.'-'.$to}; $sendseqlast -= $FirstSeq{$from.'-'.$to}; } print $from "darrow $time $sendseq\n"; print $from "uarrow $time $sendseqlast\n"; print $from "line $time $sendseq $time $sendseqlast\n"; if ($ack != -1) { $winend = $ack + $window; if (defined $LastAckTime{$to.'-'.$from}) { local($lastAckSeq, $ackSeq) = ($LastAckSeq{$to.'-'.$from}, $ack); if ($Cumulative) { # adjust sequence number to be relative to last ack. $ackSeq -= $lastAckSeq - $AckOffset{$to}; $lastAckSeq = $AckOffset{$to}; $AckOffset{$to} = $ackSeq; } else { # adjust sequence number to be relative to start of conversation. $lastAckSeq -= $FirstSeq{$to.'-'.$from}; $ackSeq -= $FirstSeq{$to.'-'.$from}; } print $to "line $LastAckTime{$to.'-'.$from} $lastAckSeq "; print $to "$time $lastAckSeq\n"; if ($LastAckSeq{$to.'-'.$from} != $ack) { print $to "line $time $lastAckSeq $time $ackSeq\n"; } else { print $to "dtick $time $ackSeq\n"; } if (exists $opts{'sack'}) { local($sacks) = $opts{'sack'}; local(@sacks); local($i); # munge newer tcpdump format for SACK so that it looks # just just like my old format $sacks =~ s/\{/ /g; $sacks =~ s/\}/ /g; # print stderr "SACKS before: $sacks \n"; $sacks =~ s/^sack [123] //; $sacks =~ s/^[123] //; # print stderr "SACKS after : $sacks \n"; @sacks = split(/ /, $sacks); for ($i = 0; $i <= $#sacks; $i++) { local($start); local($end); $sacks[$i] =~ /([0-9]*):([0-9]*)/; $start = $1; $end = $2; # print stderr "SACK start $start end $end \n"; if ($Cumulative) { # yikes! what to do? } else { # adjust sequence number to be relative to start of conversation. # print stderr "SACK relative to $FirstSeq{$to.'-'.$from} \n"; $start -= $FirstSeq{$to.'-'.$from}; $end -= $FirstSeq{$to.'-'.$from}; # print stderr "SACK line $time $start $time $end green\n"; print $to "line $time $start $time $end green\n"; } } } if ($Plotwindow) { local($lastWinSeq, $winSeq) = ($LastWind{$to.'-'.$from}, $winend); if ($Cumulative) { # adjust sequence number to be relative to last ack. $winSeq -= $lastWinSeq - $AckOffset{$to}; $lastWinSeq = $AckOffset{$to}; } else { # adjust sequence number to be relative to start of conversation. $lastWinSeq -= $FirstSeq{$to.'-'.$from}; $winSeq -= $FirstSeq{$to.'-'.$from}; } print $to "line $LastAckTime{$to.'-'.$from} $lastWinSeq "; print $to "$time $lastWinSeq\n"; if ($LastWind{$to.'-'.$from} != $winend) { print $to "line $time $lastWinSeq $time $winSeq\n"; } else { print $to "utick $time $winSeq\n"; } } } $LastAckTime{$to.'-'.$from} = $time; $LastAckSeq{$to.'-'.$from} = $ack; $LastWind{$to.'-'.$from} = $winend; } } foreach $from (@Froms) { &closeOut($from); } print "summary: $TotalPackets packets $TotalBytes bytes took ", &subTimes($MaxLast, $MinFirst), " efficiency: ", $TotalBytes/($TotalBytes + $TotalPackets*40), "\n" if (!Quiet); print $ListFile "summary: $TotalPackets $TotalBytes ", &subTimes($MaxLast, $MinFirst), " ", $TotalBytes/($TotalBytes + $TotalPackets*40), "\n" if ($ListFile); close($ListFile) if ($ListFile); exit 0;