#!/usr/bin/perl # # couldnt find any other tool to do this, so # this will track bytes xferred in/out by user # over the network # # using perl over tcpdump # Hacked a lil by vap0r :) # 8/2002 miff # use strict; $|=1; ######### CHANGE THESE ENTRIES HERE ################## my $lsof = "/usr/sbin/lsof"; my $tcpdump = "/usr/sbin/tcpdump"; my $ip = "12.36.68.130"; my @extra_ips; #for vhosts! push @extra_ips, "10.1.1.1"; push @extra_ips, "10.2.1.1"; ######### END OF CHANGABLE VARIABLES ################ my $debug = 0; #set to 1 for debug printing my %conx; #connection hash like this: # {our port}{their ip}{their port}{inbound|outbound} = bytecount my %ports; #local owners of ports like this: {port}{remoteip}{remoteport} = "(user procname)" my $timer = time; #use these to print once in a while my $lastprinttime = $timer; my $increment = 10; #print every 20 seconds # 1. get input from tcpdump # 2. read line by line and split # 3. add bytes to running totals in to/from port/ip hash # 4. when new port is active, lsof for local user # 5. print output in total bytes for local users, autorefresh # 6. stop on fin? (is this tcp only?) <-- enhancement open FILE, "tcpdump -n -p tcp |"; while () { chomp; #we are dealing with $_ right now implicitly... my @a = split(/ /); my $from = $a[1]; my @b = split(/\./,$from); $from = "$b[0].$b[1].$b[2].$b[3]"; my $fport = $b[4]; my $to = $a[3]; @b = split(/\./,$to); $to = "$b[0].$b[1].$b[2].$b[3]"; my $tport = $b[4]; chop($tport); #get rid of trailing colon my $data = $a[5]; $data =~ s/.+\(//; $data =~ s/\)+//; next if ($data =~ /[^0-9]/); #skip anything without a size #hack to cover multiple ips!! foreach my $xip (@extra_ips) { if ($from == $xip) { $from = $ip; } if ($to == $xip) { $to = $ip; } } #now add it: if ($from eq $ip) { #this is outgoing unless (exists $conx{$fport}{$to}{$tport}) { $ports{$fport}{$to}{$tport} = &whohas2($fport,$to,$tport); } $conx{$fport}{$to}{$tport}{out} += $data; } else { #incoming packet unless (exists $conx{$tport}{$to}{$fport}) { $ports{$tport}{$from}{$fport} = &whohas2($tport,$from,$fport); } $conx{$tport}{$from}{$fport}{in} += $data; } &dbg("row: $from:$fport $to:$tport datasize: $data \n"); #timer stuff here: $timer = time; if ($timer > ($lastprinttime + $increment)) { &showdata2; $lastprinttime = $timer; } } close FILE; ## {our port}{their ip}{their port}{inbound|outbound} = bytecount sub showdata2 { #using globals here my @thisrun; #so we can order the data... my @totals; print "\n\n\n\n\n\n"; print "TOTAL IN OUT PORT/OWNER REMOTE\n"; print "-------------------------------------------------------------------------------------\n"; foreach my $port (keys (%conx)) { foreach my $other (keys (%{$conx{$port}})) { foreach my $rport (keys (%{$conx{$port}{$other}})) { my $total = $conx{$port}{$other}{$rport}{in} + $conx{$port}{$other}{$rport}{out}; my $t_total = $total; my $t_in = $conx{$port}{$other}{$rport}{in}; my $t_out = $conx{$port}{$other}{$rport}{out}; my $t_port = "$port $ports{$port}{$other}{$rport}"; my $t_host = "$other P:$rport"; #push @thisrun, $text; if ($t_total > 1000000) { $t_total = int(($t_total + 500000) / 1000000); $t_total = "$t_total" . "M"; } elsif ($t_total > 1000) { $t_total = int(($t_total + 500) / 1000); $t_total = "$t_total" . "K"; } if ($t_in > 1000000) { $t_in = int(($t_in + 500000) / 1000000); $t_in = "$t_in" . "M"; } elsif ($t_in > 1000) { $t_in = int(($t_in + 500) / 1000); $t_in = "$t_in" . "K"; } if ($t_out > 1000000) { $t_out = int(($t_out + 500000) / 1000000); $t_out = "$t_out" . "M"; } elsif ($t_out > 1000) { $t_out = int(($t_out + 500) / 1000); $t_out = "$t_out" . "K"; } my $text = sprintf "$total|%-7s %-7s %-7s %-35s %-25s\n",$t_total,$t_in,$t_out,$t_port,$t_host; push @thisrun, $text; } } } @thisrun = sort {$a <=> $b} @thisrun; foreach my $row (@thisrun) { my ($nothing,$printme) = split (/\|/,$row); print $printme; } print "=====================================================================================\n"; } # # formating options can be set with special vars like : # $=, $~, etc. refere to camel book # # format = format options # write; instead of print, will flush you output buffer, which can hold a consistent template of data. # # format Declare a picture format for use by the `write' # function. For example: # # format Something = # Test: @<<<<<<<< @||||| @>>>>> # $str, $%, '$' . int($num) # . # # $str = "widget"; # $num = $cost/$quantity; # $~ = 'Something'; # write; # sub whohas2 { my ($port,$rip,$rport) = @_; my @result = split (/\n/,`$lsof -n -i tcp:$port`); my $ret; foreach my $row (@result) { next if ($row =~ /IPv6/); #skip v6 for now next if ($row =~ /COMMAND/); #skip the header row for now my @data = split (/\s+/,$row); #base this all on field 8, which holds everything: 199.105.121.137:14988->63.98.19.242:6667 my ($front,$back) = split(/\-\>/, $data[8]); my $local = "$ip:$port"; my $remote = "$rip:$rport"; if ($back eq $remote) { $ret = "($data[2] $data[0])"; return($ret); } } return("ERROR"); } #lsof -n -i tcp:40467 #COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME #BitchX-1. 16061 jimp 4u IPv4 0xe1130100 0t0 TCP 199.105.121.137:14988->63.98.19.242:6667 (ESTABLISHED) sub dbg { #debug print my ($text) = @_; if ($debug) { print "DBG: $text"; } }