#!/usr/pkg/bin/perl
#
# @(#)proc-ipmon.pl,v 1.6 2004/12/29 03:54:36 kim Exp
#
# Summarize ipf log
#
# 1999-05-06  Arto Selonen
#    template from Kimmo Suominen
#

@scanners = ();
@trials = ();
@plist = ();
@blist = ();
@olist = ();

$prev_src = "";
$prev_tgt = "";
$prev_sprt = "";
$prev_tprt = "";
$prev_opt = "";

$cnt = 0;
$cnt_ign = 0;
$cnt_pkt = 0;
$cnt_dup = 0;
$cnt_ping = 0;
$cnt_scan = 0;
$cnt_pass = 0;
$cnt_blck = 0;
$cnt_new = 0;
$cnt_close = 0;
$cnt_exp = 0;
$cnt_rdr = 0;
$cnt_map = 0;
$cnt_nexp = 0;
$cnt_stpeak = 0;
$cnt_stmax = 0;
$cnt_stpkt = 0;
$cnt_stbyte = 0;
$cnt_napeak = 0;
$cnt_namax = 0;
$cnt_napkt = 0;
$cnt_nabyte = 0;

$pass_if_exp = "IGNORE";
$pass_srcip_exp = "IGNORE";
$pass_dstip_exp = "IGNORE";
$block_if_exp = "IGNORE";
$block_srcip_exp = "^(10|192\.168)\.";
$block_dstip_exp = "IGNORE";
$block_prt_thr = 5;
$ddos_thr = 32;

