# IPXWrapper test suite # Copyright (C) 2014 Daniel Collins # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License version 2 as published by # the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., 51 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. use strict; use warnings; use Test::Spec; use FindBin; use lib "$FindBin::Bin/lib/"; use IPXWrapper::Capture::IPXOverUDP; use IPXWrapper::Tool::IPXISR; use IPXWrapper::Tool::IPXRecv; use IPXWrapper::Util; require "$FindBin::Bin/config.pm"; our ($local_dev_a, $local_mac_a, $local_ip_a); our ($local_dev_b, $local_mac_b, $local_ip_b); our ($remote_mac_a, $remote_ip_a); our ($remote_mac_b, $remote_ip_b); our ($net_a_bcast, $net_b_bcast); require "$FindBin::Bin/ptype.pm"; our $ptype_send_func; our $ptype_capture_class; use constant { UDP_BCAST_PORT => 54792, }; my $node_c_net = "00:00:00:02"; describe "IPXWrapper using IP encapsulation" => sub { before all => sub { reg_delete_key($remote_ip_a, "HKCU\\Software\\IPXWrapper"); reg_set_addr( $remote_ip_a, "HKCU\\Software\\IPXWrapper\\00:00:00:00:00:00", "net", "00:00:00:01"); reg_set_addr( $remote_ip_a, "HKCU\\Software\\IPXWrapper\\$remote_mac_a", "net", "00:00:00:01"); reg_set_addr( $remote_ip_a, "HKCU\\Software\\IPXWrapper\\$remote_mac_b", "net", "00:00:00:02"); $node_c_net = "00:00:00:02"; }; describe "packets received on the shared port" => sub { they "are only accepted from the bound interface" => sub { my $capture_a = IPXWrapper::Tool::IPXRecv->new( $remote_ip_a, "-r", "00:00:00:00", $remote_mac_a, "4444", ); my $capture_b = IPXWrapper::Tool::IPXRecv->new( $remote_ip_a, "-r", "00:00:00:00", $remote_mac_b, "4444", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, type => 0, dest_network => "00:00:00:01", dest_node => $remote_mac_a, dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 1234, data => "chamferer", ); send_ipx_over_udp( dest_ip => $net_b_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_b, type => 0, dest_network => "00:00:00:01", dest_node => $remote_mac_a, dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 1234, data => "fragmentariness", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, type => 0, dest_network => $node_c_net, dest_node => $remote_mac_b, dest_socket => 4444, src_network => $node_c_net, src_node => $local_mac_b, src_socket => 1234, data => "dowy", ); send_ipx_over_udp( dest_ip => $net_b_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_b, type => 0, dest_network => $node_c_net, dest_node => $remote_mac_b, dest_socket => 4444, src_network => $node_c_net, src_node => $local_mac_b, src_socket => 1234, data => "papermch", ); sleep(1); my @packets_a = $capture_a->kill_and_read(); my @packets_b = $capture_b->kill_and_read(); cmp_hashes_partial(\@packets_a, [ { src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 1234, data => "chamferer", }, ]); cmp_hashes_partial(\@packets_b, [ { src_network => $node_c_net, src_node => $local_mac_b, src_socket => 1234, data => "papermch", }, ]); }; }; describe "packets received on the private port" => sub { they "are only accepted on the bound interface" => sub { my $isr_a = IPXWrapper::Tool::IPXISR->new($remote_ip_a, "-r", "-b", "00:00:00:01", $remote_mac_a, "4444"); my $isr_b = IPXWrapper::Tool::IPXISR->new($remote_ip_a, "-r", "-b", $node_c_net, $remote_mac_b, "4444"); my $port_a = determine_private_port($isr_a, $local_dev_a, "fleawort"); my $port_b = determine_private_port($isr_b, $local_dev_b, "effervesce"); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => $port_a, src_ip => $local_ip_a, type => 0, dest_network => "00:00:00:01", dest_node => $remote_mac_a, dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5678, data => "infernal", ); send_ipx_over_udp( dest_ip => $net_b_bcast, dest_port => $port_a, src_ip => $local_ip_b, type => 0, dest_network => "00:00:00:01", dest_node => $remote_mac_a, dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5678, data => "multireflex", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => $port_b, src_ip => $local_ip_a, type => 0, dest_network => $node_c_net, dest_node => $remote_mac_b, dest_socket => 4444, src_network => $node_c_net, src_node => $local_mac_b, src_socket => 5678, data => "metropolises", ); send_ipx_over_udp( dest_ip => $net_b_bcast, dest_port => $port_b, src_ip => $local_ip_b, type => 0, dest_network => $node_c_net, dest_node => $remote_mac_b, dest_socket => 4444, src_network => $node_c_net, src_node => $local_mac_b, src_socket => 5678, data => "photoionization", ); sleep(1); my @packets_a = $isr_a->kill_and_read(); my @packets_b = $isr_b->kill_and_read(); cmp_hashes_partial(\@packets_a, [ { src_net => "00:00:00:01", src_node => $local_mac_a, src_socket => 5678, data => "infernal", }, ]); cmp_hashes_partial(\@packets_b, [ { src_net => $node_c_net, src_node => $local_mac_b, src_socket => 5678, data => "photoionization", }, ]); }; }; describe "broadcast packets" => sub { they "are received by sockets with SO_BROADCAST" => sub { my $capture = IPXWrapper::Tool::IPXRecv->new( $remote_ip_a, "-b", "00:00:00:00", $remote_mac_a, "4444", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, type => 0, dest_network => "00:00:00:01", dest_node => "FF:FF:FF:FF:FF:FF", dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "amity", ); sleep(1); my @packets = $capture->kill_and_read(); cmp_hashes_partial(\@packets, [ { src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "amity", }, ]); }; they "aren't received by sockets without SO_BROADCAST when win95 bug is enabled" => sub { reg_set_dword($remote_ip_a, "HKCU\\Software\\IPXWrapper", "w95_bug", 1); my $capture = IPXWrapper::Tool::IPXRecv->new( $remote_ip_a, "00:00:00:00", $remote_mac_a, "4444", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, type => 0, dest_network => "00:00:00:01", dest_node => "FF:FF:FF:FF:FF:FF", dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "unslotted", ); sleep(1); my @packets = $capture->kill_and_read(); cmp_hashes_partial(\@packets, []); }; they "are received by sockets without SO_BROADCAST when win95 bug is disabled" => sub { reg_set_dword($remote_ip_a, "HKCU\\Software\\IPXWrapper", "w95_bug", 0); my $capture = IPXWrapper::Tool::IPXRecv->new( $remote_ip_a, "00:00:00:00", $remote_mac_a, "4444", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, type => 0, dest_network => "00:00:00:01", dest_node => "FF:FF:FF:FF:FF:FF", dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "declivity", ); sleep(1); my @packets = $capture->kill_and_read(); cmp_hashes_partial(\@packets, [ { src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "declivity", }, ]); }; they "are received by concurrent sockets within the same process" => sub { my $capture = IPXWrapper::Tool::IPXRecv->new( $remote_ip_a, "-r", "-b", "00:00:00:00", $remote_mac_a, "4444", "-r", "-b", "00:00:00:00", $remote_mac_a, "4444", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, type => 0, dest_network => "00:00:00:01", dest_node => "FF:FF:FF:FF:FF:FF", dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "isohyet", ); sleep(1); my @packets = $capture->kill_and_read(); cmp_hashes_partial(\@packets, [ { src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "isohyet", }, { src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "isohyet", }, ]) and isnt($packets[0]->{sock}, $packets[1]->{sock}); }; they "are received by concurrent sockets in different processes" => sub { my $capture_a = IPXWrapper::Tool::IPXRecv->new( $remote_ip_a, "-r", "-b", "00:00:00:00", $remote_mac_a, "4444", ); my $capture_b = IPXWrapper::Tool::IPXRecv->new( $remote_ip_a, "-r", "-b", "00:00:00:00", $remote_mac_a, "4444", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, type => 0, dest_network => "00:00:00:01", dest_node => "FF:FF:FF:FF:FF:FF", dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "januaries", ); sleep(1); my @packets_a = $capture_a->kill_and_read(); my @packets_b = $capture_b->kill_and_read(); cmp_hashes_partial(\@packets_a, [ { src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "januaries", }, ]); cmp_hashes_partial(\@packets_b, [ { src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "januaries", }, ]); }; # TODO they "are only received on the bound interfaces"; }; describe "packets sent to an unknown address" => sub { they "are only broadcast on the bound interface" => sub { my $capture_a = IPXWrapper::Capture::IPXOverUDP->new($local_dev_a); my $capture_b = IPXWrapper::Capture::IPXOverUDP->new($local_dev_b); run_remote_cmd( $remote_ip_a, "Z:\\tools\\ipx-send.exe", "-d" => "ligule", "-s" => "5555", "-h" => $remote_mac_a, "00:00:00:01", "11:11:11:11:11:11", "4444", ); run_remote_cmd( $remote_ip_a, "Z:\\tools\\ipx-send.exe", "-d" => "hardheads", "-s" => "5555", "-h" => $remote_mac_b, "00:00:00:02", "22:22:22:22:22:22", "4444", ); sleep(1); my @packets_a = $capture_a->read_available(); my @packets_b = $capture_b->read_available(); cmp_hashes_partial(\@packets_a, [ { src_ip => $remote_ip_a, dst_ip => $net_a_bcast, dst_port => UDP_BCAST_PORT, dst_network => "00:00:00:01", dst_node => "11:11:11:11:11:11", dst_socket => 4444, src_network => "00:00:00:01", src_node => $remote_mac_a, src_socket => 5555, data => "ligule", }, ]); cmp_hashes_partial(\@packets_b, [ { src_ip => $remote_ip_b, dst_ip => $net_b_bcast, dst_port => UDP_BCAST_PORT, dst_network => "00:00:00:02", dst_node => "22:22:22:22:22:22", dst_socket => 4444, src_network => $node_c_net, src_node => $remote_mac_b, src_socket => 5555, data => "hardheads", }, ]); }; }; describe "packets sent to a known address" => sub { my @packets_a; my @packets_b; before all => sub { my $sender = IPXWrapper::Tool::IPXISR->new($remote_ip_a, "00:00:00:01", $remote_mac_a, "7777"); # Send a packet from our target address. It # isn't aimed at an open socket, but will still # be seen by the router thread and should update # the address cache. send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, src_port => 6666, type => 0, dest_network => "00:00:00:01", dest_node => "FF:FF:FF:FF:FF:FF", dest_socket => 7778, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 8888, data => "", ); # Lay in some chaff - send packets with other # source addresses to make sure the correct # address gets pulled out of the cache. send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, src_port => 6667, type => 0, dest_network => "00:00:00:01", dest_node => "FF:FF:FF:FF:FF:FF", dest_socket => 7778, src_network => "00:00:00:01", src_node => "00:00:00:12:34:56", src_socket => 8888, data => "", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, src_port => 6668, type => 0, dest_network => "00:00:00:01", dest_node => "FF:FF:FF:FF:FF:FF", dest_socket => 7778, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 8889, data => "", ); sleep(1); my $capture_a = IPXWrapper::Capture::IPXOverUDP->new($local_dev_a); my $capture_b = IPXWrapper::Capture::IPXOverUDP->new($local_dev_b); $sender->send("00:00:00:01", $local_mac_a, "8888", "nondomesticating"); sleep(1); @packets_a = $capture_a->read_available(); @packets_b = $capture_b->read_available(); }; they "are sent to the process's UDP port" => sub { cmp_hashes_partial(\@packets_a, [ { src_ip => $remote_ip_a, dst_ip => $local_ip_a, dst_port => 6666, src_network => "00:00:00:01", src_node => $remote_mac_a, src_socket => 7777, dst_network => "00:00:00:01", dst_node => $local_mac_a, dst_socket => 8888, data => "nondomesticating", }, ]); }; they "aren't transmitted on the other network" => sub { cmp_hashes_partial(\@packets_b, []); }; }; describe "sockets bound to the wildcard interface" => sub { my ($wildcard_net, $wildcard_node); before each => sub { return if(defined($wildcard_net)); my @interfaces = getsockopt_interfaces($remote_ip_a); my ($wc) = grep { $_->{node} ne $remote_mac_a && $_->{node} ne $remote_mac_b } @interfaces; die("Could not determine wildcard address") unless($wc && (scalar @interfaces) == 3); $wildcard_net = $wc->{net}; $wildcard_node = $wc->{node}; }; they "transmit broadcast packets on all underlying interfaces" => sub { my $capture_a = IPXWrapper::Capture::IPXOverUDP->new($local_dev_a); my $capture_b = IPXWrapper::Capture::IPXOverUDP->new($local_dev_b); run_remote_cmd( $remote_ip_a, "Z:\\tools\\ipx-send.exe", "-b", "-h" => $wildcard_node, "-s" => "5555", "-d" => "tibetan", "FF:FF:FF:FF", "FF:FF:FF:FF:FF:FF", "4444" ); sleep(1); my @packets_a = $capture_a->read_available(); my @packets_b = $capture_b->read_available(); my $ref_packet = sub { my ($dst_ip) = @_; return { dst_port => UDP_BCAST_PORT, dst_ip => $dst_ip, dst_network => "FF:FF:FF:FF", dst_node => "FF:FF:FF:FF:FF:FF", dst_socket => 4444, src_network => $wildcard_net, src_node => $wildcard_node, src_socket => 5555, data => "tibetan", }; }; cmp_hashes_partial(\@packets_a, [ $ref_packet->($net_a_bcast) ]); cmp_hashes_partial(\@packets_b, [ $ref_packet->($net_b_bcast) ]); }; they "receive broadcast packets on all underlying interfaces" => sub { my $capture = IPXWrapper::Tool::IPXRecv->new( $remote_ip_a, "-b", "00:00:00:00", $wildcard_node, "4444", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, type => 0, dest_network => "00:00:00:01", dest_node => "FF:FF:FF:FF:FF:FF", dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "overtake", ); send_ipx_over_udp( dest_ip => $net_b_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_b, type => 0, dest_network => "00:00:00:01", dest_node => "FF:FF:FF:FF:FF:FF", dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_b, src_socket => 5555, data => "leakey", ); sleep(1); my @packets = $capture->kill_and_read(); cmp_hashes_partial(\@packets, [ { src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "overtake", }, { src_network => "00:00:00:01", src_node => $local_mac_b, src_socket => 5555, data => "leakey", }, ]); }; they "broadcast packets to an unknown address on all underlying interfaces" => sub { my $capture_a = IPXWrapper::Capture::IPXOverUDP->new($local_dev_a); my $capture_b = IPXWrapper::Capture::IPXOverUDP->new($local_dev_b); run_remote_cmd( $remote_ip_a, "Z:\\tools\\ipx-send.exe", "-h", $wildcard_node, "-s", "5555", "-d", "bowdrill", "00:00:00:01", "11:11:11:11:11:11", "4444", ); run_remote_cmd( $remote_ip_a, "Z:\\tools\\ipx-send.exe", "-h", $wildcard_node, "-s", "5555", "-d", "developer", "00:00:00:02", "22:22:22:22:22:22", "4444", ); sleep(1); my @packets_a = $capture_a->read_available(); my @packets_b = $capture_b->read_available(); cmp_hashes_partial(\@packets_a, [ { src_ip => $remote_ip_a, dst_ip => $net_a_bcast, dst_port => UDP_BCAST_PORT, dst_network => "00:00:00:01", dst_node => "11:11:11:11:11:11", dst_socket => 4444, src_network => "00:00:00:01", src_node => $wildcard_node, src_socket => 5555, data => "bowdrill", }, { src_ip => $remote_ip_a, dst_ip => $net_a_bcast, dst_port => UDP_BCAST_PORT, dst_network => "00:00:00:02", dst_node => "22:22:22:22:22:22", dst_socket => 4444, src_network => "00:00:00:01", src_node => $wildcard_node, src_socket => 5555, data => "developer", }, ]); cmp_hashes_partial(\@packets_b, [ { src_ip => $remote_ip_b, dst_ip => $net_b_bcast, dst_port => UDP_BCAST_PORT, dst_network => "00:00:00:01", dst_node => "11:11:11:11:11:11", dst_socket => 4444, src_network => "00:00:00:01", src_node => $wildcard_node, src_socket => 5555, data => "bowdrill", }, { src_ip => $remote_ip_b, dst_ip => $net_b_bcast, dst_port => UDP_BCAST_PORT, dst_network => "00:00:00:02", dst_node => "22:22:22:22:22:22", dst_socket => 4444, src_network => "00:00:00:01", src_node => $wildcard_node, src_socket => 5555, data => "developer", }, ]); }; # TODO: Test transmission to a known IPX address. they "receive unicast packets on all underlying interfaces" => sub { # TODO: Sent to private port, or not bcast at least. my $capture = IPXWrapper::Tool::IPXRecv->new( $remote_ip_a, "00:00:00:00", $wildcard_node, "4444", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, type => 0, dest_network => "00:00:00:01", dest_node => $wildcard_node, dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "orville", ); send_ipx_over_udp( dest_ip => $net_b_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_b, type => 0, dest_network => "00:00:00:01", dest_node => $wildcard_node, dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_b, src_socket => 5555, data => "eliminability", ); sleep(1); my @packets = $capture->kill_and_read(); cmp_hashes_partial(\@packets, [ { src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "orville", }, { src_network => "00:00:00:01", src_node => $local_mac_b, src_socket => 5555, data => "eliminability", }, ]); }; they "do not receive packets addressed to underlying interfaces" => sub { my $capture = IPXWrapper::Tool::IPXRecv->new( $remote_ip_a, "00:00:00:00", $wildcard_node, "4444", ); send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, type => 0, dest_network => "00:00:00:01", dest_node => $remote_mac_a, dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 5555, data => "antisuffragist", ); send_ipx_over_udp( dest_ip => $net_b_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_b, type => 0, dest_network => "00:00:00:02", dest_node => $remote_mac_b, dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_b, src_socket => 5555, data => "ambrosiaceous", ); sleep(1); my @packets = $capture->kill_and_read(); cmp_hashes_partial(\@packets, []); }; }; before all => sub { $ptype_capture_class = "IPXWrapper::Capture::IPXOverUDP"; $ptype_send_func = sub { my ($type, $data) = @_; send_ipx_over_udp( dest_ip => $net_a_bcast, dest_port => UDP_BCAST_PORT, src_ip => $local_ip_a, type => $type, dest_network => "00:00:00:01", dest_node => $remote_mac_a, dest_socket => 4444, src_network => "00:00:00:01", src_node => $local_mac_a, src_socket => 1234, data => $data, ); }; }; it_should_behave_like "ipx packet type handling"; }; runtests unless caller; sub determine_private_port { my ($isr, $capture_dev, $key) = @_; my $capture = IPXWrapper::Capture::IPXOverUDP->new($capture_dev); $isr->send("00:00:00:00", "FF:FF:FF:FF:FF:FF", "1234", $key); sleep(1); my ($ip_packet) = grep { $_->{data} eq $key } $capture->read_available(); die("Couldn't determine port number of process") unless($ip_packet); return $ip_packet->{src_port}; }