#! /usr/bin/env perl # ####################################################################### # Copyright (C) 2013-2015 by Carnegie Mellon University. # # See end of file ####################################################################### # resend-nf-from-pcap.pl : [ ...] # # Extracts the NetFlow v5, NetFlow v9, or IPFIX records from the # specified s and sends them to the specified # :. # ####################################################################### use warnings; use strict; use Getopt::Long; use Pod::Usage; use Socket; use Time::HiRes qw/usleep/; #use Net::Pcap -- this is handlee in an eval block below #use Socket6; -- this is handled in an eval block below # default sleep (in microseconds) between records our $usleep = 0; # only send packets marked as this version of NetFlow/IPFIX. allow # all by default. our $target_version = 0; # number of extra packets to send to flush libfixbuf our $flush_count = 0; # whether to send fragmented packets to the destination our $send_frag = ''; # number of packets to read from the pcap before sending packets to # the destination our $skip_count = 0; # force the hostname to be resolved using IPv4 our $ipv4_host; # reduce the verbosity our $quiet = ''; # number of packets sent to the destination; this is used by the exit # hander, and it is set to 0 once we are connected our $packets_sent; # host:port to send packets to our $host_port; # name of this script our $APP = $0; $APP =~ s,.*/,,; END { if (defined $packets_sent) { print STDERR "$APP: Sent $packets_sent packets to $host_port\n"; } } parse_options(); eval 'use Net::Pcap;'; if ($@) { die "$APP: Cannot find the required Net::Pcap module.\n"; } $host_port = shift @ARGV; if (!$host_port || $host_port !~ /\S+:\d+$/) { die "$APP : \n"; } # Create output socket our $out = open_socket($host_port); # set to zero $packets_sent = 0; # destination values seen in the input my %dest_seen; # process the input for my $f (@ARGV) { my %stats; process_file($f, \%stats); $packets_sent += $stats{sent} if defined $stats{sent}; next if $quiet; for my $key (qw(pkts skip sent)) { $stats{$key} = 0 unless defined $stats{$key}; } $stats{igno} = $stats{pkts} - $stats{skip} - $stats{sent}; printf STDERR "$APP: %s: %u packets, %u skipped, %u ignored, %u sent\n", $f, $stats{pkts}, $stats{skip}, $stats{igno}, $stats{sent}; } # send a final packet to flush fixbuf if ($flush_count) { my %stats; $stats{sent} = 0; send_flush_pkt($flush_count, \%stats); $packets_sent += $stats{sent}; } exit; sub open_socket { my ($host_port_pair) = @_; my $socket; unless ($host_port_pair =~ m/^(.+):(\d+)$/) { die "$APP: Badly formed host:port pair '$host_port_pair'\n"; } my ($host, $port) = ($1, $2); if ($host =~ /:/) { if ($host !~ m/^\[(.+)\]$/) { die "$APP: Must specify IPv6 address as []:\n"; } } $host =~ s/^\[(.+)\]$/$1/; # attempt to load Perl's IPv6 support unless user explicitly # requetsted only IPv4 support if (!$ipv4_host) { # first, see if Socket6 is available, since it is not part of # the Perl CORE distribution eval "require Socket6"; if ($@) { # Socket6 is not available # If $host is an IPv6 address, exit if ($host =~ /:/) { die("$APP: IPv6 host address provided", " but Socket6 Perl module not available\n"); } # Force use of IPv4 $ipv4_host = 1; } else { # Socket6 is available. Use an eval-string to handle # Socket6, so we don't get errors when Socket6 is not # available. eval << 'EOF'; use Socket6 (); my ($s, $family, $socktype, $proto, $saddr, $canonname, @res); @res = Socket6::getaddrinfo($host, $port, AF_UNSPEC, SOCK_DGRAM, scalar(getprotobyname("udp"))); if (scalar(@res) == 1) { die "$APP: Cannot resolve '$host': @res\n"; } $family = -1; while (scalar(@res) >= 5) { ($family, $socktype, $proto, $saddr, $canonname, @res) = @res; my ($thost, $tport) = Socket6::getnameinfo($saddr, (Socket6::NI_NUMERICHOST() | Socket6::NI_NUMERICSERV())); #print STDERR "Trying to connect to \[$thost\]:$tport...\n"; socket($s, $family, $socktype, $proto) || next; connect($s, $saddr) && last; close($s); $family = -1; } if ($family == -1) { die "$APP: Cannot create connection to $host_port_pair\n"; } getpeername($s) or die("$APP: Cannot create connection", " to $host_port_pair\n"); $socket = $s; EOF die "$@" if $@; } } if ($ipv4_host) { # Either IPv4 explicitly requested, or IPv6 support is not # available in Perl my $s; my $addr = inet_aton($host) or die "$APP: Cannot resolve '$host' to an IPv4 address\n"; my $sa = sockaddr_in($port, $addr); socket($s, AF_INET, SOCK_DGRAM, scalar(getprotobyname("udp"))) or die "$APP: Cannot create UDP socket: $!\n"; connect($s, $sa) or die "$APP: Cannot create connection to $host_port_pair: $!\n"; getpeername($s) or die "$APP: Cannot create connection to $host_port_pair\n"; $socket = $s; } return $socket; } sub process_file { my ($file, $stats) = @_; my ($pcap, $err, $pkt, %header); unless ($pcap = Net::Pcap::pcap_open_offline($file, \$err)) { print STDERR "$APP: Cannot read pcap file '$file': $err\n" unless $quiet; return; } print STDERR "$APP: $file...\n" unless $quiet; while ($skip_count > 0 && Net::Pcap::pcap_next_ex($pcap, \%header, \$pkt) > 0) { ++$stats->{pkts}; ++$stats->{skip}; --$skip_count; } if ($skip_count > 0) { Net::Pcap::pcap_close($pcap); return; } while (Net::Pcap::pcap_next_ex($pcap, \%header, \$pkt) > 0) { ++$stats->{pkts}; #print join " ", %header, "\n"; # check that packet is long enough to hold the 14 byte # ethernet header, the 28 byte UDP header, and the 2 byte # netflow version if ($header{caplen} < 44) { print STDERR "$APP: Skipping packet of $header{caplen} bytes\n" unless $quiet; next; } # unpack those 44 bytes my ($s_mac, $d_mac, $ether_type, $vers_hdr_len, $tos, $len, $id, $frags, $ttl, $proto, $chksum, $sip, $dip, $sport, $dport, $udp_len, $upd_chksum, $nf_version) = unpack("a6a6n" . "CCnnnCCn" . "a4a4nnnnn", $pkt); if ($ether_type != 0x0800) { print STDERR "$APP: Skipping non-ethernet packet\n" unless $quiet; next; } if (($vers_hdr_len >> 4) != 4) { print STDERR "$APP: Skipping non-IPv4 packet\n" unless $quiet; next; } if ($proto != 17) { print STDERR "$APP: Skipping non-UDP packet\n" unless $quiet; next; } if ($target_version && $nf_version != $target_version) { print STDERR "$APP: Skipping non-NetFlow v$target_version packet\n" unless $quiet; next; } # Fragment flag of 0x4000 is Don't Fragment if ($frags && ($frags != 0x4000) && !$send_frag) { #printf STDERR "$APP: Skipping packet with fragment flag 0x%x\n", # $frags # unless $quiet; next; } # keep track of destinations, since these tend to cause # netflow sequence number jumps if (!$dest_seen{"$dip$dport"}) { print STDERR ("$APP: New destination: ", join(".", unpack("C4", $dip)), ":$dport\n") unless $quiet; $dest_seen{"$dip$dport"} = 1; } # remove the 14 byte ethernet header and the 28 byte UDP # header substr($pkt, 0, 42) = ""; defined(send($out, $pkt, 0)) or die "$APP: Failed to send to $host_port: $!\n"; ++$stats->{sent}; if ($usleep) { usleep($usleep); } } Net::Pcap::pcap_close($pcap); } sub send_flush_pkt { my ($count, $stats) = @_; my $pkt = pack("nnnnnnnnnn", 5, 0, 0, 0, 0, 0, 0, 0, 0, 0); while ($count > 0) { --$count; defined(send($out, $pkt, 0)) or die "$APP: Failed to send to $host_port: $!\n"; ++$stats->{sent}; } } sub parse_options { my $help; my $man; Getopt::Long::Configure(qw(gnu_getopt)); GetOptions("usleep=i" => \$usleep, "target-version=i" => \$target_version, "flush-count=i" => \$flush_count, "skip-count=i" => \$skip_count, "send-frag" => \$send_frag, "ipv4-host" => \$ipv4_host, "quiet" => \$quiet, "help" => \$help, "man", => \$man, ) or pod2usage(2); pod2usage(-exitstatus=>0, -output=>\*STDOUT) if $help; pod2usage(-exitstatus=>0, -verbose=>2) if $man; pod2usage(2) if @ARGV < 2; } __END__ =pod =head1 NAME resend-nf-from-pcap.pl - NetFlow/IPFIX PCAP Re-player =head1 SYNOPSIS resend-nf-from-pcap.pl [--usleep=SLEEP] [--target-version=NUM] [--flush-count=COUNT] [--skip-count=COUNT] [--send-frag] [--ipv4-host] [--quiet] host:port [ ...] resend-nf-from-pcap.pl --help resend-nf-from-pcap.pl --man =head1 OPTIONS =over 4 =item B<--usleep>=I Pause I microseconds between sending packets to the destination. =item B<--target-version>=I Only resend NetFlow/IPFIX records whose version is I. If this switch is not specified, all NetFlow/IPFIX versions are resent. =item B<--flush-count>=I Send I extra packets after all pcap files have been processed in an effort to flush any records still held by libfixbuf. These extra packets are 16 bytes long and marked as NetFlow v5. =item B<--skip-count>=I Skip the first I packets in the pcap files before beginning to send packets to the destination. =item B<--send-frag> Send fragmented packets to the destnation. Normally, fragmented packets are dropped. =item B<--ipv4-host> Force the destination host to be resolved using IPv4. =item B<--quiet> Do not print warning messages. =item B<--help> Print a brief usage message and exit. =item B<--man> Print the manual page and exit. =back =head1 DESCRIPTION Extracts the NetFlow v5, NetFlow v9, and/or IPFIX records from the specified Is and sends them to the specified I:I pair. Use the B<--target-version> switch to limit which version of NetFlow/IPFIX is sent to the I. Typical versions are 5, 9, and 10 (IPFIX). To specify an IPv6 host address, use "[host]:port", for example, "[::1]:9988". Sending to an IPv6 host is only supported if Perl can locate the Socket6 module, which is available from CPAN. =cut ####################################################################### # Copyright (C) 2013-2015 by Carnegie Mellon University. # # @OPENSOURCE_HEADER_START@ # # Use of the SILK system and related source code is subject to the terms # of the following licenses: # # GNU General Public License (GPL) Rights pursuant to Version 2, June 1991 # Government Purpose License Rights (GPLR) pursuant to DFARS 252.227.7013 # # NO WARRANTY # # ANY INFORMATION, MATERIALS, SERVICES, INTELLECTUAL PROPERTY OR OTHER # PROPERTY OR RIGHTS GRANTED OR PROVIDED BY CARNEGIE MELLON UNIVERSITY # PURSUANT TO THIS LICENSE (HEREINAFTER THE "DELIVERABLES") ARE ON AN # "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY # KIND, EITHER EXPRESS OR IMPLIED AS TO ANY MATTER INCLUDING, BUT NOT # LIMITED TO, WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABILITY, INFORMATIONAL CONTENT, NONINFRINGEMENT, OR ERROR-FREE # OPERATION. CARNEGIE MELLON UNIVERSITY SHALL NOT BE LIABLE FOR INDIRECT, # SPECIAL OR CONSEQUENTIAL DAMAGES, SUCH AS LOSS OF PROFITS OR INABILITY # TO USE SAID INTELLECTUAL PROPERTY, UNDER THIS LICENSE, REGARDLESS OF # WHETHER SUCH PARTY WAS AWARE OF THE POSSIBILITY OF SUCH DAMAGES. # LICENSEE AGREES THAT IT WILL NOT MAKE ANY WARRANTY ON BEHALF OF # CARNEGIE MELLON UNIVERSITY, EXPRESS OR IMPLIED, TO ANY PERSON # CONCERNING THE APPLICATION OF OR THE RESULTS TO BE OBTAINED WITH THE # DELIVERABLES UNDER THIS LICENSE. # # Licensee hereby agrees to defend, indemnify, and hold harmless Carnegie # Mellon University, its trustees, officers, employees, and agents from # all claims or demands made against them (and any related losses, # expenses, or attorney's fees) arising out of, or relating to Licensee's # and/or its sub licensees' negligent use or willful misuse of or # negligent conduct or willful misconduct regarding the Software, # facilities, or other rights or assistance granted by Carnegie Mellon # University under this License, including, but not limited to, any # claims of product liability, personal injury, death, damage to # property, or violation of any laws or regulations. # # Carnegie Mellon University Software Engineering Institute authored # documents are sponsored by the U.S. Department of Defense under # Contract FA8721-05-C-0003. Carnegie Mellon University retains # copyrights in all material produced under this contract. The U.S. # Government retains a non-exclusive, royalty-free license to publish or # reproduce these documents, or allow others to do so, for U.S. # Government purposes only pursuant to the copyright license under the # contract clause at 252.227.7013. # # @OPENSOURCE_HEADER_END@ #######################################################################