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