Add the ability to disable all slicing tests via --no_slicing flag Change submitted by: Jean Tourrilhes <jt@hpl.hp.com>
2296 lines
60 KiB
Perl
2296 lines
60 KiB
Perl
#############################################################
|
|
# vim:set shiftwidth=2 softtabstop=2 expandtab:
|
|
# $Id: TestLib.pm 3654 2008-04-24 06:20:21Z jnaous $
|
|
#
|
|
#
|
|
# NetFPGA test library for sending/receiving packets
|
|
#
|
|
#
|
|
# Invoke using: use Test::TestLib
|
|
#
|
|
# Module provides the ability send and receive packets
|
|
#
|
|
# Revisions:
|
|
#
|
|
##############################################################
|
|
|
|
package Test::TestLib;
|
|
|
|
use strict;
|
|
use Exporter;
|
|
|
|
use Test::Pcap;
|
|
use Test::PacketLib;
|
|
|
|
use threads;
|
|
use threads::shared;
|
|
use Net::RawIP;
|
|
use Getopt::Long qw(GetOptionsFromArray);
|
|
|
|
use vars qw(@ISA @EXPORT); # needed cos strict is on
|
|
|
|
@ISA = ('Exporter');
|
|
@EXPORT = qw(
|
|
&nftest_init
|
|
&nftest_default_controllers
|
|
&nftest_parse_controllers
|
|
&nftest_start
|
|
&nftest_restart
|
|
&nftest_send
|
|
&nftest_expect
|
|
&nftest_finish
|
|
&nftest_get_unexpected
|
|
&nftest_get_missing
|
|
&nftest_precreate_pkts
|
|
&nftest_print_errors
|
|
&nftest_process_iface_map
|
|
&nftest_get_iface
|
|
&nftest_pkt_cap_start
|
|
&nftest_pkt_cap_finish
|
|
&nftest_get_iface_name_map
|
|
&nftest_get_pkt_hashes
|
|
|
|
&nftest_ignore
|
|
&nftest_ignore_ospf
|
|
&nftest_ignore_igmp
|
|
&nftest_ignore_mdns
|
|
&nftest_ignore_arp_request
|
|
|
|
&nftest_create_ICMP_echo_reply
|
|
&nftest_start_vhosts
|
|
&nftest_vhost_expect
|
|
&nftest_print_vhost_errors
|
|
&nftest_create_host
|
|
&nftest_register_router
|
|
&nftest_send_IP
|
|
&nftest_create_IP
|
|
&nftest_send_UDP
|
|
&nftest_send_ARP_req
|
|
&nftest_disable_ARP_reply
|
|
&nftest_send_ICMP_echo_req
|
|
&nftest_expect_ARP_exchange
|
|
&nftest_expect_ARP_request
|
|
&nftest_expect_ARP_reply
|
|
&nftest_get_vhost_mac
|
|
&nftest_expect_ICMP_host_unreach
|
|
&nftest_expect_ICMP_port_unreach
|
|
&nftest_expect_ICMP_proto_unreach
|
|
&nftest_expect_ICMP_network_unreach
|
|
&nftest_expect_ICMP_time_exceeded
|
|
);
|
|
|
|
use constant UNEXPECTED => 1;
|
|
use constant MISSING => -1;
|
|
|
|
use constant ETH_DA_LEN => 6;
|
|
use constant ETH_SA_LEN => 6;
|
|
use constant ETH_TYPE_LEN => 2;
|
|
use constant ETH_MIN_HDR_LEN => ETH_DA_LEN + ETH_SA_LEN + ETH_TYPE_LEN;
|
|
|
|
use constant MIN_PKT_LEN => 60;
|
|
use constant MAX_PKT_LEN => 1514;
|
|
|
|
use constant MODE_NON_VHOST => 1;
|
|
use constant MODE_VHOST => 1;
|
|
|
|
my $mode : shared;
|
|
|
|
# Hash of received/expected packets
|
|
#
|
|
# Packets are keys
|
|
# - Received packets increment the value
|
|
# - Expected packets decrement the value
|
|
my %pktHashes : shared;
|
|
my %pktArrays : shared;
|
|
|
|
# Interface names
|
|
my %ifaceNameMap = ();
|
|
|
|
my @threads;
|
|
|
|
# Masks used to ignore packets
|
|
my %ignoreMasks;
|
|
|
|
# Header values to match in packets
|
|
my %ignoreValues;
|
|
|
|
my %rawIPs;
|
|
|
|
|
|
|
|
# ARP request/reply mask/data
|
|
my $arpMask;
|
|
my $arpReqPkt;
|
|
my $arpReplyPkt;
|
|
|
|
# IGMP packet
|
|
my $igmpMask;
|
|
my $igmpPkt;
|
|
|
|
# MDNS packet
|
|
my $mdnsMask;
|
|
my $mdnsPkt;
|
|
|
|
# OSPF hello mask/data
|
|
my $ospfMask;
|
|
my $ospfPkt;
|
|
|
|
# ICMP packet
|
|
my $icmpMask;
|
|
my $icmpPkt;
|
|
|
|
# ICMP echo
|
|
my $icmpEchoMask;
|
|
my $icmpEchoReqPkt;
|
|
my $icmpEchoReplyPkt;
|
|
|
|
# Create the masks
|
|
&create_masks();
|
|
|
|
# Virtual hosts
|
|
my %vhostsIPiface : shared;
|
|
my %vhostsIPmac : shared;
|
|
my %vhostsRouterIPs;
|
|
my %vhostsKnownIPs;
|
|
my %vhostsPendingPkts : shared;
|
|
my %vhostsARP : shared;
|
|
my %vhostsARPdisable : shared;
|
|
my %vhARPReqLog : shared;
|
|
my %vhARPRepLog : shared;
|
|
|
|
|
|
|
|
###############################################################
|
|
# Name: nftest_default_controllers
|
|
#
|
|
# Returns a string of two default controllers (to support
|
|
# failover testing)
|
|
###############################################################
|
|
sub nftest_default_controllers { return "tcp:127.0.0.1:6633,tcp:127.0.0.1:6634"; }
|
|
|
|
###############################################################
|
|
# Name: nftest_parse_controllers
|
|
#
|
|
# Parses a list of controllers for easy access
|
|
#
|
|
# Arguments: controllers controller parameter string
|
|
#
|
|
# Returns: list of ((proto,host,port)...)
|
|
#
|
|
###############################################################
|
|
sub nftest_parse_controllers {
|
|
# Parse a list of proto:host:port,proto:host:port...
|
|
my @controllers = split(/,/, $_[0]);
|
|
my @default_controllers = split(/,/, nftest_default_controllers());
|
|
my ($proto, $host, $port) = split(/:/, $default_controllers[0]);
|
|
my @results = ();
|
|
foreach my $controller (@controllers) {
|
|
my @params = split(/:/, $controller);
|
|
# Accept "proto:host:port", "host:port", "host", or ""
|
|
if (@params == 1) { $controller = "$proto:$controller:$port"; }
|
|
if (@params == 2) { $controller = "$proto:$controller"; }
|
|
if (@params < 1 || @params > 3) { die "bad controller specified: $controller\n"; }
|
|
@params = split(/:/, $controller);
|
|
# print "parse: adding $params[0], $params[1], $params[2]\n";
|
|
push(@results, @params);
|
|
}
|
|
return @results;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_init
|
|
#
|
|
# Parses commandline arguments
|
|
#
|
|
# Arguments: argv_ref reference to @ARGV
|
|
# active_ports_ref reference to list of port names
|
|
# to listen/send on
|
|
#
|
|
###############################################################
|
|
sub nftest_init {
|
|
my ( $argv_ref, $active_ports_ref ) = @_;
|
|
|
|
my @ARGV = @$argv_ref;
|
|
|
|
my %options = ();
|
|
|
|
$options{'port_base'} = 0;
|
|
$options{'send_delay'} = 100000; # delay in us
|
|
$options{'base_idle'} = 2;
|
|
|
|
unless (
|
|
GetOptionsFromArray(
|
|
\@ARGV,
|
|
\%options,
|
|
"map=s",
|
|
"controller=s",
|
|
"listener=s",
|
|
"port_base=i",
|
|
"send_delay=i",
|
|
"base_idle=i",
|
|
"ignore_byte_count",
|
|
"num_ports=i",
|
|
"common-st-args=s",
|
|
"no_slicing",
|
|
"less_ports"
|
|
)
|
|
)
|
|
{
|
|
usage();
|
|
exit 1;
|
|
}
|
|
|
|
# probably don't need to be changed
|
|
$options{'pkt_len'} = 64;
|
|
$options{'pkt_total'} = 1;
|
|
# Make sure to take send_delay into account - Jean II
|
|
$options{'max_idle'} = $options{'base_idle'} + ($options{'send_delay'} / 1000000);
|
|
|
|
# next in line for generalizing the tests
|
|
$options{'num_ports'} = 4;
|
|
|
|
if ( not defined ($options{'controller'})) {
|
|
$options{'controller'} = nftest_default_controllers();
|
|
print "Using default controller(s) $options{'controller'}\n";
|
|
}
|
|
|
|
# Process the mappings if specified, else use defaults
|
|
if ( defined( $options{'map'} ) ) {
|
|
nftest_process_iface_map( $options{'map'} );
|
|
} else {
|
|
# If not specified on command line, use enviroment variable
|
|
# Jean II
|
|
if (defined($ENV{'OFT_MAP_ETH'})) {
|
|
nftest_process_iface_map( $ENV{OFT_MAP_ETH} );
|
|
} else {
|
|
foreach my $iface (@$active_ports_ref) {
|
|
# add the interface with a default name
|
|
$ifaceNameMap{$iface} = $iface;
|
|
}
|
|
}
|
|
}
|
|
|
|
return %options;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_start
|
|
#
|
|
# Initializes the receivers on the requested ports.
|
|
#
|
|
# Arguments: active_ports_ref reference to list of port names
|
|
# to listen/send on
|
|
#
|
|
###############################################################
|
|
sub nftest_start {
|
|
my ($active_ports_ref) = @_;
|
|
|
|
@threads = ();
|
|
|
|
lock($mode);
|
|
$mode = MODE_NON_VHOST;
|
|
|
|
foreach my $iface (@$active_ports_ref) {
|
|
|
|
# Create the hash to store the received packets
|
|
my $pkts = &share( {} );
|
|
{
|
|
lock %pktHashes;
|
|
$pktHashes{$iface} = $pkts;
|
|
}
|
|
|
|
# Create the capture thread
|
|
push @threads, NF2::Pcap::start( $iface, \&nftest_pkt_arrival, $iface );
|
|
}
|
|
|
|
# Sleep 2 seconds before returning to give threads time to launch
|
|
# If you don't do this PCAP misses packets you send
|
|
sleep(2);
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_restart
|
|
#
|
|
# Resets the packet hashes
|
|
#
|
|
###############################################################
|
|
sub nftest_restart {
|
|
|
|
lock %pktHashes;
|
|
|
|
my @ifaces = keys(%pktHashes);
|
|
|
|
foreach my $iface (@ifaces) {
|
|
|
|
# Create the hash to store the received packets
|
|
my $pkts = &share( {} );
|
|
{
|
|
$pktHashes{$iface} = $pkts;
|
|
}
|
|
}
|
|
}
|
|
|
|
################################################################
|
|
# Name: nftest_pkt_arrival
|
|
#
|
|
# Callback function called when a packet arrives
|
|
#
|
|
# Arguments: dev interface on which packet arrives
|
|
# packet
|
|
#
|
|
# Return:
|
|
################################################################
|
|
sub nftest_pkt_arrival {
|
|
my ( $dev, $packet, $iface ) = @_;
|
|
|
|
# Store the packet in the correct hash
|
|
lock %pktHashes;
|
|
if ( !defined( $pktHashes{$iface} ) ) {
|
|
die "Not currently listening on device $iface. Make sure you called nftest_start before 1.\n";
|
|
}
|
|
my $pkts = $pktHashes{$iface};
|
|
|
|
lock %{$pkts};
|
|
if ( !defined( $$pkts{$packet} ) ) {
|
|
$$pkts{$packet} = 1;
|
|
}
|
|
else {
|
|
my $cnt = ++$$pkts{$packet};
|
|
delete( $$pkts{$packet} ) if ( $cnt == 0 );
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_send
|
|
# Subroutine to send a packet out an interface
|
|
# Arguments: ifaceName string
|
|
# frame (packed string representation of packet)
|
|
# expect optional parameter specifying whether to
|
|
# "expect" the packet
|
|
#
|
|
###############################################################
|
|
sub nftest_send {
|
|
my ( $ifaceName, $frame, $expect ) = @_;
|
|
|
|
# Set the expect flag if it's not already set
|
|
$expect = 1 if ( !defined($expect) );
|
|
|
|
# Check that the interface is valid
|
|
my $iface = getIface($ifaceName);
|
|
|
|
# Check the length of the frame
|
|
if ( length($frame) < ETH_MIN_HDR_LEN ) {
|
|
die "Invalid frame data. Must be at least " . ETH_MIN_HDR_LEN . " bytes";
|
|
}
|
|
|
|
# Set the DA and SA
|
|
#$iface->ethset(
|
|
# source => substr($frame, ETH_DA_LEN, ETH_SA_LEN),
|
|
# dest => substr($frame, 0, ETH_DA_LEN)
|
|
#);
|
|
$iface->{ethpack} = substr( $frame, 0, ETH_MIN_HDR_LEN );
|
|
|
|
# add the packet to the expected queue since it will be caught on the way out
|
|
nftest_expect( $ifaceName, $frame, 1 ) if $expect;
|
|
|
|
# Send the packet
|
|
$iface->send_eth_frame(
|
|
substr( $frame, ETH_DA_LEN + ETH_SA_LEN, length($frame) - ETH_DA_LEN + ETH_SA_LEN ),
|
|
0, 1 );
|
|
}
|
|
|
|
#####################################################################
|
|
# Name: nf2_expect
|
|
# Notify the capture module that we're expecting a packet
|
|
#
|
|
# Arguments: device
|
|
# pkt
|
|
#
|
|
#####################################################################
|
|
sub nftest_expect {
|
|
my ( $dev, $pkt, $noPad ) = @_;
|
|
|
|
my $pkts;
|
|
|
|
$noPad = 0 if ( !defined($noPad) );
|
|
|
|
# Update the packet length if necessary
|
|
my $len = length($pkt);
|
|
if ( $len < MIN_PKT_LEN && !$noPad ) {
|
|
$pkt .= pack( "C*", (0) x ( MIN_PKT_LEN - $len ) );
|
|
}
|
|
|
|
# Verify that something is listening on the requested port
|
|
{
|
|
lock %pktHashes;
|
|
if ( !defined( $pktHashes{$dev} ) ) {
|
|
die
|
|
"Not currently listening on device $dev. Make sure you called nftest_start before 2.\n";
|
|
}
|
|
$pkts = $pktHashes{$dev};
|
|
}
|
|
|
|
# Record the packet
|
|
lock %{$pkts};
|
|
if ( !defined( $$pkts{$pkt} ) ) {
|
|
$$pkts{$pkt} = -1;
|
|
}
|
|
else {
|
|
$$pkts{$pkt}--;
|
|
delete( $$pkts{$pkt} ) if ( $$pkts{$pkt} == 0 );
|
|
}
|
|
|
|
}
|
|
|
|
#####################################################################
|
|
# Name: nftest_get_unexpected
|
|
# Get the unexpected packets that arrived on a given port
|
|
#
|
|
# Arguments: dev
|
|
#####################################################################
|
|
sub nftest_get_unexpected {
|
|
my ($dev) = @_;
|
|
|
|
return get_pkts( $dev, UNEXPECTED );
|
|
}
|
|
|
|
#####################################################################
|
|
# Name: nftest_get_missing
|
|
# Get the unexpected packets that arrived on a given port
|
|
#
|
|
# Arguments: dev
|
|
#####################################################################
|
|
sub nftest_get_missing {
|
|
my ($dev) = @_;
|
|
|
|
return get_pkts( $dev, MISSING );
|
|
}
|
|
|
|
#####################################################################
|
|
# Name: get_pkts
|
|
# Get the unexpected or missing packets that arrived on a given port
|
|
#
|
|
# Arguments: dev
|
|
# type: MISSING/UNEXPECTED
|
|
#####################################################################
|
|
sub get_pkts {
|
|
my ( $dev, $type ) = @_;
|
|
|
|
my @pkts;
|
|
|
|
# Verify that something is listening on the requested port
|
|
if ( !defined( $pktHashes{$dev} ) ) {
|
|
die "Not currently listening on device $dev. Make sure you called nftest_start before 3.\n";
|
|
}
|
|
|
|
# Work out which packets were unexpected
|
|
my $pkts = $pktHashes{$dev};
|
|
foreach my $pkt ( keys(%$pkts) ) {
|
|
for ( my $i = 0 ; $i < $$pkts{$pkt} * $type ; $i++ ) {
|
|
push @pkts, $pkt;
|
|
}
|
|
}
|
|
|
|
return @pkts;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: getIface
|
|
# Get the Net::RawIP object associated with a [pre-map] interface
|
|
# Arguments: iface string
|
|
# Return: Net::RawIP object
|
|
###############################################################
|
|
sub getIface {
|
|
my $ifaceName = shift;
|
|
|
|
die "Interface $ifaceName is not active\n" unless defined $ifaceNameMap{$ifaceName};
|
|
|
|
# Check if the object already exists
|
|
if ( exists( $rawIPs{$ifaceName} ) ) {
|
|
return $rawIPs{$ifaceName};
|
|
}
|
|
|
|
# Convert the values to strings and create the object
|
|
$rawIPs{$ifaceName} = Net::RawIP->new;
|
|
$rawIPs{$ifaceName}->ethnew($ifaceNameMap{$ifaceName} );
|
|
return $rawIPs{$ifaceName};
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_finish
|
|
# Stops listening to the interfaces, and returns to the user
|
|
# the hash of hashes of unmatched packets so she can check them
|
|
# as she wishes.
|
|
# Arguments:
|
|
# Returns: unmatched_pkts_hashes reference to hash of hashes
|
|
###############################################################
|
|
sub nftest_finish {
|
|
|
|
# signal done
|
|
NF2::Pcap::finish();
|
|
|
|
foreach my $thread (@threads) {
|
|
$thread->join();
|
|
}
|
|
|
|
my $i;
|
|
foreach my $ifaceName ( keys %ignoreMasks ) {
|
|
foreach my $pkt ( keys %{ $pktHashes{$ifaceName} } ) {
|
|
my $ignoreMask = $ignoreMasks{$ifaceName};
|
|
my $ignoreValue = $ignoreValues{$ifaceName};
|
|
for ( $i = 0 ; $i < scalar(@$ignoreMask) ; $i++ ) {
|
|
if ( ( $pkt & $ignoreMask->[$i] ) eq ( $ignoreValue->[$i] & $ignoreMask->[$i] ) ) {
|
|
delete $pktHashes{$ifaceName}->{$pkt};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return \%pktHashes;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_ignore
|
|
# sets a mask for packets to ignore. The check is done at the
|
|
# beginning of packets.
|
|
# Arguments: $ifaceName string containing name of interface
|
|
# $mask packed string specifying bits to match
|
|
# $values packed string specifying values to match
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_ignore {
|
|
my $ifaceName = shift;
|
|
|
|
die "Couldn't find interface $ifaceName.\n" unless defined $ifaceNameMap{$ifaceName};
|
|
|
|
if ( !defined $ignoreMasks{$ifaceName} ) {
|
|
$ignoreMasks{$ifaceName} = [];
|
|
$ignoreValues{$ifaceName} = [];
|
|
}
|
|
|
|
push @{ $ignoreMasks{$ifaceName} }, shift;
|
|
push @{ $ignoreValues{$ifaceName} }, shift;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_precreate_pkts
|
|
# creates a list of random sized packets including min and max
|
|
# using the predefined given packed headers and a pdu of random
|
|
# size.
|
|
# Arguments: $num_precreated_pkts int number of packets to precreate
|
|
# $header packed of headers to add
|
|
# $minlen minimum pkt len (optional)
|
|
# $maxlen maximum pkt len (optional)
|
|
# Returns: list of packed packets
|
|
###############################################################
|
|
sub nftest_precreate_pkts {
|
|
my ( $num_precreated, $header, $minlen, $maxlen ) = @_;
|
|
|
|
die "Need to supply number of packets to pre-create.\n" if ( !defined $num_precreated );
|
|
|
|
$minlen = MIN_PKT_LEN if ( !defined($minlen) );
|
|
$minlen = MIN_PKT_LEN if ( $minlen < MIN_PKT_LEN );
|
|
|
|
$maxlen = MAX_PKT_LEN if ( !defined($maxlen) );
|
|
$maxlen = MAX_PKT_LEN if ( $maxlen > MAX_PKT_LEN );
|
|
|
|
my @precreated;
|
|
|
|
# create packets
|
|
for ( my $i = 0 ; $i < $num_precreated - 2 ; $i++ ) {
|
|
my $len = int( rand( $maxlen - $minlen + 1 ) ) + $minlen;
|
|
push @precreated, gen_random_pkt( $len, $header );
|
|
}
|
|
|
|
# precreate min size and max size packets
|
|
push @precreated, gen_random_pkt( $minlen, $header );
|
|
push @precreated, gen_random_pkt( $maxlen, $header );
|
|
|
|
return @precreated;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: gen_random_pkt
|
|
# Generate a random pkt
|
|
# Arguments: $length length of packet data
|
|
# $header optional header to include with packet
|
|
# Returns: random packet data as a packed string
|
|
###############################################################
|
|
sub gen_random_pkt {
|
|
my ( $length, $header ) = @_;
|
|
|
|
$header = "" if ( !defined($header) );
|
|
|
|
# Set the Ethertype to something somewhat reasonable
|
|
if ( length($header) < ETH_MIN_HDR_LEN ) {
|
|
my $pkt = $header . gen_random_payload( $length - length($header) );
|
|
$pkt =
|
|
substr( $pkt, 0, ETH_DA_LEN + ETH_SA_LEN )
|
|
. pack( 'H*', 'FFFF' )
|
|
. substr( $pkt, ETH_MIN_HDR_LEN, $length - ETH_MIN_HDR_LEN );
|
|
return $pkt;
|
|
}
|
|
else {
|
|
return $header . gen_random_payload( $length - length($header) );
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Name: gen_random_payload
|
|
# Generate a random payload
|
|
# Note: If the length is < 0 it returns an empty array
|
|
# Arguments: $length length of packet data
|
|
# Returns: random payload data as a packed string
|
|
###############################################################
|
|
sub gen_random_payload {
|
|
my $length = shift;
|
|
|
|
if ( $length > 0 ) {
|
|
return join( '', ( map { chr( int( rand(256) ) ) } ( 1 .. $length ) ) );
|
|
}
|
|
else {
|
|
return ();
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_print_errors
|
|
# Print the errors (unexpected/missing packets)
|
|
# Arguments: $errors Hash of erroneous packets
|
|
# Returns: total number of errors
|
|
###############################################################
|
|
sub nftest_print_errors {
|
|
my $errors = shift;
|
|
|
|
my $total_errors = 0;
|
|
|
|
# Print out the unmatched packets
|
|
while ( my ( $ifacename, $ref ) = each(%$errors) ) {
|
|
|
|
# See if we actually have errors
|
|
my $ifaceTotal = scalar( keys(%$ref) );
|
|
if ( $ifaceTotal > 0 ) {
|
|
print "Errors seen on $ifacename:\n";
|
|
}
|
|
|
|
my $ifaceCnt = 0;
|
|
my $ifaceErrors = 0;
|
|
while ( my ( $pkt, $count ) = each(%$ref) ) {
|
|
$ifaceCnt++;
|
|
|
|
use bytes;
|
|
my $packet_len = length($pkt);
|
|
no bytes;
|
|
|
|
my $unpacked_pkt = unpack( 'H*', $pkt );
|
|
|
|
if ( $count < 0 ) {
|
|
$count = -$count;
|
|
print "Missing packet len: $packet_len with count $count:\n$unpacked_pkt\n"
|
|
if ( $ifaceCnt < 4 );
|
|
}
|
|
else {
|
|
print "Unexpected packets len: $packet_len with count $count:\n$unpacked_pkt\n"
|
|
if ( $ifaceCnt < 4 );
|
|
}
|
|
print "Skipping remaining errors on $ifacename...\n" if ( $ifaceCnt == 4 );
|
|
$ifaceErrors += $count;
|
|
$total_errors += $count;
|
|
}
|
|
print "Total: $ifaceErrors on $ifacename\n\n" if ( $ifaceTotal > 0 );
|
|
}
|
|
|
|
return $total_errors;
|
|
}
|
|
|
|
|
|
|
|
###############################################################
|
|
# Name: nftest_ignore_ospf
|
|
# Ignore OSPF packets in given interfaces
|
|
# Arguments: @interfaces List of interfaces to ignore OSPF packets
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_ignore_ospf {
|
|
my (@interfaces) = @_;
|
|
|
|
foreach my $iface (@interfaces) {
|
|
nftest_ignore( $iface, $ospfMask, $ospfPkt );
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_ignore_mdns
|
|
# Ignore MDNS packets in given interfaces
|
|
# Arguments: @interfaces List of interfaces to ignore MDNS packets
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_ignore_mdns {
|
|
my (@interfaces) = @_;
|
|
|
|
foreach my $iface (@interfaces) {
|
|
nftest_ignore( $iface, $mdnsMask, $mdnsPkt );
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_ignore_igmp
|
|
# Ignore IGMP packets in given interfaces
|
|
# Arguments: @interfaces List of interfaces to ignore IGMP packets
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_ignore_igmp {
|
|
my (@interfaces) = @_;
|
|
|
|
foreach my $iface (@interfaces) {
|
|
nftest_ignore( $iface, $igmpMask, $igmpPkt );
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_ignore_arp_request
|
|
# Ignore ARP request packets in given interfaces
|
|
# Arguments: @interfaces List of interfaces to ignore ARP request packets
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_ignore_arp_request {
|
|
my (@interfaces) = @_;
|
|
|
|
foreach my $iface (@interfaces) {
|
|
nftest_ignore( $iface, $arpMask, $arpReqPkt );
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Name: validateIP
|
|
# Validate an IP address
|
|
# Arguments: ip Host's IP address
|
|
# Returns: value indicating success
|
|
###############################################################
|
|
sub validateIP {
|
|
my $mac = shift;
|
|
|
|
if ( !( $mac =~ /^(\d+).(\d+).(\d+).(\d+)$/ ) ) {
|
|
return 0;
|
|
}
|
|
|
|
foreach my $val ( $1, $2, $3, $4 ) {
|
|
return 0 if ( $val < 0 || $val > 255 );
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: validateMAC
|
|
# Validate a MAC address
|
|
# Arguments: mac Host's MAC address
|
|
# Returns: value indicating success
|
|
###############################################################
|
|
sub validateMAC {
|
|
my $mac = shift;
|
|
|
|
return ( $mac =~ /^(([0-9a-f]){2}:){5}([0-9a-f]){2}$/i );
|
|
}
|
|
|
|
|
|
|
|
###############################################################
|
|
# Name: isOspfHello
|
|
# Check if a packet is an OSPF hello
|
|
# Arguments: pkt Packet
|
|
# Returns: 1 = OSPF hello
|
|
###############################################################
|
|
sub isOspfHello {
|
|
my $pkt = shift;
|
|
|
|
return ( ( $pkt & $ospfMask ) eq ( $ospfPkt & $ospfMask ) );
|
|
}
|
|
|
|
###############################################################
|
|
# Name: isArpReq
|
|
# Check if a packet is an ARP request
|
|
# Arguments: pkt Packet
|
|
# Returns: 1 = ARP request
|
|
###############################################################
|
|
sub isArpReq {
|
|
my $pkt = shift;
|
|
|
|
return ( ( $pkt & $arpMask ) eq ( $arpReqPkt & $arpMask ) );
|
|
}
|
|
|
|
###############################################################
|
|
# Name: isArpReply
|
|
# Check if a packet is an ARP reply
|
|
# Arguments: pkt Packet
|
|
# Returns: 1 = ARP reply
|
|
###############################################################
|
|
sub isArpReply {
|
|
my $pkt = shift;
|
|
|
|
return ( ( $pkt & $arpMask ) eq ( $arpReplyPkt & $arpMask ) );
|
|
}
|
|
|
|
###############################################################
|
|
# Name: isICMP
|
|
# Check if a packet is an ICMP packet
|
|
# Arguments: pkt Packet
|
|
# Returns: 1 = ICMP packet
|
|
###############################################################
|
|
sub isICMP {
|
|
my $pkt = shift;
|
|
|
|
return ( ( $pkt & $icmpMask ) eq ( $icmpPkt & $icmpMask ) );
|
|
}
|
|
|
|
|
|
###############################################################
|
|
# Name: create_masks
|
|
# Create the various masks
|
|
# Arguments:
|
|
# Returns:
|
|
###############################################################
|
|
sub create_masks {
|
|
|
|
# ARP request/reply mask/data
|
|
$arpMask = NF2::ARP_pkt->new(
|
|
force => 1,
|
|
Ethertype => 0xffff,
|
|
Op => NF2::ARP->ARP_OP_MASK
|
|
)->packed();
|
|
|
|
$arpReqPkt = NF2::ARP_pkt->new( Op => NF2::ARP->ARP_REQUEST )->packed();
|
|
|
|
$arpReplyPkt = NF2::ARP_pkt->new( Op => NF2::ARP->ARP_REPLY )->packed();
|
|
|
|
#MDNS Mask
|
|
$mdnsMask = NF2::IP_pkt->new(
|
|
force => 1,
|
|
DA => "00:00:00:00:00:00",
|
|
dst_ip => "255.255.255.255",
|
|
Ethertype => 0xffff,
|
|
version => 0x0,
|
|
ip_hdr_len => 0x0,
|
|
proto => 0x00,
|
|
dgram_len => 0x0,
|
|
checksum => 0x0,
|
|
)->packed();
|
|
|
|
$mdnsPkt = NF2::IP_pkt->new(
|
|
dst_ip => "224.0.0.251",
|
|
Ethertype => 0x0800,
|
|
)->packed();
|
|
|
|
#IGMP Mask
|
|
$igmpMask = NF2::IP_pkt->new(
|
|
force => 1,
|
|
DA => "00:00:00:00:00:00",
|
|
dst_ip => "255.255.255.255",
|
|
Ethertype => 0xffff,
|
|
version => 0x0,
|
|
ip_hdr_len => 0x0,
|
|
proto => 0x00,
|
|
dgram_len => 0x0,
|
|
checksum => 0x0,
|
|
)->packed();
|
|
|
|
$igmpPkt = NF2::IP_pkt->new(
|
|
dst_ip => "224.0.0.22",
|
|
Ethertype => 0x0800,
|
|
)->packed();
|
|
|
|
# OSPF hello mask/data
|
|
$ospfMask = NF2::IP_pkt->new(
|
|
force => 1,
|
|
DA => "ff:ff:ff:ff:ff:ff",
|
|
dst_ip => "255.255.255.255",
|
|
Ethertype => 0xffff,
|
|
version => 0xf,
|
|
ip_hdr_len => 0x0,
|
|
proto => 0xFF,
|
|
dgram_len => 0x0,
|
|
checksum => 0x0,
|
|
)->packed();
|
|
|
|
$ospfPkt = NF2::IP_pkt->new(
|
|
DA => "ff:ff:ff:ff:ff:ff",
|
|
dst_ip => "224.0.0.5",
|
|
proto => 0x59,
|
|
)->packed();
|
|
|
|
# ICMP packet
|
|
$icmpMask = NF2::IP_pkt->new(
|
|
force => 1,
|
|
Ethertype => 0xffff,
|
|
version => 0xf,
|
|
ip_hdr_len => 0x0,
|
|
proto => 0xFF,
|
|
dgram_len => 0x0,
|
|
ttl => 0x0,
|
|
checksum => 0x0,
|
|
)->packed();
|
|
|
|
$icmpPkt = NF2::IP_pkt->new( proto => 0x01, )->packed();
|
|
|
|
# ICMP echo request
|
|
$icmpEchoMask = NF2::ICMP_pkt->new(
|
|
force => 1,
|
|
Ethertype => 0xffff,
|
|
version => 0xf,
|
|
ip_hdr_len => 0x0,
|
|
proto => 0xFF,
|
|
dgram_len => 0x0,
|
|
ttl => 0x0,
|
|
checksum => 0x0,
|
|
ICMP_checksum => 0x0,
|
|
Type => 0xff,
|
|
);
|
|
|
|
$icmpEchoReqPkt = NF2::ICMP_pkt->new( Type => NF2::ICMP->ECHO_REQ, )->packed();
|
|
|
|
$icmpEchoReplyPkt = NF2::ICMP_pkt->new( Type => NF2::ICMP->ECHO_REP, )->packed();
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_create_ICMP_echo_reply
|
|
# Generate an ICMP echo reply message
|
|
# Arguments: req ICMP echo request
|
|
# Returns: ICMP echo reply message
|
|
###############################################################
|
|
sub nftest_create_ICMP_echo_reply {
|
|
my $req = shift;
|
|
|
|
return NF2::ICMP_pkt->new_echo_reply( Request => $req );
|
|
}
|
|
|
|
|
|
|
|
###############################################################
|
|
# Name: nftest_process_iface_map
|
|
#
|
|
# Read a map file that maps interfaces
|
|
#
|
|
###############################################################
|
|
sub nftest_process_iface_map {
|
|
my $mapFile = shift;
|
|
|
|
# Verify the precence of the mapfile
|
|
if ( !-f $mapFile ) {
|
|
die("Cannot locate interface map file $mapFile");
|
|
}
|
|
|
|
open MAPFILE, $mapFile;
|
|
while (<MAPFILE>) {
|
|
chomp;
|
|
|
|
# Remove comments and leading white space
|
|
s/#.*//;
|
|
s/^\s\+//;
|
|
|
|
# Skip blank lines
|
|
next if /^$/;
|
|
|
|
# Work out if we've got something that looks like a mapping
|
|
if (/^(\w+):\s*(\w+)$/) {
|
|
$ifaceNameMap{$1} = $2;
|
|
}
|
|
}
|
|
close MAPFILE;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_get_iface
|
|
#
|
|
# Get an interface (may be mapped)
|
|
#
|
|
###############################################################
|
|
sub nftest_get_iface {
|
|
my $iface = shift;
|
|
|
|
if ( defined( $ifaceNameMap{$iface} ) ) {
|
|
return $ifaceNameMap{$iface};
|
|
}
|
|
else {
|
|
return $iface;
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_pkt_cap_start
|
|
#
|
|
# Initializes the receivers on the requested ports.
|
|
#
|
|
# Arguments: active_ports_ref reference to list of port names
|
|
# to listen/send on
|
|
#
|
|
###############################################################
|
|
sub nftest_pkt_cap_start {
|
|
my ($active_ports_ref) = @_;
|
|
|
|
@threads = ();
|
|
|
|
lock($mode);
|
|
$mode = MODE_NON_VHOST;
|
|
|
|
foreach my $iface (@$active_ports_ref) {
|
|
# Create the hash to store the received packets
|
|
my $pkts = &share( [] );
|
|
{
|
|
lock %pktArrays;
|
|
$pktArrays{$iface} = $pkts;
|
|
}
|
|
|
|
my $ifaceReal = $ifaceNameMap{$iface};
|
|
|
|
# Create the capture thread
|
|
push @threads, NF2::Pcap::start($ifaceReal, \&nftest_pkt_cap_arrival, $iface);
|
|
}
|
|
|
|
# Sleep 2 seconds before returning to give threads time to launch
|
|
# If you don't do this PCAP misses packets you send
|
|
sleep(2);
|
|
}
|
|
|
|
################################################################
|
|
# Name: nftest_pkt_cap_arrival
|
|
#
|
|
# Callback function called when a packet arrives
|
|
#
|
|
# Arguments: dev interface on which packet arrives
|
|
# packet
|
|
#
|
|
# Return:
|
|
################################################################
|
|
sub nftest_pkt_cap_arrival {
|
|
my ($dev, $packet, $iface) = @_;
|
|
|
|
# Store the packet in the correct hash
|
|
lock %pktArrays;
|
|
if (!defined($pktArrays{$iface})) {
|
|
die "Not currently listening on device $iface. Make sure you called nftest_start before 4.\n";
|
|
}
|
|
|
|
push @{ $pktArrays{$iface} }, $packet;
|
|
|
|
#print "after\n";
|
|
#my @temp = @{ $pktArrays{"eth1"} };
|
|
#print @temp;
|
|
#print "\n";
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_pkt_cap_finish
|
|
# Stops listening to the interfaces, and returns to the user
|
|
# the hash of hashes of unmatched packets so she can check them
|
|
# as she wishes.
|
|
# Arguments:
|
|
# Returns: unmatched_pkts_hashes reference to hash of hashes
|
|
###############################################################
|
|
sub nftest_pkt_cap_finish {
|
|
# signal done
|
|
NF2::Pcap::finish();
|
|
|
|
foreach my $thread (@threads) {
|
|
$thread->join();
|
|
}
|
|
|
|
#for my $intface ( keys %pktArrays ) {
|
|
# print "$intface: @{ $pktArrays{$intface} }\n";
|
|
#}
|
|
|
|
return %pktArrays;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_get_iface_name_map
|
|
#
|
|
# Get copy of iface_name_map hash
|
|
#
|
|
#############################################################
|
|
sub nftest_get_iface_name_map {
|
|
return %ifaceNameMap;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_get_iface_name_map
|
|
#
|
|
# Get copy of pktHashes hash
|
|
#
|
|
#############################################################
|
|
sub nftest_get_pkt_hashes {
|
|
return %pktHashes;
|
|
}
|
|
|
|
#***********************
|
|
|
|
###############################################################
|
|
# Name: nftest_start_vhosts
|
|
#
|
|
# Initializes the receivers on the requested ports for a virtual
|
|
# host setup
|
|
#
|
|
# Arguments: active_ports_ref reference to list of port names
|
|
# to listen/send on
|
|
#
|
|
###############################################################
|
|
sub nftest_start_vhosts {
|
|
my ($active_ports_ref) = @_;
|
|
|
|
@threads = ();
|
|
|
|
lock($mode);
|
|
$mode = MODE_VHOST;
|
|
|
|
foreach my $iface (@$active_ports_ref) {
|
|
|
|
# Create the hash to store the received packets
|
|
my $pkts = &share( {} );
|
|
{
|
|
lock %pktHashes;
|
|
$pktHashes{$iface} = $pkts;
|
|
}
|
|
|
|
my %ifaceNameMap = nftest_get_iface_name_map();
|
|
my $ifaceReal = $ifaceNameMap{$iface};
|
|
|
|
# Create the capture thread
|
|
push @threads, NF2::Pcap::start( $ifaceReal, \&nftest_vhost_pkt_arrival, $iface );
|
|
}
|
|
|
|
# Sleep 2 seconds before returning to give threads time to launch
|
|
# If you don't do this PCAP misses packets you send
|
|
sleep(2);
|
|
}
|
|
|
|
################################################################
|
|
# Name: nftest_vhost_pkt_arrival
|
|
#
|
|
# Callback function called when a packet arrives
|
|
#
|
|
# Arguments: dev interface on which packet arrives
|
|
# packet
|
|
#
|
|
# Return:
|
|
################################################################
|
|
sub nftest_vhost_pkt_arrival {
|
|
my ( $devReal, $packet, $iface ) = @_;
|
|
|
|
# Work out if what sort of packet we're dealing with
|
|
if ( isArpReq($packet) ) {
|
|
|
|
#print "Arp req: " . unpack('H*', substr($packet, 0, 32)) . "\n";
|
|
processArpRequest( $iface, $packet );
|
|
}
|
|
elsif ( isArpReply($packet) ) {
|
|
|
|
#print "Arp reply: " . unpack('H*', substr($packet, 0, 32)) . "\n";
|
|
processArpReply( $iface, $packet );
|
|
}
|
|
elsif ( isOspfHello($packet) ) {
|
|
|
|
#print "OSPF Hello: " . unpack('H*', substr($packet, 0, 32)) . "\n";
|
|
#processArpReply($iface, $packet);
|
|
}
|
|
|
|
#elsif (isICMP($packet)) {
|
|
# print "ICMP: " . unpack('H*', substr($packet, 0, 32)) . "\n";
|
|
# #processArpReply($iface, $packet);
|
|
#}
|
|
else {
|
|
|
|
#print "Unknown packet: " . unpack('H*', substr($packet, 0, 32)) . "\n";
|
|
|
|
# Store the packet in the correct hash
|
|
lock %pktHashes;
|
|
if ( !defined( $pktHashes{$iface} ) ) {
|
|
die
|
|
"Not currently listening on device $iface. Make sure you called nftest_start_vhosts before.\n";
|
|
}
|
|
my $pkts = $pktHashes{$iface};
|
|
|
|
lock %{$pkts};
|
|
if ( !defined( $$pkts{$packet} ) ) {
|
|
$$pkts{$packet} = 1;
|
|
}
|
|
else {
|
|
my $cnt = ++$$pkts{$packet};
|
|
delete( $$pkts{$packet} ) if ( $cnt == 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
#####################################################################
|
|
# Name: nftest_vhost_expect
|
|
# Notify the capture module that we're expecting a packet
|
|
#
|
|
# Arguments: host
|
|
# pkt
|
|
#
|
|
#####################################################################
|
|
sub nftest_vhost_expect {
|
|
my ( $host, $pkt ) = @_;
|
|
|
|
# Ensure all params look valid
|
|
if ( !validateIP($host) ) {
|
|
die "Invalid host IP address '$host'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
|
|
# Verify that both IPs are known
|
|
if ( !defined( $vhostsIPiface{$host} ) ) {
|
|
die "Unknown host IP address '$host'";
|
|
}
|
|
|
|
# Get the iface
|
|
my $iface = $vhostsIPiface{$host};
|
|
|
|
# Call the other expect function
|
|
nftest_expect( $iface, $pkt, 1 );
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_print_vhost_errors
|
|
# Print the errors (unexpected/missing packets)
|
|
# Arguments: $errors Hash of erroneous packets
|
|
# Returns: total number of errors
|
|
###############################################################
|
|
sub nftest_print_vhost_errors {
|
|
my $total_errors = 0;
|
|
|
|
# Print out the unmatched packets
|
|
while ( my ( $ifacename, $ref ) = each(%pktHashes) ) {
|
|
|
|
# See if we actually have errors
|
|
my $ifaceTotal = scalar( keys(%$ref) );
|
|
if ( $ifaceTotal > 0 ) {
|
|
print "Errors seen on $ifacename:\n";
|
|
}
|
|
|
|
my $ifaceCnt = 0;
|
|
my $ifaceErrors = 0;
|
|
while ( my ( $pkt, $count ) = each(%$ref) ) {
|
|
$ifaceCnt++;
|
|
|
|
my $unpacked_pkt = unpack( 'H*', $pkt );
|
|
if ( length($unpacked_pkt) > 32 ) {
|
|
$unpacked_pkt = substr( $unpacked_pkt, 0, 32 ) . "...";
|
|
}
|
|
if ( $count < 0 ) {
|
|
$count = -$count;
|
|
print "Missing packet with count $count : $unpacked_pkt\n" if ( $ifaceCnt < 4 );
|
|
}
|
|
else {
|
|
print "Unexpected packets with count $count: $unpacked_pkt\n" if ( $ifaceCnt < 4 );
|
|
}
|
|
print "Skipping remaining errors on $ifacename...\n" if ( $ifaceCnt == 4 );
|
|
$ifaceErrors += $count;
|
|
$total_errors += $count;
|
|
}
|
|
print "Total: $ifaceErrors on $ifacename\n\n" if ( $ifaceTotal > 0 );
|
|
}
|
|
|
|
# Print out ARP errors
|
|
foreach my $pair ( sort( keys(%vhARPReqLog) ) ) {
|
|
|
|
# ARP errors indicated when the cnt does not have a .5 at the end
|
|
my $cnt = $vhARPReqLog{$pair};
|
|
if ( int($cnt) == $cnt || $cnt < 1 ) {
|
|
|
|
# Work out the source and dest
|
|
my ( $src, $dst ) = split( /:/, $pair );
|
|
|
|
# Get the interface
|
|
my $iface = $vhostsIPiface{$src};
|
|
$iface =~ s/:rtr//;
|
|
|
|
# Check if there's a corresponding error in the reply table
|
|
my $rep = defined( $vhARPRepLog{"$dst:$src"} );
|
|
my $repError = 0;
|
|
if ($rep) {
|
|
my $repCnt = $vhARPRepLog{"$dst:$src"};
|
|
$repError = ( int($repCnt) == $repCnt || $repCnt < 1 );
|
|
delete( $vhARPRepLog{"$dst:$src"} );
|
|
}
|
|
|
|
# Work out the error
|
|
my $errorStr = $cnt < 1 ? "Missing" : "Unexpected";
|
|
|
|
# Print the error
|
|
if ($repError) {
|
|
print "$errorStr ARP request/reply pair from $src to $dst on $iface\n";
|
|
}
|
|
else {
|
|
print "$errorStr ARP request from $src to $dst on $iface\n";
|
|
}
|
|
|
|
$total_errors++;
|
|
}
|
|
}
|
|
|
|
# Print out ARP errors
|
|
foreach my $pair ( sort( keys(%vhARPRepLog) ) ) {
|
|
|
|
# ARP errors indicated when the cnt does not have a .5 at the end
|
|
my $cnt = $vhARPRepLog{$pair};
|
|
if ( int($cnt) == $cnt || $cnt < 1 ) {
|
|
|
|
# Work out the source and dest
|
|
my ( $src, $dst ) = split( /:/, $pair );
|
|
|
|
# Get the interface
|
|
my $iface = $vhostsIPiface{$src};
|
|
$iface =~ s/:rtr//;
|
|
|
|
# Work out the error
|
|
my $errorStr = $cnt < 1 ? "Missing" : "Unexpected";
|
|
|
|
# Print the error
|
|
print "$errorStr ARP reply from $src to $dst on $iface\n";
|
|
|
|
$total_errors++;
|
|
}
|
|
}
|
|
|
|
return $total_errors;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_create_host
|
|
# Create a "virtual" host on the test network
|
|
# Arguments: iface Interface on which the host lives
|
|
# mac Host's MAC address
|
|
# ip Host's IP address
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_create_host {
|
|
my ( $iface, $mac, $ip ) = @_;
|
|
|
|
# Ensure all params look valid
|
|
my %ifaceNameMap = nftest_get_iface_name_map();
|
|
if ( !defined( $ifaceNameMap{$iface} ) ) {
|
|
die "Interface $iface is not known";
|
|
}
|
|
if ( !( $mac =~ /^(([0-9a-f]){2}:){5}([0-9a-f]){2}$/i ) ) {
|
|
die "Invalid MAC address '$mac'. Should look like '00:ca:fe:00:01:01'";
|
|
}
|
|
if ( !validateIP($ip) ) {
|
|
die "Invalid IP address '$ip'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
lock(%vhostsIPmac);
|
|
|
|
# Verify that the host doesn't already exist
|
|
if ( defined( $vhostsIPiface{$ip} ) ) {
|
|
die "Virtual host with IP address '$ip' already exists";
|
|
}
|
|
|
|
# Insert the virtual host in the table
|
|
$vhostsIPiface{$ip} = $iface;
|
|
$vhostsIPmac{$ip} = $mac;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_register_router
|
|
# Register the IP for a given interface
|
|
# Arguments: iface Interface on which hosts live
|
|
# ip IP address of the router
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_register_router {
|
|
my ( $iface, $mac, $ip ) = @_;
|
|
|
|
# Ensure all params look valid
|
|
my %ifaceNameMap = nftest_get_iface_name_map();
|
|
if ( !defined( $ifaceNameMap{$iface} ) ) {
|
|
die "Interface $iface is not known";
|
|
}
|
|
if ( !validateIP($ip) ) {
|
|
die "Invalid IP address '$ip'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
lock(%vhostsIPmac);
|
|
|
|
# Verify that the host doesn't already exist
|
|
if ( defined( $vhostsIPiface{$ip} ) ) {
|
|
die "Virtual host with IP address '$ip' already exists";
|
|
}
|
|
|
|
# Insert the virtual host in the table
|
|
$vhostsRouterIPs{$iface} = $ip;
|
|
$vhostsIPiface{$ip} = "$iface:rtr";
|
|
$vhostsIPmac{$ip} = "$mac";
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_send_IP
|
|
# Send an IP packet between two virtual hosts
|
|
# Arguments: src IP address of source
|
|
# dst IP address of destination
|
|
# arg optional hash of parameters to pass into
|
|
# the IP packet creation function
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_send_IP {
|
|
my ( $src, $dst, %arg ) = @_;
|
|
|
|
# Ensure all params look valid
|
|
if ( !validateIP($src) ) {
|
|
die "Invalid source IP address '$src'. Should look like '192.168.0.1'";
|
|
}
|
|
if ( !validateIP($dst) ) {
|
|
die "Invalid dest IP address '$dst'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
lock(%vhostsIPmac);
|
|
lock(%vhostsARP);
|
|
|
|
# Verify that both IPs are known
|
|
if ( !defined( $vhostsIPiface{$src} ) ) {
|
|
die "Unknown source IP address '$src'";
|
|
}
|
|
if ( !defined( $vhostsIPiface{$dst} ) ) {
|
|
die "Unknown dest IP address '$dst'";
|
|
}
|
|
|
|
# Get the MAC of the source
|
|
my $srcMac = $vhostsIPmac{$src};
|
|
|
|
# Get the ifaces of both IPs
|
|
my $srcIface = $vhostsIPiface{$src};
|
|
my $dstIface = $vhostsIPiface{$dst};
|
|
|
|
# Verify that they are on different interfaces
|
|
return if ( $srcIface eq $dstIface );
|
|
|
|
# Get the IP of the router node for the src
|
|
my $nextHop = $vhostsRouterIPs{$srcIface};
|
|
|
|
# Construct the ARP table string
|
|
my $arpStr = "$src:$nextHop";
|
|
|
|
# Check if we know the next hop's MAC address
|
|
my $macKnown = defined( $vhostsARP{$arpStr} );
|
|
|
|
# Get the MAC address of the next hop (Note: this may not actually
|
|
# be known at this stage by the virtual host. We'll fill it in now
|
|
# but maybe send later.)
|
|
my $nextHopMac = $vhostsIPmac{$nextHop};
|
|
|
|
# Construct the IP packet
|
|
$arg{DA} = $nextHopMac;
|
|
$arg{SA} = $srcMac;
|
|
$arg{src_ip} = $src;
|
|
$arg{dst_ip} = $dst;
|
|
$arg{ttl} = 64 if ( !defined( $arg{ttl} ) );
|
|
$arg{len} = 60 if ( !defined( $arg{len} ) );
|
|
|
|
my $ippkt = new NF2::IP_pkt(%arg);
|
|
|
|
# Send or store the packet
|
|
if ($macKnown) {
|
|
nftest_send( $srcIface, $ippkt->packed() );
|
|
}
|
|
else {
|
|
lock(%vhostsPendingPkts);
|
|
|
|
# Store the ip packet
|
|
my $pkts;
|
|
if ( defined( $vhostsPendingPkts{$arpStr} ) ) {
|
|
$pkts = $vhostsPendingPkts{$arpStr};
|
|
}
|
|
else {
|
|
$pkts = &share( [] );
|
|
$vhostsPendingPkts{$arpStr} = $pkts;
|
|
|
|
# Generate an ARP packet
|
|
my $arppkt = NF2::ARP_pkt->new_request(
|
|
SA => $srcMac,
|
|
SenderIpAddr => $src,
|
|
TargetIpAddr => $nextHop,
|
|
);
|
|
nftest_send( $srcIface, $arppkt->packed(), 0 );
|
|
}
|
|
|
|
lock($pkts);
|
|
push @$pkts, $ippkt->packed;
|
|
|
|
# Record the pending packet
|
|
nftest_expect( $srcIface, $ippkt->packed, 1 );
|
|
}
|
|
|
|
return $ippkt;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_create_IP
|
|
# Create an IP packet to send between two virtual hosts (but don't
|
|
# actually send)
|
|
# Arguments: src IP address of source
|
|
# dst IP address of destination
|
|
# arg optional hash of parameters to pass into
|
|
# the IP packet creation function
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_create_IP {
|
|
my ( $src, $dst, %arg ) = @_;
|
|
|
|
# Ensure all params look valid
|
|
if ( !validateIP($src) ) {
|
|
die "Invalid source IP address '$src'. Should look like '192.168.0.1'";
|
|
}
|
|
if ( !validateIP($dst) ) {
|
|
die "Invalid dest IP address '$dst'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
lock(%vhostsIPmac);
|
|
lock(%vhostsARP);
|
|
|
|
# Verify that both IPs are known
|
|
if ( !defined( $vhostsIPiface{$src} ) ) {
|
|
die "Unknown source IP address '$src'";
|
|
}
|
|
if ( !defined( $vhostsIPiface{$dst} ) ) {
|
|
die "Unknown dest IP address '$dst'";
|
|
}
|
|
|
|
# Get the MAC of the source
|
|
my $srcMac = $vhostsIPmac{$src};
|
|
|
|
# Get the ifaces of both IPs
|
|
my $srcIface = $vhostsIPiface{$src};
|
|
my $dstIface = $vhostsIPiface{$dst};
|
|
|
|
# Check to see if the two devices are on different interfaces
|
|
my $nextHopMac = $vhostsIPmac{$dst};
|
|
if ( $srcIface ne $dstIface ) {
|
|
|
|
# Get the IP of the router node for the src
|
|
my $nextHop = $vhostsRouterIPs{$srcIface};
|
|
|
|
# Get the MAC address of the next hop (Note: this may not actually
|
|
# be known at this stage by the virtual host. We'll fill it in now
|
|
# but maybe send later.)
|
|
$nextHopMac = $vhostsIPmac{$nextHop};
|
|
}
|
|
|
|
# Construct the IP packet
|
|
$arg{DA} = $nextHopMac;
|
|
$arg{SA} = $srcMac;
|
|
$arg{src_ip} = $src;
|
|
$arg{dst_ip} = $dst;
|
|
$arg{ttl} = 64 if ( !defined( $arg{ttl} ) );
|
|
$arg{len} = 60 if ( !defined( $arg{len} ) );
|
|
|
|
my $ippkt = new NF2::IP_pkt(%arg);
|
|
|
|
return $ippkt;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_send_UDP
|
|
# Send an UDP packet between two virtual hosts
|
|
# Arguments: src IP address of source
|
|
# dst IP address of destination
|
|
# arg optional hash of parameters to pass into
|
|
# the UDP packet creation function
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_send_UDP {
|
|
my ( $src, $dst, %arg ) = @_;
|
|
|
|
# Ensure all params look valid
|
|
if ( !validateIP($src) ) {
|
|
die "Invalid source IP address '$src'. Should look like '192.168.0.1'";
|
|
}
|
|
if ( !validateIP($dst) ) {
|
|
die "Invalid dest IP address '$dst'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
lock(%vhostsIPmac);
|
|
lock(%vhostsARP);
|
|
|
|
# Verify that both IPs are known
|
|
if ( !defined( $vhostsIPiface{$src} ) ) {
|
|
die "Unknown source IP address '$src'";
|
|
}
|
|
if ( !defined( $vhostsIPiface{$dst} ) ) {
|
|
die "Unknown dest IP address '$dst'";
|
|
}
|
|
|
|
# Get the MAC of the source
|
|
my $srcMac = $vhostsIPmac{$src};
|
|
|
|
# Get the ifaces of both IPs
|
|
my $srcIface = $vhostsIPiface{$src};
|
|
my $dstIface = $vhostsIPiface{$dst};
|
|
|
|
# Verify that they are on different interfaces
|
|
return if ( $srcIface eq $dstIface );
|
|
|
|
# Get the IP of the router node for the src
|
|
my $nextHop = $vhostsRouterIPs{$srcIface};
|
|
|
|
# Construct the ARP table string
|
|
my $arpStr = "$src:$nextHop";
|
|
|
|
# Check if we know the next hop's MAC address
|
|
my $macKnown = defined( $vhostsARP{$arpStr} );
|
|
|
|
# Get the MAC address of the next hop (Note: this may not actually
|
|
# be known at this stage by the virtual host. We'll fill it in now
|
|
# but maybe send later.)
|
|
my $nextHopMac = $vhostsIPmac{$nextHop};
|
|
|
|
# Construct the IP packet
|
|
$arg{DA} = $nextHopMac;
|
|
$arg{SA} = $srcMac;
|
|
$arg{src_ip} = $src;
|
|
$arg{dst_ip} = $dst;
|
|
$arg{ttl} = 64 if ( !defined( $arg{ttl} ) );
|
|
$arg{len} = 60 if ( !defined( $arg{len} ) );
|
|
|
|
my $udppkt = new NF2::UDP_pkt(%arg);
|
|
|
|
# Send or store the packet
|
|
if ($macKnown) {
|
|
nftest_send( $srcIface, $udppkt->packed() );
|
|
}
|
|
else {
|
|
lock(%vhostsPendingPkts);
|
|
|
|
# Store the ip packet
|
|
my $pkts;
|
|
if ( defined( $vhostsPendingPkts{$arpStr} ) ) {
|
|
$pkts = $vhostsPendingPkts{$arpStr};
|
|
}
|
|
else {
|
|
$pkts = &share( [] );
|
|
$vhostsPendingPkts{$arpStr} = $pkts;
|
|
|
|
# Generate an ARP packet
|
|
my $arppkt = NF2::ARP_pkt->new_request(
|
|
SA => $srcMac,
|
|
SenderIpAddr => $src,
|
|
TargetIpAddr => $nextHop,
|
|
);
|
|
nftest_send( $srcIface, $arppkt->packed(), 0 );
|
|
}
|
|
|
|
lock($pkts);
|
|
push @$pkts, $udppkt->packed;
|
|
|
|
# Record the pending packet
|
|
nftest_expect( $srcIface, $udppkt->packed, 1 );
|
|
}
|
|
|
|
return $udppkt;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_send_ARP_req
|
|
# Send an ARP packet between two virtual hosts
|
|
# Arguments: src IP address of source
|
|
# dst IP address of destination
|
|
# arg optional hash of parameters to pass into
|
|
# the IP packet creation function
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_send_ARP_req {
|
|
my ( $src, $dst, %arg ) = @_;
|
|
|
|
# Ensure all params look valid
|
|
if ( !validateIP($src) ) {
|
|
die "Invalid source IP address '$src'. Should look like '192.168.0.1'";
|
|
}
|
|
if ( !validateIP($dst) ) {
|
|
die "Invalid dest IP address '$dst'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
lock(%vhostsIPmac);
|
|
lock(%vhostsARP);
|
|
|
|
# Verify that both IPs are known
|
|
if ( !defined( $vhostsIPiface{$src} ) ) {
|
|
die "Unknown source IP address '$src'";
|
|
}
|
|
if ( !defined( $vhostsIPiface{$dst} ) ) {
|
|
die "Unknown dest IP address '$dst'";
|
|
}
|
|
|
|
# Get the MAC of the source
|
|
my $srcMac = $vhostsIPmac{$src};
|
|
|
|
# Get the ifaces of both IPs
|
|
my $srcIface = $vhostsIPiface{$src};
|
|
my $dstIface = $vhostsIPiface{$dst};
|
|
|
|
# Verify that they are on different interfaces (one hopefully has rtr as a postfix)
|
|
return if ( $srcIface eq $dstIface );
|
|
|
|
# Drop the rtr postfix
|
|
$srcIface =~ s/:rtr//;
|
|
$dstIface =~ s/:rtr//;
|
|
|
|
# Verify that they are actually connected
|
|
return if ( $srcIface ne $dstIface );
|
|
|
|
# Generate an ARP packet
|
|
my $arppkt = NF2::ARP_pkt->new_request(
|
|
SA => $srcMac,
|
|
SenderIpAddr => $src,
|
|
TargetIpAddr => $dst,
|
|
);
|
|
nftest_send( $srcIface, $arppkt->packed(), 0 );
|
|
|
|
# Return the ARP packet
|
|
return $arppkt;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_disable_ARP_reply
|
|
# Disable ARP replies for a virtual host
|
|
# Arguments: host IP address of host
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_disable_ARP_reply {
|
|
my ($host) = @_;
|
|
|
|
# Ensure all params look valid
|
|
if ( !validateIP($host) ) {
|
|
die "Invalid IP address '$host'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
lock(%vhostsARPdisable);
|
|
|
|
# Verify that the IP is known
|
|
if ( !defined( $vhostsIPiface{$host} ) ) {
|
|
die "Unknown IP address '$host'";
|
|
}
|
|
|
|
# Record that the host should be disabled from sending ARP replies
|
|
$vhostsARPdisable{$host} = 1;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_send_ICMP_echo_req
|
|
# Send an ICMP packet between two virtual hosts
|
|
# Arguments: src IP address of source
|
|
# dst IP address of destination
|
|
# arg optional hash of parameters to pass into
|
|
# the ICMP packet creation function
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_send_ICMP_echo_req {
|
|
my ( $src, $dst, %arg ) = @_;
|
|
|
|
# Ensure all params look valid
|
|
if ( !validateIP($src) ) {
|
|
die "Invalid source IP address '$src'. Should look like '192.168.0.1'";
|
|
}
|
|
if ( !validateIP($dst) ) {
|
|
die "Invalid dest IP address '$dst'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
lock(%vhostsIPmac);
|
|
lock(%vhostsARP);
|
|
|
|
# Verify that both IPs are known
|
|
if ( !defined( $vhostsIPiface{$src} ) ) {
|
|
die "Unknown source IP address '$src'";
|
|
}
|
|
if ( !defined( $vhostsIPiface{$dst} ) ) {
|
|
die "Unknown dest IP address '$dst'";
|
|
}
|
|
|
|
# Get the MAC of the source
|
|
my $srcMac = $vhostsIPmac{$src};
|
|
|
|
# Get the ifaces of both IPs
|
|
my $srcIface = $vhostsIPiface{$src};
|
|
my $dstIface = $vhostsIPiface{$dst};
|
|
|
|
# Verify that they are on different interfaces
|
|
return if ( $srcIface eq $dstIface );
|
|
|
|
# Get the IP of the router node for the src
|
|
my $nextHop = $vhostsRouterIPs{$srcIface};
|
|
|
|
# Construct the ARP table string
|
|
my $arpStr = "$src:$nextHop";
|
|
|
|
# Check if we know the next hop's MAC address
|
|
my $macKnown = defined( $vhostsARP{$arpStr} );
|
|
|
|
# Get the MAC address of the next hop (Note: this may not actually
|
|
# be known at this stage by the virtual host. We'll fill it in now
|
|
# but maybe send later.)
|
|
my $nextHopMac = $vhostsIPmac{$nextHop};
|
|
|
|
# Construct the IP packet
|
|
$arg{DA} = $nextHopMac;
|
|
$arg{SA} = $srcMac;
|
|
$arg{src_ip} = $src;
|
|
$arg{dst_ip} = $dst;
|
|
|
|
my $icmppkt = NF2::ICMP_pkt->new_echo_request(%arg);
|
|
|
|
# Send or store the packet
|
|
if ($macKnown) {
|
|
nftest_send( $srcIface, $icmppkt->packed() );
|
|
}
|
|
else {
|
|
lock(%vhostsPendingPkts);
|
|
|
|
# Store the ip packet
|
|
my $pkts;
|
|
if ( defined( $vhostsPendingPkts{$arpStr} ) ) {
|
|
$pkts = $vhostsPendingPkts{$arpStr};
|
|
}
|
|
else {
|
|
$pkts = &share( [] );
|
|
$vhostsPendingPkts{$arpStr} = $pkts;
|
|
|
|
# Generate an ARP packet
|
|
my $arppkt = NF2::ARP_pkt->new_request(
|
|
SA => $srcMac,
|
|
SenderIpAddr => $src,
|
|
TargetIpAddr => $nextHop,
|
|
);
|
|
nftest_send( $srcIface, $arppkt->packed(), 0 );
|
|
}
|
|
|
|
lock($pkts);
|
|
push @$pkts, $icmppkt->packed;
|
|
|
|
# Record the pending packet
|
|
nftest_expect( $srcIface, $icmppkt->packed, 1 );
|
|
}
|
|
|
|
return $icmppkt;
|
|
}
|
|
|
|
|
|
###############################################################
|
|
# Name: processArpRequest
|
|
# Process an ARP request packet
|
|
# Arguments: iface interface
|
|
# pkt packet
|
|
# Returns:
|
|
###############################################################
|
|
sub processArpRequest {
|
|
my ( $iface, $pkt ) = @_;
|
|
|
|
# Convert the ARP request to an ARP_pkt
|
|
my $arppkt = NF2::ARP_pkt->new_packed( Data => $pkt );
|
|
|
|
# Get the sender IP, sender MAC and target IP
|
|
my $src = $arppkt->get('SenderIpAddr');
|
|
my $srcMac = $arppkt->get('SenderEthAddr');
|
|
my $dst = $arppkt->get('TargetIpAddr');
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
|
|
# Record that we've seen an arp request for the src/dst pair
|
|
{
|
|
lock(%vhARPReqLog);
|
|
|
|
if ( !defined( $vhARPReqLog{"$src:$dst"} ) ) {
|
|
$vhARPReqLog{"$src:$dst"} = 1;
|
|
}
|
|
else {
|
|
$vhARPReqLog{"$src:$dst"} += 1;
|
|
}
|
|
}
|
|
|
|
# Verify that we should in fact be sending an ARP reply
|
|
{
|
|
lock(%vhostsARPdisable);
|
|
|
|
if ( defined( $vhostsARPdisable{$dst} ) ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
# Check to see if we know the dst and if it's on the correct interface
|
|
if ( defined( $vhostsIPiface{$dst} ) ) {
|
|
if ( $vhostsIPiface{$dst} eq $iface ) {
|
|
lock(%vhostsIPmac);
|
|
my $dstMac = $vhostsIPmac{$dst};
|
|
|
|
# Generate an ARP reply
|
|
my $arpreply = NF2::ARP_pkt->new_reply(
|
|
SA => $dstMac,
|
|
DA => $srcMac,
|
|
SenderIpAddr => $dst,
|
|
TargetIpAddr => $src,
|
|
);
|
|
|
|
# Send the packet
|
|
nftest_send( $iface, $arpreply->packed, 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Name: processArpReply
|
|
# Process an ARP request packet
|
|
# Arguments: iface interface
|
|
# pkt packet
|
|
# Returns:
|
|
###############################################################
|
|
sub processArpReply {
|
|
my ( $iface, $pkt ) = @_;
|
|
|
|
# Convert the ARP reply to an ARP_pkt
|
|
my $arppkt = NF2::ARP_pkt->new_packed( Data => $pkt );
|
|
|
|
# Get the sender and target IP and MAC
|
|
my $src = $arppkt->get('SenderIpAddr');
|
|
my $srcMac = $arppkt->get('SenderEthAddr');
|
|
my $dst = $arppkt->get('TargetIpAddr');
|
|
my $dstMac = $arppkt->get('TargetEthAddr');
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
|
|
# Record that we've seen an arp reply for the src/dst pair
|
|
{
|
|
lock(%vhARPRepLog);
|
|
|
|
if ( !defined( $vhARPRepLog{"$src:$dst"} ) ) {
|
|
$vhARPRepLog{"$src:$dst"} = 1;
|
|
}
|
|
else {
|
|
$vhARPRepLog{"$src:$dst"} += 1;
|
|
}
|
|
}
|
|
|
|
# Check to see if we know the dst and if it's on the correct interface
|
|
if ( defined( $vhostsIPiface{$dst} ) ) {
|
|
lock(%vhostsIPmac);
|
|
if ( $vhostsIPiface{$dst} eq $iface && $vhostsIPmac{$dst} eq $dstMac ) {
|
|
|
|
# Store the response in the ARP table
|
|
my $arpStr = "$dst:$src";
|
|
lock(%vhostsARP);
|
|
lock(%vhostsPendingPkts);
|
|
$vhostsARP{$arpStr} = $srcMac;
|
|
|
|
# Process any pending packets
|
|
if ( defined( $vhostsPendingPkts{$arpStr} ) ) {
|
|
my $pkts = $vhostsPendingPkts{$arpStr};
|
|
foreach my $pkt (@$pkts) {
|
|
|
|
# Send the packet (without expect, we've already expected it)
|
|
nftest_send( $iface, $pkt, 0 );
|
|
}
|
|
|
|
# Delete the list of packets from the hash
|
|
delete( $vhostsPendingPkts{$arpStr} );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_expect_ARP_exchange
|
|
# Expect an ARP exchange between two hosts
|
|
# Arguments: src ARP sender
|
|
# dst ARP receiver
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_expect_ARP_exchange {
|
|
my ( $src, $dst ) = @_;
|
|
|
|
expect_ARP_message( $src, $dst, \%vhARPReqLog );
|
|
expect_ARP_message( $dst, $src, \%vhARPRepLog );
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_expect_ARP_request
|
|
# Expect an ARP exchange between two hosts
|
|
# Arguments: src ARP sender
|
|
# dst ARP receiver
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_expect_ARP_request {
|
|
my ( $src, $dst ) = @_;
|
|
|
|
expect_ARP_message( $src, $dst, \%vhARPReqLog );
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_expect_ARP_reply
|
|
# Expect an ARP exchange between two hosts
|
|
# Arguments: src ARP sender
|
|
# dst ARP receiver
|
|
# Returns:
|
|
###############################################################
|
|
sub nftest_expect_ARP_reply {
|
|
my ( $src, $dst ) = @_;
|
|
|
|
expect_ARP_message( $src, $dst, \%vhARPRepLog );
|
|
}
|
|
|
|
###############################################################
|
|
# Name: expect_ARP_message
|
|
# Expect an ARP message between two hosts
|
|
# Arguments: src ARP sender
|
|
# dst ARP receiver
|
|
# msgHash Hash reference into which to store the expected
|
|
# message
|
|
# Returns:
|
|
###############################################################
|
|
sub expect_ARP_message {
|
|
my ( $src, $dst, $msgHash ) = @_;
|
|
|
|
# Ensure all params look valid
|
|
if ( !validateIP($src) ) {
|
|
die "Invalid source IP address '$src'. Should look like '192.168.0.1'";
|
|
}
|
|
if ( !validateIP($dst) ) {
|
|
die "Invalid dest IP address '$dst'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
lock(%vhostsIPmac);
|
|
|
|
# Verify that both IPs are known
|
|
if ( !defined( $vhostsIPiface{$src} ) ) {
|
|
die "Unknown source IP address '$src'";
|
|
}
|
|
if ( !defined( $vhostsIPiface{$dst} ) ) {
|
|
die "Unknown dest IP address '$dst'";
|
|
}
|
|
|
|
# Get the MAC of the source and dest
|
|
my $srcMac = $vhostsIPmac{$src};
|
|
my $dstMac = $vhostsIPmac{$dst};
|
|
|
|
# Get the ifaces of both IPs
|
|
my $srcIface = $vhostsIPiface{$src};
|
|
my $dstIface = $vhostsIPiface{$dst};
|
|
|
|
# Ensure that the two hosts are on the same interface
|
|
if ( ( $srcIface ne "$dstIface:rtr" ) && ( "$srcIface:rtr" ne $dstIface ) ) {
|
|
die "Source and destination not on same interface";
|
|
}
|
|
|
|
# Record the expected message
|
|
#
|
|
# Note: expected packets are denoted in this instance by a 0.5. This allows for the
|
|
# case of multiple requests etc arriving
|
|
{
|
|
lock(%$msgHash);
|
|
|
|
if ( !defined( $$msgHash{"$src:$dst"} ) ) {
|
|
$$msgHash{"$src:$dst"} = 0.5;
|
|
}
|
|
else {
|
|
$$msgHash{"$src:$dst"} = int( $$msgHash{"$src:$dst"} ) + 0.5;
|
|
}
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_get_vhost_mac
|
|
# Get the MAC address of a virtual host
|
|
# Arguments: ip Host's IP address
|
|
# Returns: MAC address of host
|
|
###############################################################
|
|
sub nftest_get_vhost_mac {
|
|
my ($ip) = @_;
|
|
|
|
# Ensure all params look valid
|
|
if ( !validateIP($ip) ) {
|
|
die "Invalid IP address '$ip'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPmac);
|
|
|
|
# Verify that the host doesn't already exist
|
|
if ( !defined( $vhostsIPmac{$ip} ) ) {
|
|
die "Virtual host with IP address '$ip' does not exist";
|
|
}
|
|
|
|
# Insert the virtual host in the table
|
|
return $vhostsIPmac{$ip};
|
|
}
|
|
|
|
|
|
###############################################################
|
|
# Name: nftest_expect_ICMP_host_unreach
|
|
# Expect an ICMP host unreachable message
|
|
# Arguments: dst Host receiving ICMP packet
|
|
# src Host sending ICMP packet
|
|
# pkt Rejected packet
|
|
# Returns: The expected IMCP packet
|
|
###############################################################
|
|
sub nftest_expect_ICMP_host_unreach {
|
|
my ( $dst, $src, $pkt ) = @_;
|
|
|
|
return expect_ICMP_dest_unreach( $dst, $src, NF2::ICMP::DEST_HOST_UNREACH, $pkt );
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_expect_ICMP_port_unreach
|
|
# Expect an ICMP port unreachable message
|
|
# Arguments: dst Host receiving ICMP packet
|
|
# src Host sending ICMP packet
|
|
# pkt Rejected packet
|
|
# Returns: The expected IMCP packet
|
|
###############################################################
|
|
sub nftest_expect_ICMP_port_unreach {
|
|
my ( $dst, $src, $pkt ) = @_;
|
|
|
|
return expect_ICMP_dest_unreach( $dst, $src, NF2::ICMP::DEST_PORT_UNREACH, $pkt );
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_expect_ICMP_proto_unreach
|
|
# Expect an ICMP proto unreachable message
|
|
# Arguments: dst Host receiving ICMP packet
|
|
# src Host sending ICMP packet
|
|
# pkt Rejected packet
|
|
# Returns: The expected IMCP packet
|
|
###############################################################
|
|
sub nftest_expect_ICMP_proto_unreach {
|
|
my ( $dst, $src, $pkt ) = @_;
|
|
|
|
return expect_ICMP_dest_unreach( $dst, $src, NF2::ICMP::DEST_PROTO_UNREACH, $pkt );
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_expect_ICMP_network_unreach
|
|
# Expect an ICMP network unreachable message
|
|
# Arguments: dst Host receiving ICMP packet
|
|
# src Host sending ICMP packet
|
|
# pkt Rejected packet
|
|
# Returns: The expected IMCP packet
|
|
###############################################################
|
|
sub nftest_expect_ICMP_network_unreach {
|
|
my ( $dst, $src, $pkt ) = @_;
|
|
|
|
return expect_ICMP_dest_unreach( $dst, $src, NF2::ICMP::DEST_NET_UNREACH, $pkt );
|
|
}
|
|
|
|
###############################################################
|
|
# Name: nftest_expect_ICMP_time_exceeded
|
|
# Expect an ICMP time exceeded message
|
|
# Arguments: dst Host receiving ICMP packet
|
|
# src Host sending ICMP packet
|
|
# reason Why is the packet rejected
|
|
# pkt Rejected packet
|
|
# Returns: The expected IMCP packet
|
|
###############################################################
|
|
sub nftest_expect_ICMP_time_exceeded {
|
|
my ( $dst, $src, $pkt ) = @_;
|
|
|
|
# Ensure all params look valid
|
|
if ( !validateIP($src) ) {
|
|
die "Invalid source IP address '$src'. Should look like '192.168.0.1'";
|
|
}
|
|
if ( !validateIP($dst) ) {
|
|
die "Invalid dest IP address '$dst'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
|
|
# Verify that both IPs are known
|
|
if ( !defined( $vhostsIPiface{$src} ) ) {
|
|
die "Unknown source IP address '$src'";
|
|
}
|
|
if ( !defined( $vhostsIPiface{$dst} ) ) {
|
|
die "Unknown dest IP address '$dst'";
|
|
}
|
|
|
|
# Get the iface
|
|
my $iface = $vhostsIPiface{$dst};
|
|
|
|
# Create the actual ICMP packet
|
|
my $icmppkt = NF2::ICMP_pkt->new_time_exceeded(
|
|
Packet => $pkt,
|
|
src_ip => $src,
|
|
);
|
|
|
|
# Call the other expect function
|
|
nftest_expect( $iface, $icmppkt->packed(), 1 );
|
|
|
|
# Return the packet
|
|
return $icmppkt;
|
|
}
|
|
|
|
###############################################################
|
|
# Name: expect_ICMP_dest_unreach
|
|
# Expect an ICMP destination unreachable message
|
|
# Arguments: dst Host receiving ICMP packet
|
|
# src Host sending ICMP packet
|
|
# reason Why is the packet rejected
|
|
# pkt Rejected packet
|
|
# Returns: The expected IMCP packet
|
|
###############################################################
|
|
sub expect_ICMP_dest_unreach {
|
|
my ( $dst, $src, $reason, $pkt ) = @_;
|
|
|
|
# Ensure all params look valid
|
|
if ( !validateIP($src) ) {
|
|
die "Invalid source IP address '$src'. Should look like '192.168.0.1'";
|
|
}
|
|
if ( !validateIP($dst) ) {
|
|
die "Invalid dest IP address '$dst'. Should look like '192.168.0.1'";
|
|
}
|
|
|
|
# Lock the appropriate data elements
|
|
lock(%vhostsIPiface);
|
|
|
|
# Verify that both IPs are known
|
|
if ( !defined( $vhostsIPiface{$src} ) ) {
|
|
die "Unknown source IP address '$src'";
|
|
}
|
|
if ( !defined( $vhostsIPiface{$dst} ) ) {
|
|
die "Unknown dest IP address '$dst'";
|
|
}
|
|
|
|
# Get the iface
|
|
my $iface = $vhostsIPiface{$dst};
|
|
|
|
# Create the actual ICMP packet
|
|
my $icmppkt = NF2::ICMP_pkt->new_dest_unreach(
|
|
Reason => $reason,
|
|
Packet => $pkt,
|
|
src_ip => $src,
|
|
);
|
|
|
|
# Call the other expect function
|
|
nftest_expect( $iface, $icmppkt->packed(), 1 );
|
|
|
|
# Return the packet
|
|
return $icmppkt;
|
|
}
|
|
|
|
|
|
# Always end library in 1
|
|
1;
|