Files
openflow/regress/lib/Perl5/Test/TestLib.pm
T
Glen Gibb 84bd3acc54 regress: --no_slicing cmd line arg
Add the ability to disable all slicing tests via --no_slicing flag

Change submitted by: Jean Tourrilhes <jt@hpl.hp.com>
2010-03-10 16:27:41 -08:00

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;