1*bc4097aaSchristos#!/usr/bin/perl -wT 2*bc4097aaSchristos# 3*bc4097aaSchristos# Author: Jefferson Ogata (JO317) <jogata@pobox.com> 4*bc4097aaSchristos# Date: 2000/04/22 5*bc4097aaSchristos# Version: 0.10 6*bc4097aaSchristos# 7*bc4097aaSchristos# Please feel free to use or redistribute this program if you find it useful. 8*bc4097aaSchristos# If you have suggestions, or even better, bits of new code, send them to me 9*bc4097aaSchristos# and I will add them when I have time. The current version of this script 10*bc4097aaSchristos# can always be found at the URL: 11*bc4097aaSchristos# 12*bc4097aaSchristos# http://www.antibozo.net/ogata/webtools/plog.pl 13*bc4097aaSchristos# http://pobox.com/~ogata/webtools/plog.txt 14*bc4097aaSchristos# 15*bc4097aaSchristos# Parse ipmon output into a coherent form. This program only handles the 16*bc4097aaSchristos# lines regarding filter actions. It does not parse nat and state lines. 17*bc4097aaSchristos# 18*bc4097aaSchristos# Present lines from ipmon to this program on standard input. 19*bc4097aaSchristos# 20*bc4097aaSchristos# EXAMPLES 21*bc4097aaSchristos# 22*bc4097aaSchristos# plog -AF block,log < /var/log/ipf 23*bc4097aaSchristos# 24*bc4097aaSchristos# Generate source and destination reports of all packets logged with 25*bc4097aaSchristos# block or log actions, and report TCP flags and keep state actions. 26*bc4097aaSchristos# 27*bc4097aaSchristos# plog -S -s ./services www.example.com < /var/log/ipf 28*bc4097aaSchristos# 29*bc4097aaSchristos# Generate a source report of traffic to or from www.example.com using 30*bc4097aaSchristos# the additional services defined in ./services. 31*bc4097aaSchristos# 32*bc4097aaSchristos# plog -nSA block < /var/log/ipf 33*bc4097aaSchristos# 34*bc4097aaSchristos# Generate a source report of all blocked packets with no hostname 35*bc4097aaSchristos# lookups. This is handy for an initial pass to identify portscans or 36*bc4097aaSchristos# other aggressive traffic. 37*bc4097aaSchristos# 38*bc4097aaSchristos# plog -SFp 192.168.0.0/24 www.example.com/24 < /var/log/ipf 39*bc4097aaSchristos# 40*bc4097aaSchristos# Generate a source report of all packets whose source or destination 41*bc4097aaSchristos# address is either in 192.168.0.0/24 or an address associated with 42*bc4097aaSchristos# the host www.example.com, report packet flags and perform paranoid 43*bc4097aaSchristos# hostname lookups. This is a handy usage for examining traffic more 44*bc4097aaSchristos# closely after identifying a potential attack. 45*bc4097aaSchristos# 46*bc4097aaSchristos# TODO 47*bc4097aaSchristos# 48*bc4097aaSchristos# - Handle output from ipmon -v. 49*bc4097aaSchristos# - Handle timestamps from other locales. Anyone with a timestamp problem 50*bc4097aaSchristos# please email me the format of your timestamps. 51*bc4097aaSchristos# - It looks as though short TCP or UDP packets will break things, but I 52*bc4097aaSchristos# haven't seen any yet. 53*bc4097aaSchristos# 54*bc4097aaSchristos# CHANGES 55*bc4097aaSchristos# 56*bc4097aaSchristos# 2000/04/22 (0.10): 57*bc4097aaSchristos# - Restructured host name and address caches. Hosts are now cached using 58*bc4097aaSchristos# packed addresses as keys. Conversion to IPv6 should be simple now. 59*bc4097aaSchristos# - Added paranoid hostname lookups. 60*bc4097aaSchristos# - Added netmask qualifications for address arguments. 61*bc4097aaSchristos# - Tweaked usage info. 62*bc4097aaSchristos# 2000/04/20: 63*bc4097aaSchristos# - Added parsing and tracking of TCP and state flags. 64*bc4097aaSchristos# 2000/04/12 (0.9): 65*bc4097aaSchristos# - Wasn't handling underscore in hostname,servicename fields; these may be 66*bc4097aaSchristos# logged using ipmon -n. Observation by <ark@eltex.ru>. 67*bc4097aaSchristos# - Hadn't properly attributed observation and fix for repetition counter in 68*bc4097aaSchristos# 0.8 change log. Added John Ladwig to attribution. Thanks, John. 69*bc4097aaSchristos# 70*bc4097aaSchristos# 2000/04/10 (0.8): 71*bc4097aaSchristos# - Service names can also have hyphens, dummy. I wasn't allowing these 72*bc4097aaSchristos# either. Observation and fix thanks to Taso N. Devetzis 73*bc4097aaSchristos# <devetzis@snet.net>. 74*bc4097aaSchristos# - IP Filter now logs a repetition counter. Observation and fixes (changed 75*bc4097aaSchristos# slightly) from Andy Kreiling <Andy@ntcs-inc.com> and John Ladwig 76*bc4097aaSchristos# <jladwig@nts.umn.edu>. 77*bc4097aaSchristos# - Added fix to handle new Solaris log format, e.g.: 78*bc4097aaSchristos# Nov 30 04:49:37 raoul ipmon[121]: [ID 702911 local0.warning] 04:49:36.420541 hme0 @0:34 b 205.152.16.6,58596 -> 204.60.220.24,113 PR tcp len 20 44 79*bc4097aaSchristos# Fix thanks to Taso N. Devetzis <devetzis@SNET.Net>. 80*bc4097aaSchristos# - Added services map option. 81*bc4097aaSchristos# - Added options for generating only source/destination tables. 82*bc4097aaSchristos# - Added verbosity option. 83*bc4097aaSchristos# - Added option for reporting traffic for specific hosts. 84*bc4097aaSchristos# - Added some more ICMP unreachable codes, and made code and type names 85*bc4097aaSchristos# match the ones in IP Filter parse.c. 86*bc4097aaSchristos# - Condensed output format somewhat. 87*bc4097aaSchristos# - Various minor improvements, perhaps slight speed improvements. 88*bc4097aaSchristos# - Documented new options in usage() and tried to improve wording. 89*bc4097aaSchristos# 90*bc4097aaSchristos# 1999/08/02 (0.7): 91*bc4097aaSchristos# - Hostnames can have hyphens, dummy. I wasn't allowing them in the syslog 92*bc4097aaSchristos# line. Fix from Antoine Verheijen <antoine.verheijen@ualberta.ca>. 93*bc4097aaSchristos# 94*bc4097aaSchristos# 1999/05/05 (0.6): 95*bc4097aaSchristos# - IRIX syslog prefixes the hostname with a severity code. Handle it. Fix 96*bc4097aaSchristos# from John Ladwig <jladwig@nts.umn.edu>. 97*bc4097aaSchristos# 98*bc4097aaSchristos# 1999/05/05 (0.5): 99*bc4097aaSchristos# - Protocols other than TCP, UDP, or ICMP have packet lengths reported in 100*bc4097aaSchristos# parentheses for some reason. The script now handles this. Thanks to 101*bc4097aaSchristos# Dispatcher <dispatch@blackhelicopters.org>. 102*bc4097aaSchristos# - I had mixed up info-request and info-reply ICMP codes, and omitted the 103*bc4097aaSchristos# traceroute code. Sorted this out. I had also missed code 0 for type 6 104*bc4097aaSchristos# (alternate address for host). Thanks to John Ladwig <jladwig@nts.umn.edu>. 105*bc4097aaSchristos# 106*bc4097aaSchristos# 1999/05/03: 107*bc4097aaSchristos# - Now accepts hostnames in the source and destination address fields, as 108*bc4097aaSchristos# well as port names in the port fields. This allows the people who are 109*bc4097aaSchristos# using ipmon -n to still use plog. Note that if you are logging 110*bc4097aaSchristos# hostnames, you are vulnerable to forgery of DNS information, modified 111*bc4097aaSchristos# DNS information, and your log files will be larger also. If you are 112*bc4097aaSchristos# using this program you can have it look up the names for you (still 113*bc4097aaSchristos# vulnerable to forgery) and keep your logged addresses all in numeric 114*bc4097aaSchristos# format, so that packets from the same source will always show the same 115*bc4097aaSchristos# source address regardless of what's up with DNS. Obviously, I don't 116*bc4097aaSchristos# favor using ipmon -n. Nevertheless, some people wanted this, so here it 117*bc4097aaSchristos# is. 118*bc4097aaSchristos# - Added S and n flags to %acts hash. Thanks to Stephen J. Roznowski 119*bc4097aaSchristos# <sjr@home.net>. 120*bc4097aaSchristos# - Stopped reporting host IPs twice when numeric output was requested. 121*bc4097aaSchristos# Thanks, yet again, to Stephen J. Roznowski <sjr@home.net>. 122*bc4097aaSchristos# - Number of minor tweaks that might speed it up a bit, and some comments. 123*bc4097aaSchristos# - Put the script back up on the web site. I had moved the site and 124*bc4097aaSchristos# forgotten to move the tool. 125*bc4097aaSchristos# 126*bc4097aaSchristos# 1999/02/04: 127*bc4097aaSchristos# - Changed log line parser to accept fully-qualified name in the logging 128*bc4097aaSchristos# host field. Thanks to Stephen J. Roznowski <sjr@home.net>. 129*bc4097aaSchristos# 130*bc4097aaSchristos# 1999/01/22: 131*bc4097aaSchristos# - Changed high port strategy to use 65536 for unknown high ports so that 132*bc4097aaSchristos# they are sorted last. 133*bc4097aaSchristos# 134*bc4097aaSchristos# 1999/01/21: 135*bc4097aaSchristos# - Moved icmp parsing to output loop. 136*bc4097aaSchristos# - Added parsing of icmp codes, and more types. 137*bc4097aaSchristos# - Changed packet sort routine to sort by port number rather than service 138*bc4097aaSchristos# name. 139*bc4097aaSchristos# 140*bc4097aaSchristos# 1999/01/20: 141*bc4097aaSchristos# - Fixed problem matching ipmon log lines. Sometimes they have "/ipmon" in 142*bc4097aaSchristos# them, sometimes just "ipmon". 143*bc4097aaSchristos# - Added numeric parse option to turn off hostname lookups. 144*bc4097aaSchristos# - Moved summary to usage() sub. 145*bc4097aaSchristos 146*bc4097aaSchristosuse strict; 147*bc4097aaSchristosuse Socket; 148*bc4097aaSchristosuse IO::File; 149*bc4097aaSchristos 150*bc4097aaSchristosselect STDOUT; $| = 1; 151*bc4097aaSchristos 152*bc4097aaSchristosmy %hosts; 153*bc4097aaSchristos 154*bc4097aaSchristosmy $me = $0; 155*bc4097aaSchristos$me =~ s/^.*\///; 156*bc4097aaSchristos 157*bc4097aaSchristos# Map of log codes for various actions. Not all of these can occur, but 158*bc4097aaSchristos# I've included everything in print_ipflog() from ipmon.c. 159*bc4097aaSchristosmy %acts = ( 160*bc4097aaSchristos 'p' => 'pass', 161*bc4097aaSchristos 'P' => 'pass', 162*bc4097aaSchristos 'b' => 'block', 163*bc4097aaSchristos 'B' => 'block', 164*bc4097aaSchristos 'L' => 'log', 165*bc4097aaSchristos 'S' => 'short', 166*bc4097aaSchristos 'n' => 'nomatch', 167*bc4097aaSchristos); 168*bc4097aaSchristos 169*bc4097aaSchristos# Map of ICMP types and their relevant codes. 170*bc4097aaSchristosmy %icmpTypeMap = ( 171*bc4097aaSchristos 0 => +{ 172*bc4097aaSchristos name => 'echorep', 173*bc4097aaSchristos codes => +{0 => undef}, 174*bc4097aaSchristos }, 175*bc4097aaSchristos 3 => +{ 176*bc4097aaSchristos name => 'unreach', 177*bc4097aaSchristos codes => +{ 178*bc4097aaSchristos 0 => 'net-unr', 179*bc4097aaSchristos 1 => 'host-unr', 180*bc4097aaSchristos 2 => 'proto-unr', 181*bc4097aaSchristos 3 => 'port-unr', 182*bc4097aaSchristos 4 => 'needfrag', 183*bc4097aaSchristos 5 => 'srcfail', 184*bc4097aaSchristos 6 => 'net-unk', 185*bc4097aaSchristos 7 => 'host-unk', 186*bc4097aaSchristos 8 => 'isolate', 187*bc4097aaSchristos 9 => 'net-prohib', 188*bc4097aaSchristos 10 => 'host-prohib', 189*bc4097aaSchristos 11 => 'net-tos', 190*bc4097aaSchristos 12 => 'host-tos', 191*bc4097aaSchristos 13 => 'filter-prohib', 192*bc4097aaSchristos 14 => 'host-preced', 193*bc4097aaSchristos 15 => 'preced-cutoff', 194*bc4097aaSchristos }, 195*bc4097aaSchristos }, 196*bc4097aaSchristos 4 => +{ 197*bc4097aaSchristos name => 'squench', 198*bc4097aaSchristos codes => +{0 => undef}, 199*bc4097aaSchristos }, 200*bc4097aaSchristos 5 => +{ 201*bc4097aaSchristos name => 'redir', 202*bc4097aaSchristos codes => +{ 203*bc4097aaSchristos 0 => 'net', 204*bc4097aaSchristos 1 => 'host', 205*bc4097aaSchristos 2 => 'tos', 206*bc4097aaSchristos 3 => 'tos-host', 207*bc4097aaSchristos }, 208*bc4097aaSchristos }, 209*bc4097aaSchristos 6 => +{ 210*bc4097aaSchristos name => 'alt-host-addr', 211*bc4097aaSchristos codes => +{ 212*bc4097aaSchristos 0 => 'alt-addr' 213*bc4097aaSchristos }, 214*bc4097aaSchristos }, 215*bc4097aaSchristos 8 => +{ 216*bc4097aaSchristos name => 'echo', 217*bc4097aaSchristos codes => +{0 => undef}, 218*bc4097aaSchristos }, 219*bc4097aaSchristos 9 => +{ 220*bc4097aaSchristos name => 'routerad', 221*bc4097aaSchristos codes => +{0 => undef}, 222*bc4097aaSchristos }, 223*bc4097aaSchristos 10 => +{ 224*bc4097aaSchristos name => 'routersol', 225*bc4097aaSchristos codes => +{0 => undef}, 226*bc4097aaSchristos }, 227*bc4097aaSchristos 11 => +{ 228*bc4097aaSchristos name => 'timex', 229*bc4097aaSchristos codes => +{ 230*bc4097aaSchristos 0 => 'in-transit', 231*bc4097aaSchristos 1 => 'frag-assy', 232*bc4097aaSchristos }, 233*bc4097aaSchristos }, 234*bc4097aaSchristos 12 => +{ 235*bc4097aaSchristos name => 'paramprob', 236*bc4097aaSchristos codes => +{ 237*bc4097aaSchristos 0 => 'ptr-err', 238*bc4097aaSchristos 1 => 'miss-opt', 239*bc4097aaSchristos 2 => 'bad-len', 240*bc4097aaSchristos }, 241*bc4097aaSchristos }, 242*bc4097aaSchristos 13 => +{ 243*bc4097aaSchristos name => 'timest', 244*bc4097aaSchristos codes => +{0 => undef}, 245*bc4097aaSchristos }, 246*bc4097aaSchristos 14 => +{ 247*bc4097aaSchristos name => 'timestrep', 248*bc4097aaSchristos codes => +{0 => undef}, 249*bc4097aaSchristos }, 250*bc4097aaSchristos 15 => +{ 251*bc4097aaSchristos name => 'inforeq', 252*bc4097aaSchristos codes => +{0 => undef}, 253*bc4097aaSchristos }, 254*bc4097aaSchristos 16 => +{ 255*bc4097aaSchristos name => 'inforep', 256*bc4097aaSchristos codes => +{0 => undef}, 257*bc4097aaSchristos }, 258*bc4097aaSchristos 17 => +{ 259*bc4097aaSchristos name => 'maskreq', 260*bc4097aaSchristos codes => +{0 => undef}, 261*bc4097aaSchristos }, 262*bc4097aaSchristos 18 => +{ 263*bc4097aaSchristos name => 'maskrep', 264*bc4097aaSchristos codes => +{0 => undef}, 265*bc4097aaSchristos }, 266*bc4097aaSchristos 30 => +{ 267*bc4097aaSchristos name => 'tracert', 268*bc4097aaSchristos codes => +{ }, 269*bc4097aaSchristos }, 270*bc4097aaSchristos 31 => +{ 271*bc4097aaSchristos name => 'dgram-conv-err', 272*bc4097aaSchristos codes => +{ }, 273*bc4097aaSchristos }, 274*bc4097aaSchristos 32 => +{ 275*bc4097aaSchristos name => 'mbl-host-redir', 276*bc4097aaSchristos codes => +{ }, 277*bc4097aaSchristos }, 278*bc4097aaSchristos 33 => +{ 279*bc4097aaSchristos name => 'ipv6-whereru?', 280*bc4097aaSchristos codes => +{ }, 281*bc4097aaSchristos }, 282*bc4097aaSchristos 34 => +{ 283*bc4097aaSchristos name => 'ipv6-iamhere', 284*bc4097aaSchristos codes => +{ }, 285*bc4097aaSchristos }, 286*bc4097aaSchristos 35 => +{ 287*bc4097aaSchristos name => 'mbl-reg-req', 288*bc4097aaSchristos codes => +{ }, 289*bc4097aaSchristos }, 290*bc4097aaSchristos 36 => +{ 291*bc4097aaSchristos name => 'mbl-reg-rep', 292*bc4097aaSchristos codes => +{ }, 293*bc4097aaSchristos }, 294*bc4097aaSchristos); 295*bc4097aaSchristos 296*bc4097aaSchristos# Arguments we will parse from argument list. 297*bc4097aaSchristosmy $numeric = 0; # Don't lookup hostnames. 298*bc4097aaSchristosmy $paranoid = 0; # Do paranoid hostname lookups. 299*bc4097aaSchristosmy $verbosity = 0; # Bla' bla' bla'. 300*bc4097aaSchristosmy $sTable = 0; # Generate source table. 301*bc4097aaSchristosmy $dTable = 0; # Generate destination table. 302*bc4097aaSchristosmy @services = (); # Preload services tables. 303*bc4097aaSchristosmy $showFlags = 0; # Show TCP flag combinations. 304*bc4097aaSchristosmy %selectAddrs; # Limit report to these hosts. 305*bc4097aaSchristosmy %selectActs; # Limit report to these actions. 306*bc4097aaSchristos 307*bc4097aaSchristos# Parse argument list. 308*bc4097aaSchristoswhile (defined ($_ = shift)) 309*bc4097aaSchristos{ 310*bc4097aaSchristos if (s/^-//) 311*bc4097aaSchristos { 312*bc4097aaSchristos while (s/^([vnpSD\?hsAF])//) 313*bc4097aaSchristos { 314*bc4097aaSchristos my $flag = $1; 315*bc4097aaSchristos if ($flag eq 'v') 316*bc4097aaSchristos { 317*bc4097aaSchristos ++$verbosity; 318*bc4097aaSchristos } 319*bc4097aaSchristos elsif ($flag eq 'n') 320*bc4097aaSchristos { 321*bc4097aaSchristos $numeric = 1; 322*bc4097aaSchristos } 323*bc4097aaSchristos elsif ($flag eq 'p') 324*bc4097aaSchristos { 325*bc4097aaSchristos $paranoid = 1; 326*bc4097aaSchristos } 327*bc4097aaSchristos elsif ($flag eq 'S') 328*bc4097aaSchristos { 329*bc4097aaSchristos $sTable = 1; 330*bc4097aaSchristos } 331*bc4097aaSchristos elsif ($flag eq 'D') 332*bc4097aaSchristos { 333*bc4097aaSchristos $dTable = 1; 334*bc4097aaSchristos } 335*bc4097aaSchristos elsif ($flag eq 'F') 336*bc4097aaSchristos { 337*bc4097aaSchristos $showFlags = 1; 338*bc4097aaSchristos } 339*bc4097aaSchristos elsif (($flag eq '?') || ($flag eq 'h')) 340*bc4097aaSchristos { 341*bc4097aaSchristos &usage (0); 342*bc4097aaSchristos } 343*bc4097aaSchristos else 344*bc4097aaSchristos { 345*bc4097aaSchristos my $arg = shift; 346*bc4097aaSchristos defined ($arg) || &usage (1, qq{-$flag requires an argument}); 347*bc4097aaSchristos if ($flag eq 's') 348*bc4097aaSchristos { 349*bc4097aaSchristos push (@services, $arg); 350*bc4097aaSchristos } 351*bc4097aaSchristos elsif ($flag eq 'A') 352*bc4097aaSchristos { 353*bc4097aaSchristos my @acts = split (/,/, $arg); 354*bc4097aaSchristos my $a; 355*bc4097aaSchristos foreach $a (@acts) 356*bc4097aaSchristos { 357*bc4097aaSchristos my $aa; 358*bc4097aaSchristos my $match = 0; 359*bc4097aaSchristos foreach $aa (keys (%acts)) 360*bc4097aaSchristos { 361*bc4097aaSchristos if ($acts{$aa} eq $a) 362*bc4097aaSchristos { 363*bc4097aaSchristos ++$match; 364*bc4097aaSchristos $selectActs{$aa} = $a; 365*bc4097aaSchristos } 366*bc4097aaSchristos } 367*bc4097aaSchristos $match || &usage (1, qq{unknown action $a}); 368*bc4097aaSchristos } 369*bc4097aaSchristos } 370*bc4097aaSchristos } 371*bc4097aaSchristos } 372*bc4097aaSchristos 373*bc4097aaSchristos &usage (1, qq{unknown option: -$_}) if (length); 374*bc4097aaSchristos 375*bc4097aaSchristos next; 376*bc4097aaSchristos } 377*bc4097aaSchristos 378*bc4097aaSchristos # Add host to hash of hosts we're interested in. 379*bc4097aaSchristos (/^(.+)\/([\d+\.]+)$/) || (/^(.+)$/) || &usage (1, qq{invalid CIDR address $_}); 380*bc4097aaSchristos my ($addr, $mask) = ($1, $2); 381*bc4097aaSchristos my @addr = &hostAddrs ($addr); 382*bc4097aaSchristos (scalar (@addr)) || &usage (1, qq{cannot resolve hostname $_}); 383*bc4097aaSchristos if (!defined ($mask)) 384*bc4097aaSchristos { 385*bc4097aaSchristos $mask = (2 ** 32) - 1; 386*bc4097aaSchristos } 387*bc4097aaSchristos elsif (($mask =~ /^\d+$/) && ($mask <= 32)) 388*bc4097aaSchristos { 389*bc4097aaSchristos $mask = (2 ** 32) - 1 - ((2 ** (32 - $mask)) - 1); 390*bc4097aaSchristos } 391*bc4097aaSchristos elsif (defined ($mask = &isDottedAddr ($mask))) 392*bc4097aaSchristos { 393*bc4097aaSchristos $mask = &integerAddr ($mask); 394*bc4097aaSchristos } 395*bc4097aaSchristos else 396*bc4097aaSchristos { 397*bc4097aaSchristos &usage (1, qq{invalid CIDR address $_}); 398*bc4097aaSchristos } 399*bc4097aaSchristos foreach $addr (@addr) 400*bc4097aaSchristos { 401*bc4097aaSchristos # Save mask unless we already have a less specific one for this address. 402*bc4097aaSchristos my $a = &integerAddr ($addr) & $mask; 403*bc4097aaSchristos $selectAddrs{$a} = $mask unless (exists ($selectAddrs{$a}) && ($selectAddrs{$a} < $mask)); 404*bc4097aaSchristos } 405*bc4097aaSchristos} 406*bc4097aaSchristos 407*bc4097aaSchristos# Which tables will we generate? 408*bc4097aaSchristos$dTable = $sTable = 1 unless ($dTable || $sTable); 409*bc4097aaSchristosmy @dirs; 410*bc4097aaSchristospush (@dirs, 'd') if ($dTable); 411*bc4097aaSchristospush (@dirs, 's') if ($sTable); 412*bc4097aaSchristos 413*bc4097aaSchristos# Are we interested in specific hosts? 414*bc4097aaSchristosmy $selectAddrs = scalar (keys (%selectAddrs)); 415*bc4097aaSchristos 416*bc4097aaSchristos# Are we interested in specific actions? 417*bc4097aaSchristosif (scalar (keys (%selectActs)) == 0) 418*bc4097aaSchristos{ 419*bc4097aaSchristos %selectActs = %acts; 420*bc4097aaSchristos} 421*bc4097aaSchristos 422*bc4097aaSchristos# We use this hash to cache port name -> number and number -> name mappings. 423*bc4097aaSchristos# Isn't it cool that we can use the same hash for both? 424*bc4097aaSchristosmy %pn; 425*bc4097aaSchristos 426*bc4097aaSchristos# Preload any services maps. 427*bc4097aaSchristosmy $sm; 428*bc4097aaSchristosforeach $sm (@services) 429*bc4097aaSchristos{ 430*bc4097aaSchristos my $sf = new IO::File ($sm, "r"); 431*bc4097aaSchristos defined ($sf) || &quit (1, qq{cannot open services file $sm}); 432*bc4097aaSchristos 433*bc4097aaSchristos while (defined ($_ = $sf->getline ())) 434*bc4097aaSchristos { 435*bc4097aaSchristos my $text = $_; 436*bc4097aaSchristos chomp; 437*bc4097aaSchristos s/#.*$//; 438*bc4097aaSchristos s/\s+$//; 439*bc4097aaSchristos next unless (length); 440*bc4097aaSchristos my ($name, $spec, @aliases) = split (/\s+/); 441*bc4097aaSchristos ($spec =~ /^([\w\-]+)\/([\w\-]+)$/) 442*bc4097aaSchristos || &quit (1, qq{$sm:$.: invalid definition: $text}); 443*bc4097aaSchristos my ($pnum, $proto) = ($1, $2); 444*bc4097aaSchristos 445*bc4097aaSchristos # Enter service definition in pn hash both forwards and backwards. 446*bc4097aaSchristos my $port; 447*bc4097aaSchristos my $pname; 448*bc4097aaSchristos foreach $port ($name, @aliases) 449*bc4097aaSchristos { 450*bc4097aaSchristos $pname = "$pnum/$proto"; 451*bc4097aaSchristos $pn{$pname} = $port; 452*bc4097aaSchristos } 453*bc4097aaSchristos $pname = "$name/$proto"; 454*bc4097aaSchristos $pn{$pname} = $pnum; 455*bc4097aaSchristos } 456*bc4097aaSchristos 457*bc4097aaSchristos $sf->close (); 458*bc4097aaSchristos} 459*bc4097aaSchristos 460*bc4097aaSchristos# Cache for host name -> addr mappings. 461*bc4097aaSchristosmy %ipAddr; 462*bc4097aaSchristos 463*bc4097aaSchristos# Cache for host addr -> name mappings. 464*bc4097aaSchristosmy %ipName; 465*bc4097aaSchristos 466*bc4097aaSchristos# Hash for protocol number <--> name mappings. 467*bc4097aaSchristosmy %pr; 468*bc4097aaSchristos 469*bc4097aaSchristos# Under IPv4 port numbers are unsigned shorts. The value below is higher 470*bc4097aaSchristos# than the maximum value of an unsigned short, and is used in place of 471*bc4097aaSchristos# high port numbers that don't correspond to known services. This makes 472*bc4097aaSchristos# high ports get sorted behind all others. 473*bc4097aaSchristosmy $highPort = 0x10000; 474*bc4097aaSchristos 475*bc4097aaSchristoswhile (<STDIN>) 476*bc4097aaSchristos{ 477*bc4097aaSchristos chomp; 478*bc4097aaSchristos 479*bc4097aaSchristos # For ipmon output that came through syslog, we'll have an asctime 480*bc4097aaSchristos # timestamp, an optional severity code (IRIX), the hostname, 481*bc4097aaSchristos # "ipmon"[process id]: prefixed to the line. For output that was 482*bc4097aaSchristos # written directly to a file by ipmon, we'll have a date prefix as 483*bc4097aaSchristos # dd/mm/yyyy (no y2k problem here!). Both formats then have a packet 484*bc4097aaSchristos # timestamp and the log info. 485*bc4097aaSchristos my ($log); 486*bc4097aaSchristos if (s/^\w+\s+\d+\s+\d+:\d+:\d+\s+(?:\d\w:)?[\w\.\-]+\s+\S*ipmon\[\d+\]:\s+(?:\[ID\s+\d+\s+[\w\.]+\]\s+)?\d+:\d+:\d+\.\d+\s+//) 487*bc4097aaSchristos { 488*bc4097aaSchristos $log = $_; 489*bc4097aaSchristos } 490*bc4097aaSchristos elsif (s/^(?:\d+\/\d+\/\d+)\s+(?:\d+:\d+:\d+\.\d+)\s+//) 491*bc4097aaSchristos { 492*bc4097aaSchristos $log = $_; 493*bc4097aaSchristos } 494*bc4097aaSchristos else 495*bc4097aaSchristos { 496*bc4097aaSchristos # It don't look like no ipmon output to me, baby. 497*bc4097aaSchristos next; 498*bc4097aaSchristos } 499*bc4097aaSchristos next unless (defined ($log)); 500*bc4097aaSchristos 501*bc4097aaSchristos print STDERR "$log\n" if ($verbosity); 502*bc4097aaSchristos 503*bc4097aaSchristos # Parse the log line. We're expecting interface name, rule group and 504*bc4097aaSchristos # number, an action code, a source host name or IP with possible port 505*bc4097aaSchristos # name or number, a destination host name or IP with possible port 506*bc4097aaSchristos # number, "PR", a protocol name or number, "len", a header length, a 507*bc4097aaSchristos # packet length (which will be in parentheses for protocols other than 508*bc4097aaSchristos # TCP, UDP, or ICMP), and maybe some additional info. 509*bc4097aaSchristos my @fields = ($log =~ /^(?:(\d+)x)?\s*(\w+)\s+@(\d+):(\d+)\s+(\w)\s+([\w\-\.,]+)\s+->\s+([\w\-\.,]+)\s+PR\s+(\w+)\s+len\s+(\d+)\s+\(?(\d+)\)?\s*(.*)$/ox); 510*bc4097aaSchristos unless (scalar (@fields)) 511*bc4097aaSchristos { 512*bc4097aaSchristos print STDERR "$me:$.: cannot parse: $_\n"; 513*bc4097aaSchristos next; 514*bc4097aaSchristos } 515*bc4097aaSchristos my ($count, $if, $group, $rule, $act, $src, $dest, $proto, $hlen, $len, $more) = @fields; 516*bc4097aaSchristos 517*bc4097aaSchristos # Skip actions we're not interested in. 518*bc4097aaSchristos next unless (exists ($selectActs{$act})); 519*bc4097aaSchristos 520*bc4097aaSchristos # Packet count defaults to 1. 521*bc4097aaSchristos $count = 1 unless (defined ($count)); 522*bc4097aaSchristos 523*bc4097aaSchristos my ($sport, $dport, @flags); 524*bc4097aaSchristos 525*bc4097aaSchristos if ($proto eq 'icmp') 526*bc4097aaSchristos { 527*bc4097aaSchristos if ($more =~ s/^icmp (\d+)\/(\d+)\s*//) 528*bc4097aaSchristos { 529*bc4097aaSchristos # We save icmp type and code in both sport and dport. This 530*bc4097aaSchristos # allows us to sort icmp packets using the normal port-sorting 531*bc4097aaSchristos # code. 532*bc4097aaSchristos $dport = $sport = "$1.$2"; 533*bc4097aaSchristos } 534*bc4097aaSchristos else 535*bc4097aaSchristos { 536*bc4097aaSchristos $sport = ''; 537*bc4097aaSchristos $dport = ''; 538*bc4097aaSchristos } 539*bc4097aaSchristos } 540*bc4097aaSchristos else 541*bc4097aaSchristos { 542*bc4097aaSchristos if ($showFlags) 543*bc4097aaSchristos { 544*bc4097aaSchristos if (($proto eq 'tcp') && ($more =~ s/^\-([A-Z]+)\s*//)) 545*bc4097aaSchristos { 546*bc4097aaSchristos push (@flags, $1); 547*bc4097aaSchristos } 548*bc4097aaSchristos if ($more =~ s/^K\-S\s*//) 549*bc4097aaSchristos { 550*bc4097aaSchristos push (@flags, 'state'); 551*bc4097aaSchristos } 552*bc4097aaSchristos } 553*bc4097aaSchristos if ($src =~ s/,([\-\w]+)$//) 554*bc4097aaSchristos { 555*bc4097aaSchristos $sport = &portSimplify ($1, $proto); 556*bc4097aaSchristos } 557*bc4097aaSchristos else 558*bc4097aaSchristos { 559*bc4097aaSchristos $sport = ''; 560*bc4097aaSchristos } 561*bc4097aaSchristos if ($dest =~ s/,([\-\w]+)$//) 562*bc4097aaSchristos { 563*bc4097aaSchristos $dport = &portSimplify ($1, $proto); 564*bc4097aaSchristos } 565*bc4097aaSchristos else 566*bc4097aaSchristos { 567*bc4097aaSchristos $dport = ''; 568*bc4097aaSchristos } 569*bc4097aaSchristos } 570*bc4097aaSchristos 571*bc4097aaSchristos # Make sure addresses are numeric at this point. We want to sort by 572*bc4097aaSchristos # IP address later. If the hostname doesn't resolve, punt. If you 573*bc4097aaSchristos # must use ipmon -n, be ready for weirdness. Use only the first 574*bc4097aaSchristos # address returned. 575*bc4097aaSchristos my $x; 576*bc4097aaSchristos $x = (&hostAddrs ($src))[0]; 577*bc4097aaSchristos unless (defined ($x)) 578*bc4097aaSchristos { 579*bc4097aaSchristos print STDERR "$me:$.: cannot resolve hostname $src\n"; 580*bc4097aaSchristos next; 581*bc4097aaSchristos } 582*bc4097aaSchristos $src = $x; 583*bc4097aaSchristos $x = (&hostAddrs ($dest))[0]; 584*bc4097aaSchristos unless (defined ($x)) 585*bc4097aaSchristos { 586*bc4097aaSchristos print STDERR "$me:$.: cannot resolve hostname $dest\n"; 587*bc4097aaSchristos next; 588*bc4097aaSchristos } 589*bc4097aaSchristos $dest = $x; 590*bc4097aaSchristos 591*bc4097aaSchristos # Skip hosts we're not interested in. 592*bc4097aaSchristos if ($selectAddrs) 593*bc4097aaSchristos { 594*bc4097aaSchristos my ($a, $m); 595*bc4097aaSchristos my $s = &integerAddr ($src); 596*bc4097aaSchristos my $d = &integerAddr ($dest); 597*bc4097aaSchristos my $cute = 0; 598*bc4097aaSchristos while (($a, $m) = each (%selectAddrs)) 599*bc4097aaSchristos { 600*bc4097aaSchristos if ((($s & $m) == $a) || (($d & $m) == $a)) 601*bc4097aaSchristos { 602*bc4097aaSchristos $cute = 1; 603*bc4097aaSchristos last; 604*bc4097aaSchristos } 605*bc4097aaSchristos } 606*bc4097aaSchristos next unless ($cute); 607*bc4097aaSchristos } 608*bc4097aaSchristos 609*bc4097aaSchristos # Convert proto to proto number. 610*bc4097aaSchristos $proto = &protoNumber ($proto); 611*bc4097aaSchristos 612*bc4097aaSchristos sub countPacket 613*bc4097aaSchristos { 614*bc4097aaSchristos my ($host, $dir, $peer, $proto, $count, $packet, @flags) = @_; 615*bc4097aaSchristos 616*bc4097aaSchristos # Make sure host is in the hosts hash. 617*bc4097aaSchristos $hosts{$host} = 618*bc4097aaSchristos +{ 619*bc4097aaSchristos 'd' => +{ }, 620*bc4097aaSchristos 's' => +{ }, 621*bc4097aaSchristos } unless (exists ($hosts{$host})); 622*bc4097aaSchristos 623*bc4097aaSchristos # Get the source/destination traffic hash for the host in question. 624*bc4097aaSchristos my $trafficHash = $hosts{$host}->{$dir}; 625*bc4097aaSchristos 626*bc4097aaSchristos # Make sure there's a hash for the peer. 627*bc4097aaSchristos $trafficHash->{$peer} = +{ } unless (exists ($trafficHash->{$peer})); 628*bc4097aaSchristos 629*bc4097aaSchristos # Make sure the peer hash has a hash for the protocol number. 630*bc4097aaSchristos my $peerHash = $trafficHash->{$peer}; 631*bc4097aaSchristos $peerHash->{$proto} = +{ } unless (exists ($peerHash->{$proto})); 632*bc4097aaSchristos 633*bc4097aaSchristos # Make sure there's a counter for this packet type in the proto hash. 634*bc4097aaSchristos my $protoHash = $peerHash->{$proto}; 635*bc4097aaSchristos $protoHash->{$packet} = +{ '' => 0 } unless (exists ($protoHash->{$packet})); 636*bc4097aaSchristos 637*bc4097aaSchristos # Increment the counter and mark flags. 638*bc4097aaSchristos my $packetHash = $protoHash->{$packet}; 639*bc4097aaSchristos $packetHash->{''} += $count; 640*bc4097aaSchristos map { $packetHash->{$_} = undef; } (@flags); 641*bc4097aaSchristos } 642*bc4097aaSchristos 643*bc4097aaSchristos # Count the packet as outgoing traffic from the source address. 644*bc4097aaSchristos &countPacket ($src, 's', $dest, $proto, $count, "$sport:$dport:$if:$act", @flags) if ($sTable); 645*bc4097aaSchristos 646*bc4097aaSchristos # Count the packet as incoming traffic to the destination address. 647*bc4097aaSchristos &countPacket ($dest, 'd', $src, $proto, $count, "$dport:$sport:$if:$act", @flags) if ($dTable); 648*bc4097aaSchristos} 649*bc4097aaSchristos 650*bc4097aaSchristosmy $dir; 651*bc4097aaSchristosforeach $dir (@dirs) 652*bc4097aaSchristos{ 653*bc4097aaSchristos my $order = ($dir eq 's' ? 'source' : 'destination'); 654*bc4097aaSchristos my $arrow = ($dir eq 's' ? '->' : '<-'); 655*bc4097aaSchristos 656*bc4097aaSchristos print "###\n"; 657*bc4097aaSchristos print "### Traffic by $order address:\n"; 658*bc4097aaSchristos print "###\n"; 659*bc4097aaSchristos 660*bc4097aaSchristos sub ipSort 661*bc4097aaSchristos { 662*bc4097aaSchristos &integerAddr ($a) <=> &integerAddr ($b); 663*bc4097aaSchristos } 664*bc4097aaSchristos 665*bc4097aaSchristos sub packetSort 666*bc4097aaSchristos { 667*bc4097aaSchristos my ($asport, $adport, $aif, $aact) = split (/:/, $a); 668*bc4097aaSchristos my ($bsport, $bdport, $bif, $bact) = split (/:/, $b); 669*bc4097aaSchristos $bact cmp $aact || $aif cmp $bif || $asport <=> $bsport || $adport <=> $bdport; 670*bc4097aaSchristos } 671*bc4097aaSchristos 672*bc4097aaSchristos my $host; 673*bc4097aaSchristos foreach $host (sort ipSort (keys %hosts)) 674*bc4097aaSchristos { 675*bc4097aaSchristos my $traffic = $hosts{$host}->{$dir}; 676*bc4097aaSchristos 677*bc4097aaSchristos # Skip hosts with no traffic. 678*bc4097aaSchristos next unless (scalar (keys (%{$traffic}))); 679*bc4097aaSchristos 680*bc4097aaSchristos if ($numeric) 681*bc4097aaSchristos { 682*bc4097aaSchristos print &dottedAddr ($host), "\n"; 683*bc4097aaSchristos } 684*bc4097aaSchristos else 685*bc4097aaSchristos { 686*bc4097aaSchristos print &hostName ($host), " \[", &dottedAddr ($host), "\]\n"; 687*bc4097aaSchristos } 688*bc4097aaSchristos 689*bc4097aaSchristos my $peer; 690*bc4097aaSchristos foreach $peer (sort ipSort (keys %{$traffic})) 691*bc4097aaSchristos { 692*bc4097aaSchristos my $peerHash = $traffic->{$peer}; 693*bc4097aaSchristos my $peerName = ($numeric ? &dottedAddr ($peer) : &hostName ($peer)); 694*bc4097aaSchristos my $proto; 695*bc4097aaSchristos foreach $proto (sort (keys (%{$peerHash}))) 696*bc4097aaSchristos { 697*bc4097aaSchristos my $protoHash = $peerHash->{$proto}; 698*bc4097aaSchristos my $protoName = &protoName ($proto); 699*bc4097aaSchristos 700*bc4097aaSchristos my $packet; 701*bc4097aaSchristos foreach $packet (sort packetSort (keys %{$protoHash})) 702*bc4097aaSchristos { 703*bc4097aaSchristos my ($sport, $dport, $if, $act) = split (/:/, $packet); 704*bc4097aaSchristos my $packetHash = $protoHash->{$packet}; 705*bc4097aaSchristos my $count = $packetHash->{''}; 706*bc4097aaSchristos $act = '?' unless (defined ($act = $acts{$act})); 707*bc4097aaSchristos if (($protoName eq 'tcp') || ($protoName eq 'udp')) 708*bc4097aaSchristos { 709*bc4097aaSchristos printf (" %-6s %7s %4d %4s %16s %2s %s.%s", $if, $act, $count, $protoName, &portName ($sport, $protoName), $arrow, $peerName, &portName ($dport, $protoName)); 710*bc4097aaSchristos } 711*bc4097aaSchristos elsif ($protoName eq 'icmp') 712*bc4097aaSchristos { 713*bc4097aaSchristos printf (" %-6s %7s %4d %4s %16s %2s %s", $if, $act, $count, $protoName, &icmpType ($sport), $arrow, $peerName); 714*bc4097aaSchristos } 715*bc4097aaSchristos else 716*bc4097aaSchristos { 717*bc4097aaSchristos printf (" %-6s %7s %4d %4s %16s %2s %s", $if, $act, $count, $protoName, '', $arrow, $peerName); 718*bc4097aaSchristos } 719*bc4097aaSchristos if ($showFlags) 720*bc4097aaSchristos { 721*bc4097aaSchristos my @flags = sort (keys (%{$packetHash})); 722*bc4097aaSchristos if (scalar (@flags)) 723*bc4097aaSchristos { 724*bc4097aaSchristos shift (@flags); 725*bc4097aaSchristos print ' (', join (',', @flags), ')' if (scalar (@flags)); 726*bc4097aaSchristos } 727*bc4097aaSchristos } 728*bc4097aaSchristos print "\n"; 729*bc4097aaSchristos } 730*bc4097aaSchristos } 731*bc4097aaSchristos } 732*bc4097aaSchristos } 733*bc4097aaSchristos 734*bc4097aaSchristos print "\n"; 735*bc4097aaSchristos} 736*bc4097aaSchristos 737*bc4097aaSchristosexit (0); 738*bc4097aaSchristos 739*bc4097aaSchristos# Translates a numeric port/named protocol to a port name. Reserved ports 740*bc4097aaSchristos# that do not have an entry in the services database are left numeric. High 741*bc4097aaSchristos# ports that do not have an entry in the services database are mapped 742*bc4097aaSchristos# to '<high>'. 743*bc4097aaSchristossub portName 744*bc4097aaSchristos{ 745*bc4097aaSchristos my $port = shift; 746*bc4097aaSchristos my $proto = shift; 747*bc4097aaSchristos my $pname = "$port/$proto"; 748*bc4097aaSchristos unless (exists ($pn{$pname})) 749*bc4097aaSchristos { 750*bc4097aaSchristos my $name = getservbyport ($port, $proto); 751*bc4097aaSchristos $pn{$pname} = (defined ($name) ? $name : ($port <= 1023 ? $port : '<high>')); 752*bc4097aaSchristos } 753*bc4097aaSchristos return $pn{$pname}; 754*bc4097aaSchristos} 755*bc4097aaSchristos 756*bc4097aaSchristos# Translates a named port/protocol to a port number. 757*bc4097aaSchristossub portNumber 758*bc4097aaSchristos{ 759*bc4097aaSchristos my $port = shift; 760*bc4097aaSchristos my $proto = shift; 761*bc4097aaSchristos my $pname = "$port/$proto"; 762*bc4097aaSchristos unless (exists ($pn{$pname})) 763*bc4097aaSchristos { 764*bc4097aaSchristos my $number = getservbyname ($port, $proto); 765*bc4097aaSchristos unless (defined ($number)) 766*bc4097aaSchristos { 767*bc4097aaSchristos # I don't think we need to recover from this. How did the port 768*bc4097aaSchristos # name get into the log file if we can't find it? Log file from 769*bc4097aaSchristos # a different machine? Fix /etc/services on this one if that's 770*bc4097aaSchristos # your problem. 771*bc4097aaSchristos die ("Unrecognized port name \"$port\" at $."); 772*bc4097aaSchristos } 773*bc4097aaSchristos $pn{$pname} = $number; 774*bc4097aaSchristos } 775*bc4097aaSchristos return $pn{$pname}; 776*bc4097aaSchristos} 777*bc4097aaSchristos 778*bc4097aaSchristos# Convert all unrecognized high ports to the same value so they are treated 779*bc4097aaSchristos# identically. The protocol should be by name. 780*bc4097aaSchristossub portSimplify 781*bc4097aaSchristos{ 782*bc4097aaSchristos my $port = shift; 783*bc4097aaSchristos my $proto = shift; 784*bc4097aaSchristos 785*bc4097aaSchristos # Make sure port is numeric. 786*bc4097aaSchristos $port = &portNumber ($port, $proto) 787*bc4097aaSchristos unless ($port =~ /^\d+$/); 788*bc4097aaSchristos 789*bc4097aaSchristos # Look up port name. 790*bc4097aaSchristos my $portName = &portName ($port, $proto); 791*bc4097aaSchristos 792*bc4097aaSchristos # Port is an unknown high port. Return a value that is too high for a 793*bc4097aaSchristos # port number, so that high ports get sorted last. 794*bc4097aaSchristos return $highPort if ($portName eq '<high>'); 795*bc4097aaSchristos 796*bc4097aaSchristos # Return original port number. 797*bc4097aaSchristos return $port; 798*bc4097aaSchristos} 799*bc4097aaSchristos 800*bc4097aaSchristos# Translates a numeric address into a hostname. Pass only packed numeric 801*bc4097aaSchristos# addresses to this routine. 802*bc4097aaSchristossub hostName 803*bc4097aaSchristos{ 804*bc4097aaSchristos my $ip = shift; 805*bc4097aaSchristos return $ipName{$ip} if (exists ($ipName{$ip})); 806*bc4097aaSchristos 807*bc4097aaSchristos # Do an inverse lookup on the address. 808*bc4097aaSchristos my $name = gethostbyaddr ($ip, AF_INET); 809*bc4097aaSchristos unless (defined ($name)) 810*bc4097aaSchristos { 811*bc4097aaSchristos # Inverse lookup failed, so map the IP address to its dotted 812*bc4097aaSchristos # representation and cache that. 813*bc4097aaSchristos $ipName{$ip} = &dottedAddr ($ip); 814*bc4097aaSchristos return $ipName{$ip}; 815*bc4097aaSchristos } 816*bc4097aaSchristos 817*bc4097aaSchristos # For paranoid hostname lookups. 818*bc4097aaSchristos if ($paranoid) 819*bc4097aaSchristos { 820*bc4097aaSchristos # If this address already matches, we're happy. 821*bc4097aaSchristos unless (exists ($ipName{$ip}) && (lc ($ipName{$ip}) eq lc ($name))) 822*bc4097aaSchristos { 823*bc4097aaSchristos # Do a forward lookup on the resulting name. 824*bc4097aaSchristos my @addr = &hostAddrs ($name); 825*bc4097aaSchristos my $match = 0; 826*bc4097aaSchristos 827*bc4097aaSchristos # Cache the forward lookup results for future inverse lookups, 828*bc4097aaSchristos # but don't stomp on inverses we've already cached, even if they 829*bc4097aaSchristos # are questionable. We want to generate consistent output, and 830*bc4097aaSchristos # the cache is growing incrementally. 831*bc4097aaSchristos foreach (@addr) 832*bc4097aaSchristos { 833*bc4097aaSchristos $ipName{$_} = $name unless (exists ($ipName{$_})); 834*bc4097aaSchristos $match = 1 if ($_ eq $ip); 835*bc4097aaSchristos } 836*bc4097aaSchristos 837*bc4097aaSchristos # Was this one of the addresses? If not, tack on a ?. 838*bc4097aaSchristos $name .= '?' unless ($match); 839*bc4097aaSchristos } 840*bc4097aaSchristos } 841*bc4097aaSchristos else 842*bc4097aaSchristos { 843*bc4097aaSchristos # Just believe it and cache it. 844*bc4097aaSchristos $ipName{$ip} = $name; 845*bc4097aaSchristos } 846*bc4097aaSchristos 847*bc4097aaSchristos return $name; 848*bc4097aaSchristos} 849*bc4097aaSchristos 850*bc4097aaSchristos# Translates a hostname or dotted address into a list of packed numeric 851*bc4097aaSchristos# addresses. 852*bc4097aaSchristossub hostAddrs 853*bc4097aaSchristos{ 854*bc4097aaSchristos my $name = shift; 855*bc4097aaSchristos my $ip; 856*bc4097aaSchristos 857*bc4097aaSchristos # Check if it's a dotted representation. 858*bc4097aaSchristos return ($ip) if (defined ($ip = &isDottedAddr ($name))); 859*bc4097aaSchristos 860*bc4097aaSchristos # Return result from cache. 861*bc4097aaSchristos $name = lc ($name); 862*bc4097aaSchristos return @{$ipAddr{$name}} if (exists ($ipAddr{$name})); 863*bc4097aaSchristos 864*bc4097aaSchristos # Look up the addresses. 865*bc4097aaSchristos my @addr = gethostbyname ($name); 866*bc4097aaSchristos splice (@addr, 0, 4); 867*bc4097aaSchristos 868*bc4097aaSchristos unless (scalar (@addr)) 869*bc4097aaSchristos { 870*bc4097aaSchristos # Again, I don't think we need to recover from this gracefully. 871*bc4097aaSchristos # If we can't resolve a hostname that ended up in the log file, 872*bc4097aaSchristos # punt. We want to be able to sort hosts by IP address later, 873*bc4097aaSchristos # and letting hostnames through will snarl up that code. Users 874*bc4097aaSchristos # of ipmon -n will have to grin and bear it for now. The 875*bc4097aaSchristos # functions that get undef back should treat it as an error or 876*bc4097aaSchristos # as some default address, e.g. 0 just to make things work. 877*bc4097aaSchristos return (); 878*bc4097aaSchristos } 879*bc4097aaSchristos 880*bc4097aaSchristos $ipAddr{$name} = [ @addr ]; 881*bc4097aaSchristos return @{$ipAddr{$name}}; 882*bc4097aaSchristos} 883*bc4097aaSchristos 884*bc4097aaSchristos# If the argument is a valid dotted address, returns the corresponding 885*bc4097aaSchristos# packed numeric address, otherwise returns undef. 886*bc4097aaSchristossub isDottedAddr 887*bc4097aaSchristos{ 888*bc4097aaSchristos my $addr = shift; 889*bc4097aaSchristos if ($addr =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) 890*bc4097aaSchristos { 891*bc4097aaSchristos my @a = (int ($1), int ($2), int ($3), int ($4)); 892*bc4097aaSchristos foreach (@a) 893*bc4097aaSchristos { 894*bc4097aaSchristos return undef if ($_ >= 256); 895*bc4097aaSchristos } 896*bc4097aaSchristos return pack ('C*', @a); 897*bc4097aaSchristos } 898*bc4097aaSchristos return undef; 899*bc4097aaSchristos} 900*bc4097aaSchristos 901*bc4097aaSchristos# Unpacks a packed numeric address and returns an integer representation. 902*bc4097aaSchristossub integerAddr 903*bc4097aaSchristos{ 904*bc4097aaSchristos my $addr = shift; 905*bc4097aaSchristos return unpack ('N', $addr); 906*bc4097aaSchristos 907*bc4097aaSchristos # The following is for generalized IPv4/IPv6 stuff. For now, it's a 908*bc4097aaSchristos # lot faster to assume IPv4. 909*bc4097aaSchristos my @a = unpack ('C*', $addr); 910*bc4097aaSchristos my $a = 0; 911*bc4097aaSchristos while (scalar (@a)) 912*bc4097aaSchristos { 913*bc4097aaSchristos $a = ($a << 8) | shift (@a); 914*bc4097aaSchristos } 915*bc4097aaSchristos return $a; 916*bc4097aaSchristos} 917*bc4097aaSchristos 918*bc4097aaSchristos# Unpacks a packed numeric address into a dotted representation. 919*bc4097aaSchristossub dottedAddr 920*bc4097aaSchristos{ 921*bc4097aaSchristos my $addr = shift; 922*bc4097aaSchristos my @a = unpack ('C*', $addr); 923*bc4097aaSchristos return join ('.', @a); 924*bc4097aaSchristos} 925*bc4097aaSchristos 926*bc4097aaSchristos# Translates a protocol number into a protocol name, or a number if no name 927*bc4097aaSchristos# is found in the protocol database. 928*bc4097aaSchristossub protoName 929*bc4097aaSchristos{ 930*bc4097aaSchristos my $code = shift; 931*bc4097aaSchristos return $code if ($code !~ /^\d+$/); 932*bc4097aaSchristos unless (exists ($pr{$code})) 933*bc4097aaSchristos { 934*bc4097aaSchristos my $name = scalar (getprotobynumber ($code)); 935*bc4097aaSchristos if (defined ($name)) 936*bc4097aaSchristos { 937*bc4097aaSchristos $pr{$code} = $name; 938*bc4097aaSchristos } 939*bc4097aaSchristos else 940*bc4097aaSchristos { 941*bc4097aaSchristos $pr{$code} = $code; 942*bc4097aaSchristos } 943*bc4097aaSchristos } 944*bc4097aaSchristos return $pr{$code}; 945*bc4097aaSchristos} 946*bc4097aaSchristos 947*bc4097aaSchristos# Translates a protocol name or number into a protocol number. 948*bc4097aaSchristossub protoNumber 949*bc4097aaSchristos{ 950*bc4097aaSchristos my $name = shift; 951*bc4097aaSchristos return $name if ($name =~ /^\d+$/); 952*bc4097aaSchristos unless (exists ($pr{$name})) 953*bc4097aaSchristos { 954*bc4097aaSchristos my $code = scalar (getprotobyname ($name)); 955*bc4097aaSchristos if (defined ($code)) 956*bc4097aaSchristos { 957*bc4097aaSchristos $pr{$name} = $code; 958*bc4097aaSchristos } 959*bc4097aaSchristos else 960*bc4097aaSchristos { 961*bc4097aaSchristos $pr{$name} = $name; 962*bc4097aaSchristos } 963*bc4097aaSchristos } 964*bc4097aaSchristos return $pr{$name}; 965*bc4097aaSchristos} 966*bc4097aaSchristos 967*bc4097aaSchristossub icmpType 968*bc4097aaSchristos{ 969*bc4097aaSchristos my $typeCode = shift; 970*bc4097aaSchristos my ($type, $code) = split ('\.', $typeCode); 971*bc4097aaSchristos 972*bc4097aaSchristos return "?" unless (defined ($code)); 973*bc4097aaSchristos 974*bc4097aaSchristos my $info = $icmpTypeMap{$type}; 975*bc4097aaSchristos 976*bc4097aaSchristos return "\(type=$type/$code?\)" unless (defined ($info)); 977*bc4097aaSchristos 978*bc4097aaSchristos my $typeName = $info->{name}; 979*bc4097aaSchristos my $codeName; 980*bc4097aaSchristos if (exists ($info->{codes}->{$code})) 981*bc4097aaSchristos { 982*bc4097aaSchristos $codeName = $info->{codes}->{$code}; 983*bc4097aaSchristos $codeName = (defined ($codeName) ? "/$codeName" : ''); 984*bc4097aaSchristos } 985*bc4097aaSchristos else 986*bc4097aaSchristos { 987*bc4097aaSchristos $codeName = "/$code"; 988*bc4097aaSchristos } 989*bc4097aaSchristos return "$typeName$codeName"; 990*bc4097aaSchristos} 991*bc4097aaSchristos 992*bc4097aaSchristossub quit 993*bc4097aaSchristos{ 994*bc4097aaSchristos my $ec = shift; 995*bc4097aaSchristos my $msg = shift; 996*bc4097aaSchristos 997*bc4097aaSchristos print STDERR "$me: $msg\n"; 998*bc4097aaSchristos exit ($ec); 999*bc4097aaSchristos} 1000*bc4097aaSchristos 1001*bc4097aaSchristossub usage 1002*bc4097aaSchristos{ 1003*bc4097aaSchristos my $ec = shift; 1004*bc4097aaSchristos my @msg = @_; 1005*bc4097aaSchristos 1006*bc4097aaSchristos if (scalar (@msg)) 1007*bc4097aaSchristos { 1008*bc4097aaSchristos print STDERR "$me: ", join ("\n", @msg), "\n\n"; 1009*bc4097aaSchristos } 1010*bc4097aaSchristos 1011*bc4097aaSchristos print <<EOT; 1012*bc4097aaSchristosusage: $me [-nSDF] [-s servicemap] [-A act1,...] [address...] 1013*bc4097aaSchristos 1014*bc4097aaSchristosParses logging from ipmon and presents it in a comprehensible format. This 1015*bc4097aaSchristosprogram generates two reports: one organized by source address and another 1016*bc4097aaSchristosorganized by destination address. For the first report, source addresses are 1017*bc4097aaSchristossorted by IP address. For each address, all packets originating at the address 1018*bc4097aaSchristosare presented in a tabular form, where all packets with the same source and 1019*bc4097aaSchristosdestination address and port are counted as a single entry. Any port number 1020*bc4097aaSchristosgreater than 1023 that does not match an entry in the services table is treated 1021*bc4097aaSchristosas a "high" port; all high ports are coalesced into the same entry. The fields 1022*bc4097aaSchristosfor the source address report are: 1023*bc4097aaSchristos iface action packet-count proto src-port dest-host.dest-port \[\(flags\)\] 1024*bc4097aaSchristosThe fields for the destination address report are: 1025*bc4097aaSchristos iface action packet-count proto dest-port src-host.src-port \[\(flags\)\] 1026*bc4097aaSchristos 1027*bc4097aaSchristosOptions are: 1028*bc4097aaSchristos-n Disable hostname lookups, and report only IP addresses. 1029*bc4097aaSchristos-p Perform paranoid hostname lookups. 1030*bc4097aaSchristos-S Generate a source address report. 1031*bc4097aaSchristos-D Generate a destination address report. 1032*bc4097aaSchristos-F Show all flag combinations associated with packets. 1033*bc4097aaSchristos-s map Supply an alternate services map to be preloaded. The map should 1034*bc4097aaSchristos be in the same format as /etc/services. Any service name not found 1035*bc4097aaSchristos in the map will be looked for in the system services file. 1036*bc4097aaSchristos-A act1,... Limit the report to the specified actions. The possible actions 1037*bc4097aaSchristos are pass, block, log, short, and nomatch. 1038*bc4097aaSchristos 1039*bc4097aaSchristosIf any addresses are supplied on the command line, the report is limited to 1040*bc4097aaSchristosthese hosts. Addresses may be given as dotted IP addresses or hostnames, and 1041*bc4097aaSchristosmay be qualified with netmasks in CIDR \(/24\) or dotted \(/255.255.255.0\) format. 1042*bc4097aaSchristosIf a hostname resolves to multiple addresses, all addresses are used. 1043*bc4097aaSchristos 1044*bc4097aaSchristosIf neither -S nor -D is given, both reports are generated. 1045*bc4097aaSchristos 1046*bc4097aaSchristosNote: if you are logging traffic with ipmon -n, ipmon will already have looked 1047*bc4097aaSchristosup and logged addresses as hostnames where possible. This has an important side 1048*bc4097aaSchristoseffect: this program will translate the hostnames back into IP addresses which 1049*bc4097aaSchristosmay not match the original addresses of the logged packets because of numerous 1050*bc4097aaSchristosDNS issues. If you care about where packets are really coming from, you simply 1051*bc4097aaSchristoscannot rely on ipmon -n. An attacker with control of his reverse DNS can map 1052*bc4097aaSchristosthe reverse lookup to anything he likes. If you haven't logged the numeric IP 1053*bc4097aaSchristosaddress, there's no way to discover the source of an attack reliably. For this 1054*bc4097aaSchristosreason, I strongly recommend that you run ipmon without the -n option, and use 1055*bc4097aaSchristosthis or a similar script to do reverse lookups during analysis, rather than 1056*bc4097aaSchristosduring logging. 1057*bc4097aaSchristosEOT 1058*bc4097aaSchristos 1059*bc4097aaSchristos exit ($ec); 1060*bc4097aaSchristos} 1061*bc4097aaSchristos 1062