xref: /openbsd/regress/usr.sbin/ospfd/Client.pm (revision d415bd75)
1#	$OpenBSD: Client.pm,v 1.7 2020/01/30 13:03:46 bluhm Exp $
2
3# Copyright (c) 2010-2015 Alexander Bluhm <bluhm@openbsd.org>
4# Copyright (c) 2014-2015 Florian Riehm <mail@friehm.de>
5#
6# Permission to use, copy, modify, and distribute this software for any
7# purpose with or without fee is hereby granted, provided that the above
8# copyright notice and this permission notice appear in all copies.
9#
10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
18use strict;
19use warnings;
20use feature "state";
21
22package Client;
23use parent 'Proc';
24use Carp;
25
26use Fcntl;
27use Data::Dumper;
28use YAML;
29
30use AnyEvent;
31use AnyEvent::Handle;
32use AnyEvent::Strict;
33
34use Packet;
35use Tap 'opentap';
36
37my $tap_number;
38my $area;
39my $hello_interval;
40# Parameters for interface state machine of the test
41my $ism_mac;
42my $ism_ip;
43my $ism_rtrid;
44# Parameters for ospfd under test
45my $ospfd_ip;
46my $ospfd_rtrid;
47
48my $handle;
49my $check;
50my $wait;
51my $cv;
52my @isms;
53
54sub handle_arp {
55    my %arp = consume_arp(\$handle->{rbuf});
56    my %ether = (
57	src_str => $ism_mac,
58	dst_str => $arp{sha_str},
59	type    => 0x0806,
60    );
61    $arp{op} = 2;
62    @arp{qw(sha_str spa_str tha_str tpa_str)} =
63	($ism_mac, @arp{qw(tpa_str sha_str spa_str)});
64    $handle->push_write(
65	construct_ether(\%ether,
66	construct_arp(\%arp))
67    );
68}
69
70sub handle_ip {
71    my %ip = consume_ip(\$handle->{rbuf});
72    unless ($ip{p} == 89) {
73	warn "ip proto is not ospf";
74	return;
75    }
76    $ip{src_str} eq $ospfd_ip
77	or return $cv->croak(
78	"ospfd src ip is $ip{src_str}: expected $ospfd_ip");
79    my %ospf = consume_ospf(\$handle->{rbuf});
80    $ospf{router_id_str} eq $ospfd_rtrid
81	or return $cv->croak(
82	"ospfd rtrid is $ospf{router_id_str}: expected $ospfd_rtrid");
83    $ospf{area_id_str} eq $area
84	or return $cv->croak(
85	"ospfd area is $ospf{area_id_str}: expected $area");
86    if ($ospf{type} == 1) {
87	handle_hello();
88    } elsif ($ospf{type} == 2) {
89	handle_dd();
90    } else {
91	warn "ospf type is not supported: $ospf{type}";
92    }
93}
94
95sub handle_hello {
96    my %hello = consume_hello(\$handle->{rbuf});
97
98    my $compare = sub {
99	my $expect = shift;
100	if ($expect->{dr}) {
101	    $hello{designated_router_str} eq $expect->{dr}
102		or return "dr is $hello{designated_router_str}: ".
103		    "expected $expect->{dr}";
104	}
105	if ($expect->{bdr}) {
106	    $hello{backup_designated_router_str} eq $expect->{bdr}
107		or return "bdr is $hello{backup_designated_router_str}: ".
108		    "expected $expect->{bdr}";
109	}
110	if ($expect->{nbrs}) {
111	    my @neighbors = sort @{$hello{neighbors_str} || []};
112	    my @nbrs = @{$expect->{nbrs}};
113	    "@neighbors" eq "@nbrs"
114		or return "nbrs [@neighbors]: expected [@nbrs]";
115	}
116	return "";
117    };
118
119    my $error = $compare->($check);
120    return $cv->croak("check: $error") if $error;
121    print "check hello successful\n";
122
123    my $reason;
124    if ($wait) {
125	$reason = $compare->($wait);
126    }
127    if ($reason) {
128	print "wait for hello because of: $reason\n";
129    } elsif (!$wait || $wait->{dr} || $wait->{bdr} || $wait->{nbrs}) {
130	$cv->send();
131    }
132}
133
134sub handle_dd {
135    my %dd = consume_dd(\$handle->{rbuf});
136
137    my $compare = sub {
138	my $expect = shift;
139	foreach my $key (qw(options bits)) {
140	    if ($expect->{"dd_$key"}) {
141		$dd{$key} == $expect->{"dd_$key"} or
142		    return sprintf("dd key '%s' is 0x%x: expected 0x%x\n",
143			$key, $dd{$key}, $expect->{"dd_$key"});
144	    }
145	}
146	if ($expect->{dd_seq}) {
147	    $dd{dd_sequence_number} == $expect->{dd_seq} or
148		return sprintf("dd_sequence_number is 0x%x: expected 0x%x\n",
149		    $dd{dd_sequence_number}, $expect->{dd_seq});
150	}
151	return "";
152    };
153
154    my $error = $compare->($check);
155    return $cv->croak("check: $error") if $error;
156    print "check dd successful\n";
157
158    my $reason;
159    if ($wait) {
160	$reason = $compare->($wait);
161    }
162    if ($reason) {
163	print "wait for dd because of: $reason\n";
164    } elsif (!$wait || $wait->{dd_bits} || $wait->{dd_options} ||
165	$wait->{dd_seq}) {
166	$cv->send();
167    }
168}
169
170my $ism_count = 0;
171sub interface_state_machine {
172    my %state = (
173	dr  => "0.0.0.0",
174	bdr => "0.0.0.0",
175	pri => 1,
176    );
177
178    # increment the ip address and router id for each instance of ism
179    my $ip_number = unpack("N", pack("C4", split(/\./, $ism_ip)));
180    my $ip = join(".", unpack("C4", pack("N", $ip_number + $ism_count)));
181    my $rtrid_number = unpack("N", pack("C4", split(/\./, $ism_rtrid)));
182    my $rtrid = join(".", unpack("C4", pack("N", $rtrid_number + $ism_count)));
183    $ism_count++;
184
185    my $hello_count = 0;
186    $state{timer} = AnyEvent->timer(
187	after => 3,
188	interval => $hello_interval,
189	cb => sub {
190	    my %ether = (
191		src_str => $ism_mac,
192		dst_str => "01:00:5e:00:00:05",  # multicast ospf
193		type    => 0x0800,               # ipv4
194	    );
195	    my %ip = (
196		v       => 4,               # ipv4
197		hlen    => 20,
198		tos     => 0xc0,
199		id      => $hello_count++,  # increment for debugging
200		off     => 0,               # no fragment
201		ttl     => 1,               # only for direct connected
202		p       => 89,              # protocol ospf
203		src_str => $ip,
204		dst_str => "224.0.0.5",     # all ospf router multicast
205	    );
206	    my %ospf = (
207		version       => 2,           # ospf v2
208		type	      => 1,           # hello
209		router_id_str => $rtrid,
210		area_id_str   => $area,
211		autype        => 0,           # no authentication
212	    );
213	    my %hello = (
214		network_mask_str             => "255.255.255.0",
215		hellointerval                => $hello_interval,
216		options                      => 0x02,
217		rtr_pri		             => $state{pri},
218		routerdeadinterval           => 4 * $hello_interval,
219		designated_router_str        => $state{dr},
220		backup_designated_router_str => $state{bdr},
221		neighbors_str                => $state{nbrs},
222	    );
223	    $handle->push_write(
224		construct_ether(\%ether,
225		construct_ip(\%ip,
226		construct_ospf(\%ospf,
227		construct_hello(\%hello))))
228	    );
229	},
230    );
231
232    return \%state;
233}
234
235sub send_dd {
236    my $state = shift;
237    my $ip_number = unpack("N", pack("C4", split(/\./, $ism_ip)));
238    my $ip = join(".", unpack("C4", pack("N", $ip_number)));
239    my $rtrid_number = unpack("N", pack("C4", split(/\./, $ism_rtrid)));
240    my $rtrid = join(".", unpack("C4", pack("N", $rtrid_number)));
241    state $dd_count = 0;
242
243    my %ether = (
244	src_str => $ism_mac,
245	dst_str => "01:00:5e:00:00:05",  # don't know the real dst mac
246	type    => 0x0800,               # ipv4
247    );
248    my %ip = (
249	v       => 4,               # ipv4
250	hlen    => 20,
251	tos     => 0xc0,
252	id      => $dd_count++,	    # increment for debugging
253	off     => 0,               # no fragment
254	ttl     => 1,               # only for direct connected
255	p       => 89,              # protocol ospf
256	src_str => $ip,
257	dst_str => $ospfd_ip,
258    );
259    my %ospf = (
260	version       => 2,           # ospf v2
261	type	      => 2,           # dd
262	router_id_str => $rtrid,
263	area_id_str   => $area,
264	autype        => 0,           # no authentication
265    );
266    my %dd = (
267	interface_mtu => 1500,
268	options => 0x02,
269	bits => $state->{dd_bits},
270	dd_sequence_number => 999,	# some value
271    );
272    $handle->push_write(
273	construct_ether(\%ether,
274	construct_ip(\%ip,
275	construct_ospf(\%ospf,
276	construct_dd(\%dd))))
277    );
278}
279
280sub ism_set_state {
281    my $state = shift;
282
283    my @states = ref($state) eq 'ARRAY' ? @$state : ( $state || () );
284    for (my $i = 0; $i < @states; $i++) {
285	$isms[$i] ||= interface_state_machine();
286	%{$isms[$i]} = (%{$isms[$i]}, %{$states[$i]});
287	if ($states[$i]{dd_bits}) {
288	    send_dd($states[$i]);
289	    delete $states[$i]{dd_bits};
290	}
291    }
292}
293
294sub runtest {
295    my $self = shift;
296
297    $| = 1;
298
299    ism_set_state($self->{state} || {});
300
301    foreach my $task (@{$self->{tasks}}) {
302	print "Task: $task->{name}\n";
303	$check = $task->{check};
304	$wait = $task->{wait};
305	my $timeout = $task->{timeout};
306	my $t;
307	if ($timeout) {
308	    $t = AnyEvent->timer(
309		after => $timeout,
310		cb => sub { $cv->croak("timeout after $timeout seconds"); },
311	    );
312	}
313	$cv = AnyEvent->condvar;
314	$cv->recv;
315	ism_set_state($task->{state});
316    }
317
318    print "Terminating\n";
319    sleep 1;
320}
321
322sub new {
323    my ($class, %args) = @_;
324    $args{logfile} ||= "client.log";
325    $args{up} = "Starting test client";
326    $args{down} = "Terminating";
327    $args{func} = \&runtest;
328    my $self = Proc::new($class, %args);
329    return $self;
330}
331
332sub child {
333    my $self = shift;
334
335    $area = $self->{area}
336	or croak ref($self), " area id missing";
337    $hello_interval = $self->{hello_intervall}
338	or croak ref($self), " hello_interval missing";
339    $ism_mac = $self->{mac_address}
340	or croak ref($self), " client mac address missing";
341    $ism_ip = $self->{ospf_address}
342	or croak ref($self), " client ospf address missing";
343    $ism_rtrid = $self->{router_id}
344	or croak ref($self), " client router id missing";
345    $tap_number =  $self->{tap_number}
346	or croak ref($self), " tap device number missing";
347    $ospfd_ip = $self->{ospfd_ip}
348	or croak ref($self), " ospfd ip missing";
349    $ospfd_rtrid = $self->{ospfd_rtrid}
350	or croak ref($self), " ospfd router id missing";
351
352    if ($ENV{KTRACE}) {
353	my @ktrace = $ENV{KTRACE};
354	push @ktrace, "-i", "-f", "client.ktrace", "-p", $$;
355	system(@ktrace)
356	    and die "Command '@ktrace' failed: $?";
357    }
358
359    my $tap = opentap($tap_number);
360
361    $handle = AnyEvent::Handle->new(
362	fh => $tap,
363	read_size => 70000,  # little more than max ip size
364	on_error => sub {
365	    $cv->croak("error on tap device $tap_number: $!");
366	    $handle->destroy();
367	    undef $handle;
368	},
369	on_eof => sub {
370	    $cv->croak("end-of-file on tap device $tap_number: $!");
371	    $handle->destroy();
372	    undef $handle;
373	},
374	on_read => sub {
375	    my %ether = consume_ether(\$handle->{rbuf});
376	    if ($ether{type} == 0x0800) {
377		handle_ip();
378	    } elsif ($ether{type} == 0x0806) {
379		handle_arp();
380	    } else {
381		warn "ether type is not supported: $ether{type_hex}";
382	    }
383	    $handle->{rbuf} = "";  # packets must not cumulate
384	},
385    );
386}
387
3881;
389