Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

(code) Netfilter/iptables log parser/reporter for PHB consumption

by ybiC (Prior)
on Nov 10, 2003 at 04:16 UTC ( [id://305787]=sourcecode: print w/replies, xml ) Need Help??
Category: Networking Code
Author/Contact Info ybiC
Description:

A lightweight parser for Netfilter logs.   Produces a highly-simplified summary report for PHB consumption.   Sample input and report at tail of pod.

From a perlish standpoint, this has been my re-entry after a couple-month absence, and an exercise in using File::Temp properly, as well as an introduction to the following modules:

Update: as always, critique is welcome and requested.

#!/usr/bin/perl -w

## fwreport
## pod at tail


$|++;
use strict;
my $version = '0.09.27';


use File::Temp qw/tempfile/;
use Socket;
use Net::Whois::IANA;
use Locale::Country;
use File::Slurp;
use Text::Autoformat;
# use Module::Versions::Report;


## Config params:
my $infile     = shift || 'fwreport.in';    ## logfile from Netfilters
+ firewall
my $foutfile   = shift || 'fwreport.out';   ## formatted output report
my $routfile   = shift || 'fwreport.raw';   ## unformatted output repo
+rt
my $tempdir    = '/tmp/';
my $template   = 'file_XXXXXXXXXX';
my $fqdn_level = 2;


## Eliminate duplicates from input file:
my $attacks = 1;
my ( $fh, $filename1 ) = tempfile($template, DIR=>$tempdir, UNLINK=>1)
+;
{
  open( IN, $infile )  or die "Error opening $infile: $!";
  while(<IN>){
    my %seen;
    for (<IN>){
      ++$attacks;
      ++$seen{$_};
    }
    print $fh sort keys %seen;
  }
  close IN  or die "Error closing $infile: $!";
}


## Ready intermediate file from write to read:
seek $fh,0,0;


## Parse intermediate file:

my (@countries, @domains, @s_svcs, @d_svcs, );
while(<$fh>){


  ## Populate hash with select elements from entry:
  my @elements = split( /\s+/ );
  my %entry;
  for my $element (@elements) {
    my ($key, $value) = split(/=/, $element, 2);
    next unless ($key =~ m/^(IN|OUT|SRC|DST|PROTO|SPT|DPT)$/);
    $entry{$key} = (defined($value) ? $value : '');
  }


  ## Determine what country attack is from:
  my $iana = new Net::Whois::IANA;
  $iana->whois_query(-ip=>$entry{SRC});
    ## leading 2 chars only, avoid CACA, USUS, EU # whole world msgs:
  my $cc = substr($iana->country(), 0, 2);
    ## decode to name:
  my $cn = code2country($cc);
  push(@countries, $cn) if $cn;


  ## Reverse DNS lookup of attacking host:
  my $domain = gethostbyaddr(inet_aton($entry{SRC}),AF_INET);
    ## truncate FQDN down to n-level:
  push @domains,($domain ? $domain =~ m/((?:[^.]+\.?){1,$fqdn_level})$
+/ : $entry{SRC});


  ## Determine name of attacking service:
  my $s_svc = getservbyport( $entry{SPT}, lc($entry{PROTO}) );
  push @s_svcs,($s_svc ? $s_svc : $entry{SPT});


  ## Determine name of attacked service:
  my $d_svc = getservbyport( $entry{DPT}, lc($entry{PROTO}) );
  push @d_svcs,( $d_svc ? $d_svc : $entry{DPT});

}



## Wrap it up:
{
  undef my %saw; @saw{@countries} = (); my @countries_u = sort keys %s
+aw;
  undef    %saw; @saw{@domains}   = (); my @domains_u   = sort keys %s
+aw;
  undef    %saw; @saw{@s_svcs}    = (); my @s_svcs_u    = sort keys %s
+aw;
  undef    %saw; @saw{@d_svcs}    = (); my @d_svcs_u    = sort keys %s
+aw;

  open( OUT, "+> $routfile" )  or die "Error opening $routfile: $!";

  my ($sec,$min,$hour,$mday,$mon,$year,) = localtime(time);
  my $stamp = sprintf(
    "%02d-%02d-%02d   %02d:%02d", $year+1900, $mon+1, $mday, $hour, $m
+in
  );
  print OUT $stamp . "\n"x2;
  print OUT "\n"x2;

  print OUT 'BLOCKED ATTACKS = ' . $attacks . "\n"x2;
  print OUT "\n"x1;

  print OUT 'ATTACKING COUNTRIES = ';
  print OUT my $countries_u = @countries_u . "\n"x2;
  print OUT $_ . '    ' for (@countries_u);
  print OUT "\n"x3;

  my @domains_us = SortAlphaThenNum( @domains_u );
  print OUT 'ATTACKING DOMAINS = ';
  print OUT my $domains_us = @domains_us . "\n"x2;
  print OUT $_ . ', ' for (@domains_us);
  print OUT "\n"x3;

  my @s_svcs_us = SortAlphaThenNum( @s_svcs_u );
  print OUT 'ATTACKING SERVICES = ';
  print OUT my $s_svcs_us = @s_svcs_us . "\n"x2;
  print OUT $_ . ', ' for (@s_svcs_us);
  print OUT "\n"x3;

  my @d_svcs_us = SortAlphaThenNum( @d_svcs_u );
  print OUT 'ATTACKED SERVICES = ';
  print OUT my $d_svcs_us = @d_svcs_us . "\n"x2;
  print OUT $_ . ', ' for (@d_svcs_us);
  print OUT "\n"x3;

  close OUT  or die "Error closing $routfile: $!";
}


## Spruce it up a bit:
{
  my $rawtext = read_file($routfile);
  my $formatted = autoformat $rawtext,{left=>1,right=>72,all=>1,squeez
+e=>0};
  write_file($foutfile, $formatted);
}



######################################################################
+####


## Elements with any alpha before elements with all numeric
sub SortAlphaThenNum {
  my @unsorted = @_;
  return my @sorted =
    map $_->[0],
    sort {
      $a->[1] <=> $b->[1] || $a->[2] cmp $b->[2] ||
      $a->[3] <=> $b->[3] || $a->[0] cmp $b->[0]
    }
    map [ $_, scalar(/^\d/), split /(\d+)/, $_, 2],
    @unsorted
  ;
}


END {
  sleep 1 &&  print "\a" for( 1..3 ); 
}


######################################################################
+####



=head1 TITLE

fwreport

=head1 SYNOPSIS

fwreport infile outfile

=head1 DESCRIPTION

 A lightweight parser for Netfilter logs.
 Produces highly-simplified summary for PHB consumption.

=head1 TODO

   ?combine redundant 'wrap it up' code into sub?

   Sanity-check IN
   Strip leading whitespace from IN:
     s/^\s+//g
   Datetimestamp for filename
   Datestamp of input file to output report
   Getopt::Long and Pod::Usage
     --infile, --outfile, --versions, --help, --man
   Module::Versions::Report conditionally with END{...}
   Test on Win32
   File::Temp instead of $routfile
     File::Slurp may not allow this

=head1 UPDATE

 2003-11-10   14:45 CST
   Post to PerlMonks
   Reduce number and scope of global vars
   PC speaker beeps on completion
   Datestamp of program run to output report
   Sample input and report to pod


 2003-11-08   22:00 CST
   File::Slurp, Text::Autoformat to prettify output report
   Output sort any-alpha first, all-numeric last
   Revise parsing:
     number of (attacks, attackers, ISPs)
     list of (countries, domains, attack(ing|ed) services)
   Parse country codes to names
   Add Module::Versions::Report
   Eliminate duplicates entries from intermediate file
   Print to OUT instead of STDOUT
   Strip all but rightmost 2 or 3 elements of FQDN
   Strip bogus  country code info:
     CACA => CA, USUS => US, EU  # Country is really world wide => EU
   Only print protocol num if no service name
   Eliminate duplicate entries from IN
   Use File::Temp for intermediate file
   Parse src/dst ports to service names:
     Debug TCP is uc while /etc/services is lc
   Read data from IN
   Rename from "whobe"
   Parse input from syslog contents
   Read from file (one IPaddr/line) instead of @ARGV
   Reverse name lookup of source address
   Variable-ize output delimiter

 2003-10-31   17:10 CST
   Initial working code

=head1 TESTED

 Debian                    3.0r1
 Perl                      5.8.0
 strict                    1.02
 warnings                  1.00
 File::Temp                0.14
 Socket                    1.75
 Net::Whois::IANA          0.03
 Locale::Country           2.06
 File::Slurp               2004.0904
 Text::Autoformat          1.12
 Modules::Versions::Report 1.02

=head1 BUGS

 Unclear on root-cause, but IP addresses registered to LACNIC
   occasionally cause errors and lookup failure:
   Use of uninitialized value in scalar chomp at Net/Whois/IANA.pm lin
+e 117.

=head1 CREDITS

 Props ta menolly for gracious reminder of rudimentary perling,
 atcroft for hash-populating aid,
 tye and jmcnamara for es'plaining seek() re: File::Temp write then re
+ad,
 wufnik and Enlil for FQDN truncating idears,
 bart, Enlil, and Limbic~Region for unnatural list sorting with ST,
 dru145 for  www.ginini.com.au/tools/fwlogsum/,
 Lincoln Stein for NPwP,
 And to some guy named vroom.

=head1 AUTHOR

ybiC

=head1 SAMPLE INPUT

 (extraneous fields stripped, to reduce lateral scrolling at PM)

 IN=eth0 OUT= SRC=172.141.50.1 DST=1.2.3.4 SPT=3155 DPT=17300 PROTO=UD
+P
 IN=eth0 OUT= SRC=200.226.91.1 DST=1.2.3.4 SPT=1369 DPT=17300 PROTO=UD
+P
 ACTION=drop-input IN=eth0 OUT= SRC=202.123.79.1 DST=1.2.3.4 SPT=49634
+ DPT=21 PROTO=TCP
 IN=eth0 OUT= SRC=211.162.110.1 DST=1.2.3.4 SPT=30112 DPT=1026 PROTO=T
+CP
 IN=eth0 OUT= SRC=213.25.170.1 DST=1.2.3.4 SPT=4674 DPT=2282 PROTO=TCP
 IN=eth0 OUT= SRC=221.141.148.1 DST=1.2.3.4 SPT=4124 DPT=25 PROTO=TCP
 IN=eth0 OUT= SRC=24.128.81.1 DST=1.2.3.4 SPT=3789 DPT=17300 PROTO=UDP
 IN=eth0 OUT= SRC=24.150.221.1 DST=1.2.3.4 SPT=1900 DPT=17300 PROTO=TC
+P
 IN=eth0 OUT= SRC=24.187.84.1 DST=1.2.3.4 SPT=4789 DPT=593 PROTO=UDP
 IN=eth0 OUT= SRC=24.194.92.1 DST=1.2.3.4 SPT=3540 DPT=17300 PROTO=TCP
 IN=eth0 OUT= SRC=24.214.27.1 DST=1.2.3.4 SPT=4076 DPT=17300 PROTO=TCP
 IN=eth0 OUT= SRC=24.60.217.1 DST=1.2.3.4 SPT=2268 DPT=17300 PROTO=TCP
 IN=eth0 OUT= SRC=61.143.182.1 DST=1.2.3.4 SPT=30110 DPT=1026 PROTO=TC
+P
 IN=eth0 OUT= SRC=61.172.3.1 DST=1.2.3.4 SPT=32997 DPT=1026 PROTO=TCP
 IN=eth0 OUT= SRC=61.250.91.1 DST=1.2.3.4 SPT=58926 DPT=443 PROTO=TCP
 IN=eth0 OUT= SRC=62.59.148.1 DST=1.2.3.4 SPT=666 DPT=1026 PROTO=TCP
 IN=eth0 OUT= SRC=63.83.69.1 DST=1.2.3.4 SPT=4823 DPT=57 PROTO=TCP
 IN=eth0 OUT= SRC=65.130.116.1 DST=1.2.3.4 SPT=2782 DPT=524 PROTO=TCP
 IN=eth0 OUT= SRC=65.50.156.1 DST=1.2.3.4 SPT=2394 DPT=17300 PROTO=TCP
 IN=eth0 OUT= SRC=68.1.179.1 DST=1.2.3.4 SPT=2878 DPT=515 PROTO=TCP
 IN=eth0 OUT= SRC=68.13.16.1 DST=1.2.3.4 SPT=53 DPT=1026 52 PROTO=TCP
 IN=eth0 OUT= SRC=68.16.97.1 DST=1.2.3.4 SPT=65322 DPT=554 PROTO=TCP
 IN=eth0 OUT= SRC=68.16.97.1 DST=1.2.3.4 SPT=65323 DPT=7070 PROTO=TCP
 IN=eth0 OUT= SRC=68.21.37.1 DST=1.2.3.4 SPT=2991 DPT=20168 PROTO=TCP
 IN=eth0 OUT= SRC=68.22.76.1 DST=1.2.3.4 SPT=1379 DPT=20168 PROTO=UDP
 IN=eth0 OUT= SRC=68.3.106.1 DST=1.2.3.4 SPT=3577 DPT=20168 PROTO=UDP
 IN=eth0 OUT= SRC=68.50.191.1 DST=1.2.3.4 SPT=4565 DPT=20168 PROTO=TCP
 IN=eth0 OUT= SRC=68.51.50.1 DST=1.2.3.4 SPT=3425 DPT=20168 PROTO=TCP
 IN=eth0 OUT= SRC=68.83.144.1 DST=1.2.3.4 SPT=1298 DPT=20168 PROTO=TCP
 IN=eth0 OUT= SRC=68.99.193.1 DST=1.2.3.4 SPT=4070 DPT=1026 PROTO=TCP
 IN=eth0 OUT= SRC=68.99.193.1 DST=1.2.3.4 SPT=4070 DPT=1027 PROTO=UDP
 IN=eth0 OUT= SRC=81.166.217.1 DST=1.2.3.4 SPT=3963 DPT=21 PROTO=TCP
 IN=eth0 OUT= SRC=193.108.95.1 DST=1.2.3.4 SPT=80 DPT=1042 PROTO=TCP
 IN=eth0 OUT= SRC=193.108.95.1 DST=1.2.3.4 SPT=80 DPT=1043 PROTO=TCP
 IN=eth0 OUT= SRC=202.12.29.1 DST=1.2.3.4 SPT=43 DPT=38423 PROTO=TCP
 IN=eth0 OUT= SRC=216.239.59.1 DST=1.2.3.4 SPT=80 DPT=1031 PROTO=TCP

=head1 SAMPLE OUTPUT

 2003-11-09   14:55


 BLOCKED ATTACKS = 36


 ATTACKING COUNTRIES = 10

 Australia    Brazil    Canada    China    France    Hong Kong    Kore
+a,
 Republic of    Netherlands    Poland    United States


 ATTACKING DOMAINS = 25

 ameritech.net, apnic.net, attbi.com, bellsouth.net, cgocable.net,
 com.br, comcast.net, cox.net, knology.net, optonline.net, qwest.net,
 rogers.com, rr.com, tiscali.fr, zonnet.nl, 61.143.182.1, 61.172.3.1,
 61.250.91.1, 63.83.69.1, 193.108.95.1, 202.123.79.1,
 211.162.110.1, 213.25.170.1, 216.239.59.1, 221.141.148.1,


 ATTACKING SERVICES = 32

 domain, whois, www, 666, 1298, 1369, 1379, 1900, 2268, 2394, 2782, 28
+78,
 2991, 3425, 3540, 3577, 3789, 3963, 4070, 4076, 4124, 4565, 4674, 478
+9,
 4823, 30110, 30112, 32997, 49634, 58926, 65322, 65323,


 ATTACKED SERVICES = 18

 ftp, https, mtp, printer, smtp, 524, 554, 593, 1026, 1027, 1031, 1042
+,
 1043, 2282, 7070, 17300, 20168, 38423,

=cut

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://305787]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others chilling in the Monastery: (4)
As of 2024-03-19 11:42 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found