summaryrefslogtreecommitdiffstats
path: root/contrib/ipfilter/perl/plog
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/ipfilter/perl/plog')
-rw-r--r--contrib/ipfilter/perl/plog653
1 files changed, 653 insertions, 0 deletions
diff --git a/contrib/ipfilter/perl/plog b/contrib/ipfilter/perl/plog
new file mode 100644
index 0000000..8f3f73c
--- /dev/null
+++ b/contrib/ipfilter/perl/plog
@@ -0,0 +1,653 @@
+#!/usr/bin/perl -wT
+#
+# Author: Jefferson Ogata <jogata@nodc.noaa.gov>
+# Date: 1998/11/01
+# Version: 0.4
+#
+# Please feel free to use or redistribute this program if you find it useful.
+# If you have suggestions, or even better, bits of new code, send them to me
+# and I will add them when I have time. The current version of this script
+# can always be found at the URL:
+#
+# http://pobox.com/~ogata/webtools/plog.txt
+#
+# Parse ipmon output into a coherent form. This program only handles the
+# lines regarding filter actions. It does not parse nat and state lines.
+#
+# Present lines from ipmon to this program on standard input. One way I
+# often use is:
+# grep ' b ' logfile | plog
+# since a ' b ' sequence indicates a blocked packet.
+#
+# TODO:
+# - Handle output from ipmon -v.
+# - Handle timestamps from other locales. Anyone with a timestamp problem
+# please email me the format of your timestamps.
+#
+# CHANGES:
+# 1999/05/03:
+# - Now accepts hostnames in the source and destination address fields, as
+# well as port names in the port fields. This allows the people who are
+# using ipmon -n to still use plog. Note that if you are logging
+# hostnames, you are vulnerable to forgery of DNS information, modified
+# DNS information, and your log files will be larger also. If you are
+# using this program you can have it look up the names for you (still
+# vulnerable to forgery) and keep your addresses all in numeric format,
+# so that packets from the same source will always show the same source
+# address regardless of what's up with DNS. Nevertheless, some people
+# wanted this, so here it is.
+# - Added S and n flags to %acts hash. Thanks to Stephen J. Roznowski
+# <sjr@home.net>.
+# - Stopped reporting host IPs twice when numeric output was requested.
+# Thanks, yet again, to Stephen J. Roznowski <sjr@home.net>.
+# - Number of minor tweaks that might speed it up a bit, and some comments.
+# - Put the script back up on the web site. I moved the site and forgot to
+# move the tool.
+# 1999/02/04:
+# - Changed log line parser to accept fully-qualified name in the logging
+# host field. Thanks to Stephen J. Roznowski <sjr@home.net>.
+# 1999/01/22:
+# - Changed high port strategy to use 65536 for unknown high ports so that
+# they are sorted last.
+# 1999/01/21:
+# - Moved icmp parsing to output loop.
+# - Added parsing of icmp codes, and more types.
+# - Changed packet sort routine to sort by port number rather than service
+# name.
+# 1999/01/20:
+# - Fixed problem matching ipmon log lines. Sometimes they have "/ipmon" in
+# them, sometimes just "ipmon".
+# - Added numeric parse option to turn off hostname lookups.
+# - Moved summary to usage() sub.
+
+use strict;
+use Socket;
+
+select STDOUT ; $| = 1 ;
+
+my %hosts;
+
+my $me = $0;
+$me =~ s/^([^\/]*\/)*//;
+
+my $numeric = 0;
+
+# Under IPv4 port numbers are unsigned shorts. The value below is higher
+# than the maximum value of an unsigned port, and is used in place of
+# high port numbers that don't correspond to known services. This makes
+# high ports get sorted behind all others.
+my $highPort = 0x10000;
+
+# Map of log codes for various actions. Not all of these can occur, but
+# I've included everything in print_ipflog() from ipmon.c.
+my %acts = (
+ 'p' => 'pass',
+ 'P' => 'pass',
+ 'b' => 'block',
+ 'B' => 'block',
+ 'L' => 'log',
+ 'S' => 'short',
+ 'n' => 'nomatch',
+);
+
+while (defined ($_ = shift))
+{
+ if (s/^-//)
+ {
+ $numeric += s/n//g;
+ &usage (0) if (s/[h\?]//g);
+ &usage (1) if (length ($_));
+ next;
+ }
+ &usage (1);
+}
+
+while (<STDIN>)
+{
+ chomp;
+
+ # For ipmon output that came through syslog, we'll have an asctime
+ # timestamp, hostname, "ipmon"[process id]: prefixed to the line. For
+ # output that was written directly to a file by ipmon, we'll have a date
+ # prefix as dd/mm/yyyy (no y2k problem here!). Both formats then have a
+ # packet timestamp and the log info.
+ my ($time, $log);
+ if (/^(\w+\s+\d+\s+\d+:\d+:\d+)\s+([\w\.]+)\s+\S*ipmon\[\d+\]:\s+(\d+:\d+:\d+\.\d+)\s+(.+)/)
+ {
+ my ($logtime, $loghost);
+ ($logtime, $loghost, $time, $log) = ($1, $2, $3, $4);
+ }
+ elsif (/^(\d+\/\d+\/\d+)\s+(\d+:\d+:\d+\.\d+)\s+(.+)$/)
+ {
+ my $logdate;
+ ($logdate, $time, $log) = ($1, $2, $3);
+ }
+ else
+ {
+ # It don't look like no ipmon output to me, baby.
+ next;
+ }
+ next unless (defined ($log));
+
+ # Parse the log line. We're expecting interface name, rule group and
+ # number, an action code, a source host name or IP with possible port
+ # name or number, a destination host name or IP with possible port
+ # number, "PR", a protocol name or number, "len", a header length, a
+ # packet length, and maybe some additional info.
+ $log =~ /^(\w+)\s+@(\d+):(\d+)\s+(\w)\s+([a-zA-Z0-9\-\.,]+)\s+->\s+([a-zA-Z0-9\-\.,]+)\s+PR\s+(\w+)\s+len\s+(\d+)\s+(\d+)\s*(.*)$/;
+ my ($if, $group, $rule, $act, $src, $dest, $proto, $hlen, $len, $more)
+ = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
+ unless (defined ($len))
+ {
+ warn ("Bad input line at $.: \"$_\"");
+ next;
+ }
+
+ my ($sport, $dport);
+
+ if ($proto eq 'icmp')
+ {
+ if ($more =~ s/^icmp (\d+)\/(\d+)\s*//)
+ {
+ # We save icmp type and code in both sport and dport.
+ $dport = $sport = "$1.$2";
+ }
+ else
+ {
+ $sport = '';
+ $dport = '';
+ }
+ }
+ else
+ {
+ $sport = (($src =~ s/,(\w+)$//) ? &portSimplify ($1, $proto) : '');
+ $dport = (($dest =~ s/,(\w+)$//) ? &portSimplify ($1, $proto) : '');
+ }
+
+ # Make sure addresses are numeric at this point. We want to sort by
+ # IP address later. This has got to do some weird things, but if you
+ # want to use ipmon -n, be ready for weirdness.
+ $src = &hostNumber ($src);
+ $dest = &hostNumber ($dest);
+
+ # Convert proto to proto number.
+ $proto = &protoNumber ($proto);
+
+ sub countPacket
+ {
+ my ($host, $dir, $peer, $proto, $packet) = @_;
+
+ # Make sure host is in the hosts hash.
+ $hosts{$host} =
+ +{
+ 'out' => +{ },
+ 'in' => +{ },
+ } unless (exists ($hosts{$host}));
+
+ # Get the incoming/outgoing traffic hash for the host in question.
+ my $trafficHash = $hosts{$host}->{$dir};
+
+ # Make sure there's a hash for the peer.
+ $trafficHash->{$peer} = +{ } unless (exists ($trafficHash->{$peer}));
+
+ # Make sure the peer hash has a hash for the protocol number.
+ my $peerHash = $trafficHash->{$peer};
+ $peerHash->{$proto} = +{ } unless (exists ($peerHash->{$proto}));
+
+ # Make sure there's a counter for this packet type in the proto hash.
+ my $protoHash = $peerHash->{$proto};
+ $protoHash->{$packet} = 0 unless (exists ($protoHash->{$packet}));
+
+ # Increment the counter.
+ ++$protoHash->{$packet};
+ }
+
+ # Count the packet as outgoing traffic from the source address.
+ &countPacket ($src, 'out', $dest, $proto, "$sport:$dport:$if:$act");
+
+ # Count the packet as incoming traffic to the destination address.
+ &countPacket ($dest, 'in', $src, $proto, "$dport:$sport:$if:$act");
+}
+
+my $dir;
+foreach $dir (qw(out in))
+{
+ my $order = ($dir eq 'out' ? 'source' : 'destination');
+ my $arrow = ($dir eq 'out' ? '->' : '<-');
+
+ print "### Traffic by $order address:\n";
+
+ sub ipSort
+ {
+ my @a = split (/\./, $a);
+ my @b = split (/\./, $b);
+ $a[0] != $b[0] ? $a[0] <=> $b[0]
+ : $a[1] != $b[1] ? $a[1] <=> $b[1]
+ : $a[2] != $b[2] ? $a[2] <=> $b[2]
+ : $a[3] != $b[3] ? $a[3] <=> $b[3]
+ : 0;
+ }
+
+ my $host;
+ foreach $host (sort ipSort (keys %hosts))
+ {
+ my $traffic = $hosts{$host}->{$dir};
+
+ # Skip hosts with no traffic.
+ next unless (scalar (keys (%{$traffic})));
+
+ if ($numeric)
+ {
+ print " $host\n";
+ }
+ else
+ {
+ print " ", &hostName ($host), " \[$host\]\n";
+ }
+
+ my $peer;
+ foreach $peer (sort ipSort (keys %{$traffic}))
+ {
+ my $peerHash = $traffic->{$peer};
+ my $peerName = &hostName ($peer);
+ my $proto;
+ foreach $proto (sort (keys (%{$peerHash})))
+ {
+ my $protoHash = $peerHash->{$proto};
+ my $protoName = &protoName ($proto);
+
+ sub packetSort
+ {
+ my ($asport, $adport, $aif, $aact) = split (/:/, $a);
+ my ($bsport, $bdport, $bif, $bact) = split (/:/, $b);
+ return $bact cmp $aact if ($aact ne $bact);
+ return $aif cmp $bif if ($aif ne $bif);
+ return $asport <=> $bsport if ($asport != $bsport);
+ return $adport <=> $bdport if ($adport != $bdport);
+ }
+
+ my $packet;
+ foreach $packet (sort packetSort (keys %{$protoHash}))
+ {
+ my ($sport, $dport, $if, $act) = split (/:/, $packet);
+ my $count = $protoHash->{$packet};
+ $act = '?' unless (defined ($act = $acts{$act}));
+ if (($protoName eq 'tcp') || ($protoName eq 'udp'))
+ {
+ printf (" %-6s %7s %5d %6s %14s %2s %s.%s\n", $if, $act, $count, $protoName, &portName ($sport, $protoName), $arrow, $peerName, &portName ($dport, $protoName));
+ }
+ elsif ($protoName eq 'icmp')
+ {
+ printf (" %-6s %7s %5d %6s %14s %2s %s\n", $if, $act, $count, $protoName, &icmpType ($sport), $arrow, $peerName);
+ }
+ else
+ {
+ printf (" %-6s %7s %5d %6s %14s %2s %s\n", $if, $act, $count, $protoName, '', $arrow, $peerName);
+ }
+ }
+ }
+ }
+ }
+
+ print "\n\n";
+}
+
+exit (0);
+
+# We use this hash to cache port name -> number and number -> name mappings.
+# Isn't is cool that we can use the same hash for both?
+my %pn;
+
+# Translates a numeric port/named protocol to a port name. Reserved ports
+# that do # not have an entry in the services database are left numeric.
+# High ports that do not have an entry in the services database are mapped
+# to '<high>'.
+sub portName
+{
+ my $port = shift;
+ my $proto = shift;
+ my $pname = "$port/$proto";
+ unless (exists ($pn{$pname}))
+ {
+ my $name = getservbyport ($port, $proto);
+ $pn{$pname} = (defined ($name) ? $name : ($port <= 1023 ? $port : '<high>'));
+ }
+ return $pn{$pname};
+}
+
+# Translates a named port/protocol to a port number.
+sub portNumber
+{
+ my $port = shift;
+ my $proto = shift;
+ my $pname = "$port/$proto";
+ unless (exists ($pn{$pname}))
+ {
+ my $number = getservbyname ($port, $proto);
+ unless (defined ($number))
+ {
+ # I don't think we need to recover from this. How did the port
+ # name get into the log file if we can't find it? Log file from
+ # a different machine? Fix /etc/services on this one if that's
+ # your problem.
+ die ("Unrecognized port name \"$port\" at $.");
+ }
+ $pn{$pname} = $number;
+ }
+ return $pn{$pname};
+}
+
+# Convert all unrecognized high ports to the same value so they are treated
+# identically. The protocol should be by name.
+sub portSimplify
+{
+ my $port = shift;
+ my $proto = shift;
+
+ # Make sure port is numeric.
+ $port = &portNumber ($port, $proto)
+ unless ($port =~ /^\d+$/);
+
+ # Look up port name.
+ my $portName = &portName ($port, $proto);
+
+ # Port is an unknown high port. Return a value that is too high for a
+ # port number, so that high ports get sorted last.
+ return $highPort if ($portName eq '<high>');
+
+ # Return original port number.
+ return $port;
+}
+
+# Again, we can use the same hash for both host name -> IP mappings and
+# IP -> name mappings.
+my %ip;
+
+# Translates a dotted quad into a hostname. Don't pass names to this
+# function.
+sub hostName
+{
+ my $ip = shift;
+ return $ip if ($numeric);
+ unless (exists ($ip{$ip}))
+ {
+ my $addr = inet_aton ($ip);
+ my $name = gethostbyaddr ($addr, AF_INET);
+ if (defined ($name))
+ {
+ $ip{$ip} = $name;
+
+ # While we're at it, cache the forward lookup.
+ $ip{$name} = $ip;
+ }
+ else
+ {
+ # Just map the IP address to itself. There's no reverse.
+ $ip{$ip} = $ip;
+ }
+ }
+ return $ip{$ip};
+}
+
+# Translates a hostname or dotted quad into a dotted quad.
+sub hostNumber
+{
+ my $name = shift;
+ if ($name =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
+ {
+ # Return original value for dotted quads.
+ my $or = int ($1) | int ($2) | int ($3) | int ($4);
+ return $name if ($or == ($or & 0xff));
+ }
+ unless (exists ($ip{$name}))
+ {
+ my $addr = inet_aton ($name);
+ unless (defined ($addr))
+ {
+ # Again, I don't think we need to recover from this. If we can't
+ # resolve a hostname that ended up in the log file, punt. We
+ # want to be able to sort hosts by IP address later, and letting
+ # hostnames through will snarl up that code. Users of ipmon -n
+ # will have to grin and bear it for now.
+ die ("Unable to resolve host \"$name\" at $.");
+ }
+ my $ip = inet_ntoa ($addr);
+ $ip{$name} = $ip;
+
+ # While we're at it, cache the reverse lookup.
+ $ip{$ip} = $name;
+ }
+ return $ip{$name};
+}
+
+# Hash for protocol number <--> name mappings.
+my %pr;
+
+# Translates a protocol number into a protocol name, or a number if no name
+# is found in the protocol database.
+sub protoName
+{
+ my $code = shift;
+ return $code if ($code !~ /^\d+$/);
+ unless (exists ($pr{$code}))
+ {
+ my $name = scalar (getprotobynumber ($code));
+ if (defined ($name))
+ {
+ $pr{$code} = $name;
+ }
+ else
+ {
+ $pr{$code} = $code;
+ }
+ }
+ return $pr{$code};
+}
+
+# Translates a protocol name or number into a protocol number.
+sub protoNumber
+{
+ my $name = shift;
+ return $name if ($name =~ /^\d+$/);
+ unless (exists ($pr{$name}))
+ {
+ my $code = scalar (getprotobyname ($name));
+ if (defined ($code))
+ {
+ $pr{$name} = $code;
+ }
+ else
+ {
+ $pr{$name} = $name;
+ }
+ }
+ return $pr{$name};
+}
+
+sub icmpType
+{
+ my %icmp = (
+ 0 => +{
+ name => 'echo-reply',
+ codes => +{0 => undef},
+ },
+ 3 => +{
+ name => 'dest-unr',
+ codes => +{
+ 0 => 'net',
+ 1 => 'host',
+ 2 => 'proto',
+ 3 => 'port',
+ 4 => 'need-frag',
+ 5 => 'no-sroute',
+ 6 => 'net-unk',
+ 7 => 'host-unk',
+ 8 => 'shost-isol',
+ 9 => 'net-proh',
+ 10 => 'host-proh',
+ 11 => 'net-tos',
+ 12 => 'host-tos',
+ },
+ },
+ 4 => +{
+ name => 'src-quench',
+ codes => +{0 => undef},
+ },
+ 5 => +{
+ name => 'redirect',
+ codes => +{
+ 0 => 'net',
+ 1 => 'host',
+ 2 => 'tos',
+ 3 => 'tos-host',
+ },
+ },
+ 6 => +{
+ name => 'alt-host-addr',
+ codes => +{0 => undef},
+ },
+ 8 => +{
+ name => 'echo',
+ codes => +{0 => undef},
+ },
+ 9 => +{
+ name => 'rtr-advert',
+ codes => +{0 => undef},
+ },
+ 10 => +{
+ name => 'rtr-select',
+ codes => +{0 => undef},
+ },
+ 11 => +{
+ name => 'time-excd',
+ codes => +{
+ 0 => 'in-transit',
+ 1 => 'frag-assy',
+ },
+ },
+ 12 => +{
+ name => 'param-prob',
+ codes => +{
+ 0 => 'ptr-err',
+ 1 => 'miss-opt',
+ 2 => 'bad-len',
+ },
+ },
+ 13 => +{
+ name => 'time',
+ codes => +{0 => undef},
+ },
+ 14 => +{
+ name => 'time-reply',
+ codes => +{0 => undef},
+ },
+ 15 => +{
+ name => 'info',
+ codes => +{0 => undef},
+ },
+ 16 => +{
+ name => 'info-req',
+ codes => +{0 => undef},
+ },
+ 17 => +{
+ name => 'mask-req',
+ codes => +{0 => undef},
+ },
+ 18 => +{
+ name => 'mask-reply',
+ codes => +{0 => undef},
+ },
+ 31 => +{
+ name => 'dgram-conv-err',
+ codes => +{ },
+ },
+ 32 => +{
+ name => 'mbl-host-redir',
+ codes => +{ },
+ },
+ 33 => +{
+ name => 'ipv6-whereru?',
+ codes => +{ },
+ },
+ 34 => +{
+ name => 'ipv6-iamhere',
+ codes => +{ },
+ },
+ 35 => +{
+ name => 'mbl-reg-req',
+ codes => +{ },
+ },
+ 36 => +{
+ name => 'mbl-reg-rep',
+ codes => +{ },
+ },
+ );
+
+ my $typeCode = shift;
+ my ($type, $code) = split ('\.', $typeCode);
+
+ return "?" unless (defined ($code));
+
+ my $info = $icmp{$type};
+
+ return "\(type=$type/$code?\)" unless (defined ($info));
+
+ my $typeName = $info->{name};
+ my $codeName;
+ if (exists ($info->{codes}->{$code}))
+ {
+ $codeName = $info->{codes}->{$code};
+ $codeName = (defined ($codeName) ? "/$codeName" : '');
+ }
+ else
+ {
+ $codeName = "/$code";
+ }
+ return "$typeName$codeName";
+}
+
+sub usage
+{
+ my $ec = shift;
+
+ print STDERR <<EOT;
+usage: $me [-n]
+
+Parses logging from ipmon and presents it in a comprehensible format.
+This program generates two tables: one organized by source address and
+another organized by destination address. For the first table, source
+addresses are sorted by IP address. For each address, all packets
+originating at the address are presented in a tabular form, where all
+packets with the same source and destination address and port are counted
+as a single entry. The packet count for each entry is shown as the third
+field. In addition, any port number greater than 1024 that doesn't match
+an entry in the services table is treated as a "high" port, and high ports
+are coalesced into the same entry. The entry fields for the source address
+table are:
+
+ iface action packet-count proto src-port dest-ip dest-port
+
+The entry fields for the destination table are:
+
+ iface action packet-count proto dest-port src-ip src-port
+
+If the -n option is given, reverse hostname lookups are disabled and all
+hosts are displayed as numeric addresses.
+
+Note: if you are logging traffic with ipmon -n, ipmon will already have
+looked up and logged addresses as hostnames where possible. This has an
+important side effect: this program will translate the hostnames back into
+IP addresses which may not match the original addresses of the logged
+packets because of numerous DNS issues. If you care about where packets
+are really coming from, you simply cannot rely on ipmon -n. An attacker
+with control of his reverse DNS can map the reverse lookup to anything he
+likes. If you haven't logged the numeric IP address, there's no way to
+discover the source of an attack reliably. For this reason, I strongly
+recommend that you run ipmon without the -n option, and use this or a
+similar script to do reverse lookups during analysis, rather than during
+logging.
+EOT
+
+ exit ($ec);
+}
+
OpenPOWER on IntegriCloud