| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 | #!/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 9my $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 optionsmy $debug = 0;my $af4 = 0;my $af6 = 0;my $port;#		Long option		Short optionGetOptions(	'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 packetfor (;;) {	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;
 |