Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 280 lines (219 sloc) 6.59 KB
#!/usr/bin/perl -w
# This is a Cisco NetFlow datagram collector
# Netflow protocol reference:
# http://www.cisco.com/en/US/products/sw/netmgtsw/ps1964/products_implementation_design_guide09186a00800d6a11.html
# XXX Doesn't support NetFlow 9
my $af;
BEGIN {
use strict;
use warnings;
use IO qw(Socket);
use Socket;
use Carp;
use POSIX qw(strftime);
use Getopt::Long;
eval "use IO::Socket::INET6;";
eval "use Socket6;";
}
############################################################################
sub timestamp()
{
return strftime "%Y-%m-%dT%H:%M:%S", localtime;
}
sub fuptime($)
{
my $t = shift;
my $r = "";
my $tmp;
# Milliseconds
$tmp = $t % 1000;
$r = sprintf ".%03u%s", $tmp, $r;
# Seconds
$t = int($t / 1000);
$tmp = $t % 60;
$r = "${tmp}s${r}";
# Minutes
$t = int($t / 60);
$tmp = $t % 60;
$r = "${tmp}m${r}" if $tmp;
# Hours
$t = int($t / 60);
$tmp = $t % 24;
$r = "${tmp}h${r}" if $tmp;
# Days
$t = int($t / 24);
$tmp = $t % 7;
$r = "${tmp}d${r}" if $tmp;
# Weeks
$t = int($t / 7);
$tmp = $t % 52;
$r = "${tmp}w${r}" if $tmp;
# Years
$t = int($t / 52);
$r = "${tmp}y${r}" if $tmp;
return $r;
}
sub do_listen($$)
{
my $port = shift
or confess "No UDP port specified";
my $socket;
if ($af == 4) {
$socket = IO::Socket::INET->new(Proto=>'udp', LocalPort=>$port)
or croak "Couldn't open UDP socket: $!";
} elsif ($af == 6) {
$socket = IO::Socket::INET6->new(Proto=>'udp', LocalPort=>$port)
or croak "Couldn't open UDP socket: $!";
} else {
croak "Unsupported AF";
}
return $socket;
}
sub process_nf_v1($$)
{
my $sender = shift;
my $pkt = shift;
my %header;
my %flow;
my $sender_s;
%header = qw();
$sender_s = inet_ntoa($sender) if $af == 4;
$sender_s = inet_ntop(AF_INET6, $sender) if $af == 6;
($header{ver}, $header{flows}, $header{uptime}, $header{secs},
$header{nsecs}) = unpack("nnNNN", $pkt);
if (length($pkt) < (16 + (48 * $header{flows}))) {
printf STDERR timestamp()." Short Netflow v.1 packet: %d < %d\n",
length($pkt), 16 + (48 * $header{flows});
return;
}
printf timestamp() . " HEADER v.%u (%u flow%s)\n", $header{ver},
$header{flows}, $header{flows} == 1 ? "" : "s";
for(my $i = 0; $i < $header{flows}; $i++) {
my $off = 16 + (48 * $i);
my $ptr = substr($pkt, $off, 52);
%flow = qw();
(my $src1, my $src2, my $src3, my $src4,
my $dst1, my $dst2, my $dst3, my $dst4,
my $nxt1, my $nxt2, my $nxt3, my $nxt4,
$flow{in_ndx}, $flow{out_ndx}, $flow{pkts}, $flow{bytes},
$flow{start}, $flow{finish}, $flow{src_port}, $flow{dst_port},
my $pad1, $flow{protocol}, $flow{tos}, $flow{tcp_flags}) =
unpack("CCCCCCCCCCCCnnNNNNnnnCCC", $ptr);
$flow{src} = sprintf "%u.%u.%u.%u", $src1, $src2, $src3, $src4;
$flow{dst} = sprintf "%u.%u.%u.%u", $dst1, $dst2, $dst3, $dst4;
$flow{nxt} = sprintf "%u.%u.%u.%u", $nxt1, $nxt2, $nxt3, $nxt4;
printf timestamp() . " " .
"from %s started %s finish %s proto %u %s:%u > %s:%u %u " .
"packets %u octets\n",
$sender_s,
fuptime($flow{start}), fuptime($flow{finish}),
$flow{protocol},
$flow{src}, $flow{src_port}, $flow{dst}, $flow{dst_port},
$flow{pkts}, $flow{bytes};
}
}
sub process_nf_v5($$)
{
my $sender = shift;
my $pkt = shift;
my %header;
my %flow;
my $sender_s;
%header = qw();
$sender_s = inet_ntoa($sender) if $af == 4;
$sender_s = inet_ntop(AF_INET6, $sender) if $af == 6;
($header{ver}, $header{flows}, $header{uptime}, $header{secs},
$header{nsecs}, $header{flow_seq}, ) = unpack("nnNNNN", $pkt);
if (length($pkt) < (24 + (48 * $header{flows}))) {
printf STDERR timestamp()." Short Netflow v.1 packet: %d < %d\n",
length($pkt), 24 + (48 * $header{flows});
return;
}
printf timestamp() . " HEADER v.%u (%u flow%s) seq %u\n", $header{ver},
$header{flows}, $header{flows} == 1 ? "" : "s", $header{flow_seq};
for(my $i = 0; $i < $header{flows}; $i++) {
my $off = 24 + (48 * $i);
my $ptr = substr($pkt, $off, 52);
%flow = qw();
(my $src1, my $src2, my $src3, my $src4,
my $dst1, my $dst2, my $dst3, my $dst4,
my $nxt1, my $nxt2, my $nxt3, my $nxt4,
$flow{in_ndx}, $flow{out_ndx}, $flow{pkts}, $flow{bytes},
$flow{start}, $flow{finish}, $flow{src_port}, $flow{dst_port},
my $pad1, $flow{tcp_flags}, $flow{protocol}, $flow{tos},
$flow{src_as}, $flow{dst_as},
$flow{src_mask}, $flow{dst_mask}) =
unpack("CCCCCCCCCCCCnnNNNNnnCCCCnnCC", $ptr);
$flow{src} = sprintf "%u.%u.%u.%u", $src1, $src2, $src3, $src4;
$flow{dst} = sprintf "%u.%u.%u.%u", $dst1, $dst2, $dst3, $dst4;
$flow{nxt} = sprintf "%u.%u.%u.%u", $nxt1, $nxt2, $nxt3, $nxt4;
printf timestamp() . " " .
"from %s started %s finish %s proto %u %s:%u > %s:%u %u " .
"packets %u octets\n",
$sender_s,
fuptime($flow{start}), fuptime($flow{finish}),
$flow{protocol},
$flow{src}, $flow{src_port}, $flow{dst}, $flow{dst_port},
$flow{pkts}, $flow{bytes};
}
}
############################################################################
# Commandline options
my $debug = 0;
my $af4 = 0;
my $af6 = 0;
my $port;
# Long option Short option
GetOptions( 'debug+' => \$debug, 'd+' => \$debug,
'4+' => \$af4,
'6+' => \$af6,
'port=i' => \$port, 'p=i' => \$port);
# Unbuffer output
$| = 1;
die "The -4 and -6 are mutually exclusive\n" if $af4 && $af6;
die "You must specify a port (collector.pl -p XXX).\n" unless $port;
$af4 = $af = 4 if $af4 || (!$af4 && !$af6);
$af6 = $af = 6 if $af6;
# These modules aren't standard everywhere, so load them only if necessary
# Main loop - receive and process a packet
for (;;) {
my $socket;
my $from;
my $payload;
my $ver;
my $failcount = 0;
my $netflow;
my $junk;
my $sender;
# Open the listening port if we haven't already
$socket = do_listen($port, $af) unless defined $socket;
# Fetch a packet
$from = $socket->recv($payload, 8192, 0);
($junk, $sender) = unpack_sockaddr_in($from) if $af4;
($junk, $sender) = unpack_sockaddr_in6($from) if $af6;
# Reopen listening socket on error
if (!defined $from) {
$socket->close;
undef $socket;
$failcount++;
die "Couldn't recv: $!\n" if ($failcount > 5);
next; # Socket will be reopened at start of loop
}
if (length($payload) < 16) {
printf STDERR timestamp()." Short packet recevied: %d < 16\n",
length($payload);
next;
}
# The version is always the first 16 bits of the packet
($ver) = unpack("n", $payload);
if ($ver == 1) { process_nf_v1($sender, $payload); }
elsif ($ver == 5) { process_nf_v5($sender, $payload); }
else {
printf STDERR timestamp()." Unsupported netflow version %d\n",
$ver;
next;
}
undef $payload;
next;
}
exit 0;