# read log lines
while ($line = <>) {
  chomp ($line);

  $cnt++;

  # /dev/ipl entries
  if ($line =~ /((\d+\/\d+\/\d+)|(\S+\s+\d+\s+\d+:\d+:\d+\s+\S+\s+\S+\[\d+\]:))\s+(\d+:\d+:\d+)\.\d+\s+((\d+)x\s+)?(\S+) \@\d+:\d+ ([pb]) (\S+) -> (\S+) (.*?)\s*$/) {
    $hour = $4;
    $reps = $6;
    $if = $7;
    $mode = $8;
    $from = $9;
    $to = $10;
    $flags = $11;

    if (!defined ($reps)) {
      $reps = 1;
    }

    # printf ("DBG0: $line\n");

    # get protocol
    ($proto) = $flags =~ /^PR (\S+)/;
    if (!defined ($proto)) {
      $proto = "unknown";
    }

    # separate IP/PORT for source
    if ($from =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(,(\d+))?/) {
      $frm_ip = $1;
      $frm_port = $3;
      if (!defined ($frm_port)) {
        $frm_port = "-";
      }
    }
    else {
      # not IPv4 address
      $cnt_ign += $reps;
      next;
    }

    # separate IP/PORT for target
    if ($to =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(,(\d+))?/) {
      $tgt_ip = $1;
      $tgt_port = $3;
      if (!defined ($tgt_port)) {
        $tgt_port = "-";
      }
    }
    else {
      # not IPv4 address
      $cnt_ign += $reps;
      next;
    }

    $cnt_pkt += $reps;
    $prot_stat{$proto} += $reps;

    # skip echo requests
    if ($flags =~ / icmp echo\/0/) {
      $cnt_ping += $reps;
      if ($mode eq "p") {
        $cnt_pass += $reps;
      }
      else {
        $cnt_blck += $reps;
      }
      next;
    }

    # XXX better duplicate handling needed
    # check if very similar to previous entry (timeout retry?)
    if (($proto eq "tcp" and $flags eq $prev_opt and $frm_ip eq $prev_src and $to eq $prev_tgt
        and $from_port eq $prev_sprt and $tgt_port eq $prev_tprt) or
        &is_dup ($frm_ip, $frm_port, $tgt_ip, $tgt_port, @trials)) {
      &stat_upd ($if, $mode, $proto, $frm_ip, $frm_port, $tgt_ip, $tgt_port, $reps);
      $cnt_dup += $reps;
      if ($mode eq "p") {
        $cnt_pass += $reps;
      }
      else {
        $cnt_blck += $reps;
      }
      next;
    }
    else {
      $prev_src = $frm_ip;
      $prev_tgt = $to;
      $prev_sprt = $frm_port;
      $prev_tprt = $tgt_port;
      $prev_opt = $flags;
    }

    # is this a known port scanner?
    if (&in_list ($frm_ip, @scanners)) {

      # printf ("DBG: found a repeat offender: $frm_ip\n");
      &stat_upd ($if, $mode, $proto, $frm_ip, $frm_port, $tgt_ip, $tgt_port, $reps);

      # XXX also update scanner stats?
      $pscan_src{$frm_ip} += $reps;
      $pscan_dst{$tgt_ip} += $reps;
      $pscan_prt{$tgt_port} += $reps;

      $cnt_scan += $reps;
      if ($mode eq "p") {
        $cnt_pass += $reps;
      }
      else {
        $cnt_blck += $reps;
      }
      next;
    }

    # is there a previous attempt (matching tuple [SRCIP,DSTIP] or [SRCIP,DSTPRT])
    if ($mode eq "b" and $proto =~ /(tcp|udp)/ and &is_guilty ($frm_ip, $tgt_ip, $tgt_port, @trials)) {

      # printf ("DBG: found a scanner: $frm_ip\n");
      push (@scanners, ($frm_ip));
      &stat_upd ($if, $mode, $proto, $frm_ip, $frm_port, $tgt_ip, $tgt_port, $reps);

      # discard all entries from this scanner
      ($rem_trial, @trials) = &off_list ($frm_ip, @trials);
      ($rem_plist, @plist) = &off_list ($frm_ip, @plist);
      ($rem_blist, @blist) = &off_list ($frm_ip, @blist);
      $blist_port{$tgt_port} -= $rem_blist;

      # XXX also update scanner stats?
      $pscan_src{$frm_ip} += ($reps+$rem_blist+$rem_plist);	# this many attempts in total
      $pscan_dst{$tgt_ip} += ($reps+1);				# XXX still wrong (first ones ignored, guess 1)
      $pscan_prt{$tgt_port} += ($reps+1);			# XXX still wrong (first ones ignored, guess 1)
      $cnt_scan += ($reps+$rem_blist+$rem_plist);
      $cnt_blck += $reps;
      next;
    }

    # blocked TCP/UDP is a potential port scan
    if ($mode eq "b" and $proto =~ /(tcp|udp)/) {
      # printf ("DBG1: $line\n");
      push (@trials, ("$frm_ip $frm_port $tgt_ip $tgt_port $reps"));
    }

    if ($mode eq "p") {
      push (@plist, ("$reps $hour $if $from -> $to $flags"));
      $cnt_pass += $reps;
      next;
    }

    elsif ($mode eq "b") {
      push (@blist, ("$reps $hour $if $from -> $to $flags"));
      $cnt_blck += $reps;
      $blist_port{$tgt_port} += $reps;
      next;
    }

  }

  # /dev/ipstate entries
  elsif ($line =~ /((\d+\/\d+\/\d+)|(\S+\s+\d+\s+\d+:\d+:\d+\s+\S+\s+\S+\[\d+\]:))\s+(\d+:\d+:\d+)\.\d+\s+STATE:(\S+)\s+(\S+) -> (\S+) (.*?)\s*$/) {
    $hour = $4;
    $mode = $5;
    $from = $6;
    $to = $7;
    $flags = $8;

    # get protocol
    ($proto) = $flags =~ /^PR (\S+)/;
    if (!defined ($proto)) {
      $proto = "unknown";
    }

    # separate IP/PORT for source
    if ($from =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(,(\d+))?/) {
      $frm_ip = $1;
      $frm_port = $3;
      if (!defined ($frm_port)) {
        $frm_port = "-";
      }
    }
    else {
      # not IPv4 address
      $cnt_ign++;
      next;
    }

    # separate IP/PORT for target
    if ($to =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(,(\d+))?/) {
      $tgt_ip = $1;
      $tgt_port = $3;
      if (!defined ($tgt_port)) {
        $tgt_port = "-";
      }
    }
    else {
      # not IPv4 address
      $cnt_ign++;
      next;
    }

    # NEW state
    if ($mode eq "NEW") {
      $cnt_new++;
      $cnt_stpeak++;
      if ($cnt_stpeak > $cnt_stmax) {
        $cnt_stmax = $cnt_stpeak;
      }
      next;
    }

    # CLOSE state
    elsif ($mode eq "CLOSE") {
      $cnt_close++;
      $cnt_stpeak--;

      if ($flags =~ /Pkts\s+(\d+)\s+Bytes\s+(\d+)\s*$/) {
        $cnt_stpkt += $1;
        $cnt_stbyte += $2;
      }
      if ($flags =~ /Forward: Pkts in\s+(\d+)\s+Bytes in\s+(\d+)\s+/) {
        $cnt_stpkt += $1;
        $cnt_stbyte += $2;
      }
      if ($flags =~ /Pkts out\s+(\d+)\s+Bytes out\s+(\d+)\s+Backward:/) {
        $cnt_stpkt += $1;
        $cnt_stbyte += $2;
      }
      if ($flags =~ /Backward: Pkts in\s+(\d+)\s+Bytes in\s+(\d+)\s/) {
        $cnt_stpkt += $1;
        $cnt_stbyte += $2;
      }
      if ($flags =~ /Pkts out\s+(\d+)\s+Bytes out\s+(\d+)\s*$/) {
        $cnt_stpkt += $1;
        $cnt_stbyte += $2;
      }
      next;
    }

    # EXPIRE state
    elsif ($mode eq "EXPIRE") {
      $cnt_exp++;
      $cnt_stpeak--;

      if ($flags =~ / Pkts\s+(\d+)\s+Bytes\s+(\d+)\s*$/) {
        $cnt_stpkt += $1;
        $cnt_stbyte += $2;
      }
      if ($flags =~ /Forward: Pkts in\s+(\d+)\s+Bytes in\s+(\d+)\s+/) {
        $cnt_stpkt += $1;
        $cnt_stbyte += $2;
      }
      if ($flags =~ /Pkts out\s+(\d+)\s+Bytes out\s+(\d+)\s+Backward:/) {
        $cnt_stpkt += $1;
        $cnt_stbyte += $2;
      }
      if ($flags =~ /Backward: Pkts in\s+(\d+)\s+Bytes in\s+(\d+)\s/) {
        $cnt_stpkt += $1;
        $cnt_stbyte += $2;
      }
      if ($flags =~ /Pkts out\s+(\d+)\s+Bytes out\s+(\d+)\s*$/) {
        $cnt_stpkt += $1;
        $cnt_stbyte += $2;
      }
      next;
    }
    else {
      printf ("UNKNOWN STATE TRANSITION: $mode\n");
      $cnt_ign++;
      next;
    }
  }

  # /dev/ipnat entries
  elsif ($line =~ /((\d+\/\d+\/\d+)|(\S+\s+\d+\s+\d+:\d+:\d+\s+\S+\s+\S+\[\d+\]:))\s+(\d+:\d+:\d+)\.\d+\s+\@\d+\s+NAT:(\S+)\s+(\S+) <- -> (\S+) \[\S+\](.*?)\s*$/) {
    $hour = $4;
    $mode = $5;
    $from = $6;
    $to = $7;
    $flags = $8;

    # separate IP/PORT for source
    if ($from =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(,(\d+))?/) {
      $frm_ip = $1;
      $frm_port = $3;
      if (!defined ($frm_port)) {
        $frm_port = "-";
      }
    }
    else {
      # not IPv4 address
      $cnt_ign++;
      next;
    }

    # separate IP/PORT for target
    if ($to =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(,(\d+))?/) {
      $tgt_ip = $1;
      $tgt_port = $3;
      if (!defined ($tgt_port)) {
        $tgt_port = "-";
      }
    }
    else {
      # not IPv4 address
      $cnt_ign++;
      next;
    }

    # redirect
    if ($mode eq "RDR") {
      $cnt_rdr++;
      $cnt_napeak++;
      if ($cnt_napeak > $cnt_namax) {
        $cnt_namax = $cnt_napeak;
      }
      next;
    }

    # MAP
    elsif ($mode eq "MAP") {
      $cnt_map++;
      $cnt_napeak++;
      if ($cnt_napeak > $cnt_namax) {
        $cnt_namax = $cnt_napeak;
      }

      next;
    }

    # EXPIRE mapping
    elsif ($mode eq "EXPIRE") {
      $cnt_nexp++;
      $cnt_napeak--;

      if ($flags =~ /Pkts\s+(\d+)\s+Bytes\s+(\d+)\s*$/) {
        $cnt_napkt += $1;
        $cnt_nabyte += $2;
      }
      elsif ($flags =~ /Pkts\s+(\d+)\/(\d+)\s+Bytes\s+(\d+)\/(\d+)\s*$/) {
        $cnt_napkt += $1 + $2;
        $cnt_nabyte += $3 + $4;
      }
      next;
    }
    else {
      printf ("UNKNOWN NAT ENTRY: $mode\n");
      $cnt_ign++;
      next;
    }
  }

  else {
    $cnt_ign++;
  }
}

