xref: /netbsd-src/external/bsd/ipf/dist/perl/plog (revision bc4097aacfdd9307c19b7947c13c6ad6982527a9)
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