src/regress/usr.sbin/ospfd/Client.pm

389 lines
10 KiB
Perl

# $OpenBSD: Client.pm,v 1.7 2020/01/30 13:03:46 bluhm Exp $
# Copyright (c) 2010-2015 Alexander Bluhm <bluhm@openbsd.org>
# Copyright (c) 2014-2015 Florian Riehm <mail@friehm.de>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
use strict;
use warnings;
use feature "state";
package Client;
use parent 'Proc';
use Carp;
use Fcntl;
use Data::Dumper;
use YAML;
use AnyEvent;
use AnyEvent::Handle;
use AnyEvent::Strict;
use Packet;
use Tap 'opentap';
my $tap_number;
my $area;
my $hello_interval;
# Parameters for interface state machine of the test
my $ism_mac;
my $ism_ip;
my $ism_rtrid;
# Parameters for ospfd under test
my $ospfd_ip;
my $ospfd_rtrid;
my $handle;
my $check;
my $wait;
my $cv;
my @isms;
sub handle_arp {
my %arp = consume_arp(\$handle->{rbuf});
my %ether = (
src_str => $ism_mac,
dst_str => $arp{sha_str},
type => 0x0806,
);
$arp{op} = 2;
@arp{qw(sha_str spa_str tha_str tpa_str)} =
($ism_mac, @arp{qw(tpa_str sha_str spa_str)});
$handle->push_write(
construct_ether(\%ether,
construct_arp(\%arp))
);
}
sub handle_ip {
my %ip = consume_ip(\$handle->{rbuf});
unless ($ip{p} == 89) {
warn "ip proto is not ospf";
return;
}
$ip{src_str} eq $ospfd_ip
or return $cv->croak(
"ospfd src ip is $ip{src_str}: expected $ospfd_ip");
my %ospf = consume_ospf(\$handle->{rbuf});
$ospf{router_id_str} eq $ospfd_rtrid
or return $cv->croak(
"ospfd rtrid is $ospf{router_id_str}: expected $ospfd_rtrid");
$ospf{area_id_str} eq $area
or return $cv->croak(
"ospfd area is $ospf{area_id_str}: expected $area");
if ($ospf{type} == 1) {
handle_hello();
} elsif ($ospf{type} == 2) {
handle_dd();
} else {
warn "ospf type is not supported: $ospf{type}";
}
}
sub handle_hello {
my %hello = consume_hello(\$handle->{rbuf});
my $compare = sub {
my $expect = shift;
if ($expect->{dr}) {
$hello{designated_router_str} eq $expect->{dr}
or return "dr is $hello{designated_router_str}: ".
"expected $expect->{dr}";
}
if ($expect->{bdr}) {
$hello{backup_designated_router_str} eq $expect->{bdr}
or return "bdr is $hello{backup_designated_router_str}: ".
"expected $expect->{bdr}";
}
if ($expect->{nbrs}) {
my @neighbors = sort @{$hello{neighbors_str} || []};
my @nbrs = @{$expect->{nbrs}};
"@neighbors" eq "@nbrs"
or return "nbrs [@neighbors]: expected [@nbrs]";
}
return "";
};
my $error = $compare->($check);
return $cv->croak("check: $error") if $error;
print "check hello successful\n";
my $reason;
if ($wait) {
$reason = $compare->($wait);
}
if ($reason) {
print "wait for hello because of: $reason\n";
} elsif (!$wait || $wait->{dr} || $wait->{bdr} || $wait->{nbrs}) {
$cv->send();
}
}
sub handle_dd {
my %dd = consume_dd(\$handle->{rbuf});
my $compare = sub {
my $expect = shift;
foreach my $key (qw(options bits)) {
if ($expect->{"dd_$key"}) {
$dd{$key} == $expect->{"dd_$key"} or
return sprintf("dd key '%s' is 0x%x: expected 0x%x\n",
$key, $dd{$key}, $expect->{"dd_$key"});
}
}
if ($expect->{dd_seq}) {
$dd{dd_sequence_number} == $expect->{dd_seq} or
return sprintf("dd_sequence_number is 0x%x: expected 0x%x\n",
$dd{dd_sequence_number}, $expect->{dd_seq});
}
return "";
};
my $error = $compare->($check);
return $cv->croak("check: $error") if $error;
print "check dd successful\n";
my $reason;
if ($wait) {
$reason = $compare->($wait);
}
if ($reason) {
print "wait for dd because of: $reason\n";
} elsif (!$wait || $wait->{dd_bits} || $wait->{dd_options} ||
$wait->{dd_seq}) {
$cv->send();
}
}
my $ism_count = 0;
sub interface_state_machine {
my %state = (
dr => "0.0.0.0",
bdr => "0.0.0.0",
pri => 1,
);
# increment the ip address and router id for each instance of ism
my $ip_number = unpack("N", pack("C4", split(/\./, $ism_ip)));
my $ip = join(".", unpack("C4", pack("N", $ip_number + $ism_count)));
my $rtrid_number = unpack("N", pack("C4", split(/\./, $ism_rtrid)));
my $rtrid = join(".", unpack("C4", pack("N", $rtrid_number + $ism_count)));
$ism_count++;
my $hello_count = 0;
$state{timer} = AnyEvent->timer(
after => 3,
interval => $hello_interval,
cb => sub {
my %ether = (
src_str => $ism_mac,
dst_str => "01:00:5e:00:00:05", # multicast ospf
type => 0x0800, # ipv4
);
my %ip = (
v => 4, # ipv4
hlen => 20,
tos => 0xc0,
id => $hello_count++, # increment for debugging
off => 0, # no fragment
ttl => 1, # only for direct connected
p => 89, # protocol ospf
src_str => $ip,
dst_str => "224.0.0.5", # all ospf router multicast
);
my %ospf = (
version => 2, # ospf v2
type => 1, # hello
router_id_str => $rtrid,
area_id_str => $area,
autype => 0, # no authentication
);
my %hello = (
network_mask_str => "255.255.255.0",
hellointerval => $hello_interval,
options => 0x02,
rtr_pri => $state{pri},
routerdeadinterval => 4 * $hello_interval,
designated_router_str => $state{dr},
backup_designated_router_str => $state{bdr},
neighbors_str => $state{nbrs},
);
$handle->push_write(
construct_ether(\%ether,
construct_ip(\%ip,
construct_ospf(\%ospf,
construct_hello(\%hello))))
);
},
);
return \%state;
}
sub send_dd {
my $state = shift;
my $ip_number = unpack("N", pack("C4", split(/\./, $ism_ip)));
my $ip = join(".", unpack("C4", pack("N", $ip_number)));
my $rtrid_number = unpack("N", pack("C4", split(/\./, $ism_rtrid)));
my $rtrid = join(".", unpack("C4", pack("N", $rtrid_number)));
state $dd_count = 0;
my %ether = (
src_str => $ism_mac,
dst_str => "01:00:5e:00:00:05", # don't know the real dst mac
type => 0x0800, # ipv4
);
my %ip = (
v => 4, # ipv4
hlen => 20,
tos => 0xc0,
id => $dd_count++, # increment for debugging
off => 0, # no fragment
ttl => 1, # only for direct connected
p => 89, # protocol ospf
src_str => $ip,
dst_str => $ospfd_ip,
);
my %ospf = (
version => 2, # ospf v2
type => 2, # dd
router_id_str => $rtrid,
area_id_str => $area,
autype => 0, # no authentication
);
my %dd = (
interface_mtu => 1500,
options => 0x02,
bits => $state->{dd_bits},
dd_sequence_number => 999, # some value
);
$handle->push_write(
construct_ether(\%ether,
construct_ip(\%ip,
construct_ospf(\%ospf,
construct_dd(\%dd))))
);
}
sub ism_set_state {
my $state = shift;
my @states = ref($state) eq 'ARRAY' ? @$state : ( $state || () );
for (my $i = 0; $i < @states; $i++) {
$isms[$i] ||= interface_state_machine();
%{$isms[$i]} = (%{$isms[$i]}, %{$states[$i]});
if ($states[$i]{dd_bits}) {
send_dd($states[$i]);
delete $states[$i]{dd_bits};
}
}
}
sub runtest {
my $self = shift;
$| = 1;
ism_set_state($self->{state} || {});
foreach my $task (@{$self->{tasks}}) {
print "Task: $task->{name}\n";
$check = $task->{check};
$wait = $task->{wait};
my $timeout = $task->{timeout};
my $t;
if ($timeout) {
$t = AnyEvent->timer(
after => $timeout,
cb => sub { $cv->croak("timeout after $timeout seconds"); },
);
}
$cv = AnyEvent->condvar;
$cv->recv;
ism_set_state($task->{state});
}
print "Terminating\n";
sleep 1;
}
sub new {
my ($class, %args) = @_;
$args{logfile} ||= "client.log";
$args{up} = "Starting test client";
$args{down} = "Terminating";
$args{func} = \&runtest;
my $self = Proc::new($class, %args);
return $self;
}
sub child {
my $self = shift;
$area = $self->{area}
or croak ref($self), " area id missing";
$hello_interval = $self->{hello_intervall}
or croak ref($self), " hello_interval missing";
$ism_mac = $self->{mac_address}
or croak ref($self), " client mac address missing";
$ism_ip = $self->{ospf_address}
or croak ref($self), " client ospf address missing";
$ism_rtrid = $self->{router_id}
or croak ref($self), " client router id missing";
$tap_number = $self->{tap_number}
or croak ref($self), " tap device number missing";
$ospfd_ip = $self->{ospfd_ip}
or croak ref($self), " ospfd ip missing";
$ospfd_rtrid = $self->{ospfd_rtrid}
or croak ref($self), " ospfd router id missing";
if ($ENV{KTRACE}) {
my @ktrace = $ENV{KTRACE};
push @ktrace, "-i", "-f", "client.ktrace", "-p", $$;
system(@ktrace)
and die "Command '@ktrace' failed: $?";
}
my $tap = opentap($tap_number);
$handle = AnyEvent::Handle->new(
fh => $tap,
read_size => 70000, # little more than max ip size
on_error => sub {
$cv->croak("error on tap device $tap_number: $!");
$handle->destroy();
undef $handle;
},
on_eof => sub {
$cv->croak("end-of-file on tap device $tap_number: $!");
$handle->destroy();
undef $handle;
},
on_read => sub {
my %ether = consume_ether(\$handle->{rbuf});
if ($ether{type} == 0x0800) {
handle_ip();
} elsif ($ether{type} == 0x0806) {
handle_arp();
} else {
warn "ether type is not supported: $ether{type_hex}";
}
$handle->{rbuf} = ""; # packets must not cumulate
},
);
}
1;