foreach $prt (sort keys %blist_port) {
  # printf ("DBG: port = %s, count = %d\n", $prt, $blist_port{$prt});
  if ($blist_port{$prt} > $ddos_thr) {
    # printf ("DBG: collapsing port $prt\n");
    @blist = &collapse_ddos_port ($prt, @blist);
  }
}

&print_hdr ("IPFILTER LOG SUMMARY");
&print_states ("IPFILTER KEEP-STATE STATISTICS") if ($cnt_new or $cnt_close or $cnt_exp);
&print_nat ("IPFILTER NAT MAPPING STATISTICS") if ($cnt_rdr or $cnt_map or $cnt_nexp);
&print_scan ("PORT SCAN STATISTICS");
&print_it ("PASSED TRAFFIC", @plist);
&print_it ("BLOCKED TRAFFIC", @blist);
&print_it ("OTHER", @olist);

exit 0;

sub in_list {
  my ($item, @list) = @_;

  return 0 if ($#list < 0);
  return 1 if (grep (/^$item$/, @list));
  return 0;
}

sub is_dup {
  my ($src_ip, $src_port, $dst_ip, $dst_port, @list) = @_;

  return 0 if ($#list < 0);
  return 1 if (grep (/^$src_ip $src_port $dst_ip $dst_port /, @list));
  return 0;
}

sub is_guilty {
  my ($src_ip, $dst_ip, $dst_port, @list) = @_;

  return 0 if ($#list < 0);
  return 1 if (grep (/^$src_ip \S+ \S+ $dst_port /, @list));
  return 1 if (grep (/^$src_ip \S+ $dst_ip /, @list));
  return 0;
}

sub off_list {
  my ($item, @list) = @_;

  @new = ();
  $off_cnt = 0;
  foreach $entry (@list) {
    if ($entry !~ /(^$item \S+| [a-z0-9]+ ${item}[, ])/) {
      push (@new, ($entry));
    }
    # we need to calculate how many *packets* (not lines) get removed
    else {
      # this is a typical @trials entry
      if ($entry =~ /\s+\d+\.\d+\.\d+\.\d+\s+\d+\s+(\d+)\s*$/) {
        $off_cnt += $1;
      }
      # this is a typical @plist/blist/olist entry
      elsif ($entry =~ /^(\d+)\s\d+:\d+:\d+\s+/) {
        $off_cnt += $1;
      }
    }
  }
  return ($off_cnt, @new);
}

sub stat_upd {
  my ($if, $mode, $proto, $src_ip, $src_port, $dst_ip, $dst_port, $count) = @_;

}

sub collapse_ddos_port {
  my ($port, @list) = @_;

  $cllp_cnt = 0;
  @new = ();
  %uniq_src = ();
  %uniq_dst = ();
  foreach $entry (@list) {
    if ($entry =~ /(\d+)\s+(\d+:\d+:\d+)\s+(\S+)\s+([^, ]+)(,(\d+))? -> ([^, ]+)(,(\d+))?\s+PR\s+(.*)\s+(IN|OUT)$/) {
      $reps = $1;
      $stamp = $2;
      $if = $3;

      $src_ip = $4;
      if (defined($6)) {
        $src_port = $6;
      }
      else {
        $src_port = "";
      }

      $dst_ip = $7;
      if (defined($9)) {
        $dst_port = int($9);
        $pcnt{$dst_port}++;
      }
      else {
        $dst_port = "";
      }

      ($flags = $10) =~ s/len \d+ \d+ //;
      $flags =~ s/icmp icmp/icmp/;

      # printf ("DBG: flags = <$flags>, dest = $dst_port, look = $port\n");
      if ($flags =~ /^tcp -S\s*$/ and $dst_port == int($port)) {
        # printf ("DBG: found matching entry: $entry\n");
        $uniq_src{$src_ip} += $reps;
        $uniq_dst{$dst_ip} += $reps;
        $cllp_cnt += $reps;
        next;
      }
      else {
        push (@new, ($entry));
        next;
      }
    }
    else {
      push (@new, ($entry));
      next;
    }
  }

  $ddos_srcip{$port} = scalar keys %uniq_src;
  $ddos_dstip{$port} = scalar keys %uniq_dst;
  $ddos_cnt{$port} = $cllp_cnt;
  $cnt_scan += $cllp_cnt;
  #printf ("There were %d unique source IPs sending %d TCP/SYN packets to %d unique target IPs to port $port\n",
  #       scalar keys %uniq_src, $cllp_cnt, scalar keys %uniq_dst);
  return (@new);
}

sub print_pairs {
  foreach $if (sort {$counter{$b} <=> $counter{$a}} keys %counter) {
    printf ("Interface %s [%d]:\n", $if, $counter{$if});
    foreach $mode (sort keys %{$counter{$if}}) {
      printf (" type %s:\n", $mode);
      #foreach $src (sort {$counter{$if}{$mode}{$b} <=> $counter{$if}{$mode}{$a}} keys %{$counter{$if}{$mode}}) {
      #  printf ("  [%d] %s\n", $counter{$if}{$mode}{$src}, $src);
      #}
    }
  }
}

sub print_hdr {
  my $title = shift;

  printf ("$title:\n");
  print "=" x79 ."\n\n";
  printf ("  %6d lines analyzed\n", $cnt);
  printf ("+ %6d lines ignored\n", $cnt_ign);
  print " " x9 . "-" x60 ."\n";
  printf ("  %6d packets passed\n", $cnt_pass);
  printf ("+ %6d packets blocked\n", $cnt_blck);
  printf ("= %6d packets logged\n", $cnt_pkt);
  print " " x9 . "-" x60 ."\n";
  $i = 0;
  foreach $proto (sort {$prot_stat{$b} <=> $prot_stat{$a}} keys %prot_stat) {
    if ($i > 0) {
      print "+";
    }
    else {
      print " ";
    }
    printf (" %6d %s packets\n", $prot_stat{$proto}, $proto);
    $i++;
  }
  print " " x9 . "-" x60 ."\n";
  printf ("  %6d packets not shown (ICMP echo/0)\n", $cnt_ping);
  printf ("  %6d packets considered as duplicate (TCP retry)\n", $cnt_dup);
  printf ("~ %6d packets considered as part of a port scan\n", $cnt_scan);
  print "\n";
}

sub print_states {
  my $title = shift;

  printf ("$title:\n");
  print "=" x79 ."\n\n";
  printf ("Sessions started: %8d\tPackets   in finished sessions: %10d\n", $cnt_new, $cnt_stpkt);
  printf ("Sessions closed:  %8d\tKilobytes in finished sessions: %10d\n", $cnt_close, $cnt_stbyte/1024.0);
  printf ("Sessions expired: %8d\n", $cnt_exp);
  printf ("--------------------------\n");
  printf ("Sessions net:     %+8d\tAverage packets per session:    %10d\n", $cnt_new - $cnt_close - $cnt_exp, $cnt_stpkt / ($cnt_close + $cnt_exp + 1.0));
  printf ("Session peak:     %8d\tAverage bytes   per packet:     %10d\n", $cnt_stmax, $cnt_stbyte / ($cnt_stpkt+ 1.0));
  print "\n";
}

sub print_nat {
  my $title = shift;

  printf ("$title:\n");
  print "=" x79 ."\n\n";
  printf ("Redirections:     %8d\tPackets   in finished mappings: %10d\n", $cnt_rdr, $cnt_napkt);
  printf ("Mappings created: %8d\tKilobytes in finished mappings: %10d\n", $cnt_map, $cnt_nabyte/1024.0);
  printf ("Mappings expired: %8d\n", $cnt_nexp);
  printf ("--------------------------\n");
  printf ("Mappings net:     %+8d\tAverage packets per session:    %10d\n", $cnt_rdr + $cnt_map - $cnt_nexp, $cnt_napkt / $cnt_nexp);
  printf ("Mappings peak:    %8d\tAverage bytes   per packet:     %10d\n", $cnt_namax, $cnt_nabyte / ($cnt_napkt+1.0));
  print "\n";
}

sub print_scan {
  my $title = shift;
  my $note = 0;

  my @scanners = sort {$pscan_src{$b} <=> $pscan_src{$a}} keys %pscan_src;
  my $max = $#scanners;
  return 0 if ($max < 0);
  my @targets = sort {$pscan_dst{$b} <=> $pscan_dst{$a}} keys %pscan_dst;
  my @ports = sort {$pscan_prt{$b} <=> $pscan_prt{$a}} keys %pscan_prt;

  print "DISTRIBUTED TCP/SYN PORT SCAN STATISTICS: (threshold $ddos_thr)\n";
  print "=" x79 ."\n\n";
  print " UNIQUE SRC IPs   UNIQUE DST IPs   ATTEMPTS [PORT#]\n";
  print "-" x79 ."\n";
  foreach $port (sort {$a <=> $b} keys %ddos_srcip) {
    next if ($ddos_dstip{$port} < 1);
    printf (" %10d       %10d   %12d [%5d]\n",
            $ddos_srcip{$port}, $ddos_dstip{$port}, $ddos_cnt{$port}, $port);
  }
  print "\n";

  printf ("$title:\n");
  print "=" x79 ."\n\n";
  print "TOP SCANNERS' IP [trials]     TOP TARGETS' IP [trials]    TOP PORTS [trials]\n";
  print "-" x79 ."\n";

  for ($i = 0; $i < $max; $i++) {
    $note = 0;
    if ($scanners[$i] =~ /$block_srcip_exp/) {
      $note = 1;
    }
    printf ("%15s%s [%6d]", $scanners[$i], $note ? "*" : " ", $pscan_src{$scanners[$i]});
    if ($i <= $#targets) {
      printf ("    %15s  [%6d]", $targets[$i], $pscan_dst{$targets[$i]});
    }
    else {
      printf ("%s", " " x29);
    }
    if ($i <= $#ports) {
      printf ("       %5d  [%6d]", $ports[$i], $pscan_prt{$ports[$i]});
    }
    print "\n";
  }
  print "\n";
}

sub print_it {
  my $title = shift (@_);
  my @list = @_;
  my %pcnt = ();
  my $note = 0;
  my $if_exp = "IGNORE";
  my $srcip_exp = "IGNORE";
  my $dstip_exp = "IGNORE";

  if ($#list < 0) {
    return 0;
  }

  # which highlighting set to use
  if ($title =~ /^PASSED /) {
    $if_exp = $pass_if_exp;
    $srcip_exp = $pass_srcip_exp;
    $dstip_exp = $pass_dstip_exp;
  }
  elsif ($title =~ /^BLOCKED /) {
    $if_exp = $block_if_exp;
    $srcip_exp = $block_srcip_exp;
    $dstip_exp = $block_dstip_exp;
  }

  printf ("$title:\n");
  print "=" x79 ."\n\n";
  print (" Time     Intf Source          PORT   Destination     PORT  Proto & Flags\n");
  print "-" x79 ."\n";
  foreach $entry (@list) {

    $note = 0;
    if ($entry =~ /(\d+)\s+(\d+:\d+:\d+)\s+(\S+)\s+([^, ]+)(,(\d+))? -> ([^, ]+)(,(\d+))?\s+PR\s+(.*)\s+(IN|OUT)/) {
      $reps = $1;
      $stamp = $2;
      $if = $3;

      $src_ip = $4;
      if (defined($6)) {
        $src_port = $6;
      }
      else {
        $src_port = "";
      }

      $dst_ip = $7;
      if (defined($9)) {
        $dst_port = int($9);
        $pcnt{$dst_port} += $reps;
      }
      else {
        $dst_port = "";
      }

      ($flags = $10) =~ s/len \d+ \d+ //;
      $flags =~ s/icmp icmp/icmp/;

      if ($if =~ /$if_exp/) {
        $note = 1;
      }
      if ($src_ip =~ /$srcip_exp/) {
        $note = 1;
      }
      if ($dst_ip =~ /$dstip_exp/) {
        $note = 1;
      }

      if ($flags =~ /icmp (.*?) for ([^, ]+)(,(\d+))? - ([^, ]+)(,(\d+))?\s+PR\s+(.*)\s*$/) {
        $icmptype = $1;
        $icmp_or_ip = $2;
        $icmp_or_pt = $4;
        $icmp_ds_ip = $5;
        $icmp_ds_pt = $7;
        $icmp_or_pr = $8;

	$icmptype =~ s/admin_prohibit/admdeny/osi;
	$icmptype =~ s/net_prohib/netdeny/osi;
	$icmptype =~ s/timxceed/timeout/osi;

        printf ("%s$stamp %-4s %-15s %-5s  %-15s %-5s icmp $icmptype\n",
                $note ? "*" : " ", $if, $src_ip, $src_port, $dst_ip, $dst_port);
        printf ("               %-15s %-5s  %-15s %-5s %s\n",
                $icmp_or_ip, $icmp_or_pt, $icmp_ds_ip, $icmp_ds_pt, $icmp_or_pr);
      }
      else {
        for ($i = 0; $i < $reps; $i++) {
          printf ("%s$stamp %-4s %-15s %-5s  %-15s %-5s $flags\n",
                  $note ? "*" : " ", $if, $src_ip, $src_port, $dst_ip, $dst_port);
        }
      }
    } else {
      printf ("XXX: $entry\n");
    }
  }

  print "-" x79 ."\n\n";
  printf ("Target Tried\t(threshold $block_prt_thr)\n");
  printf (" Port  Times\n");
  printf ("------ -----\n");
  foreach $key (sort {$a <=> $b} keys %pcnt) {
    next if ($pcnt{$key} < $block_prt_thr);
    printf ("%6d %5d\n", $key, $pcnt{$key});
  }
  printf ("\n");
}
