1 #!/usr/bin/perl -w 2 3 # Copyright (C) Internet Systems Consortium, Inc. ("ISC") 4 # 5 # SPDX-License-Identifier: MPL-2.0 6 # 7 # This Source Code Form is subject to the terms of the Mozilla Public 8 # License, v. 2.0. If a copy of the MPL was not distributed with this 9 # file, you can obtain one at https://mozilla.org/MPL/2.0/. 10 # 11 # See the COPYRIGHT file distributed with this work for additional 12 # information regarding copyright ownership. 13 14 # Framework for starting test servers. 15 # Based on the type of server specified, check for port availability, remove 16 # temporary files, start the server, and verify that the server is running. 17 # If a server is specified, start it. Otherwise, start all servers for test. 18 19 use strict; 20 use warnings; 21 22 use Cwd ':DEFAULT', 'abs_path'; 23 use English '-no_match_vars'; 24 use Getopt::Long; 25 use Time::HiRes 'sleep'; # allows sleeping fractional seconds 26 27 # Usage: 28 # perl start.pl [--noclean] [--restart] [--port port] [--taskset cpus] test [server [options]] 29 # 30 # --noclean Do not cleanup files in server directory. 31 # 32 # --restart Indicate that the server is being restarted, so get the 33 # server to append output to an existing log file instead of 34 # starting a new one. 35 # 36 # --port port Specify the default port being used by the server to answer 37 # queries (default 5300). This script will interrogate the 38 # server on this port to see if it is running. (Note: for 39 # "named" nameservers, this can be overridden by the presence 40 # of the file "named.port" in the server directory containing 41 # the number of the query port.) 42 # 43 # --taskset cpus Use taskset to signal which cpus can be used. For example 44 # cpus=fff0 means all cpus aexcept for 0, 1, 2, and 3 are 45 # eligible. 46 # 47 # test Name of the test directory. 48 # 49 # server Name of the server directory. This will be of the form 50 # "nsN" or "ansN", where "N" is an integer between 1 and 8. 51 # If not given, the script will start all the servers in the 52 # test directory. 53 # 54 # options Alternate options for the server. 55 # 56 # NOTE: options must be specified with '-- "<option list>"', 57 # for instance: start.pl . ns1 -- "-c n.conf -d 43" 58 # 59 # ALSO NOTE: this variable will be filled with the contents 60 # of the first non-commented/non-blank line of args in a file 61 # called "named.args" in an ns*/ subdirectory. Only the FIRST 62 # non-commented/non-blank line is used (everything else in 63 # the file is ignored). If "options" is already set, then 64 # "named.args" is ignored. 65 66 my $usage = "usage: $0 [--noclean] [--restart] [--port <port>] [--taskset <cpus>] test-directory [server-directory [server-options]]"; 67 my $clean = 1; 68 my $restart = 0; 69 my $queryport = 5300; 70 my $taskset = ""; 71 72 GetOptions( 73 'clean!' => \$clean, 74 'restart!' => \$restart, 75 'port=i' => \$queryport, 76 'taskset=s' => \$taskset, 77 ) or die "$usage\n"; 78 79 my( $test, $server_arg, $options_arg ) = @ARGV; 80 81 if (!$test) { 82 die "$usage\n"; 83 } 84 85 # Global variables 86 my $builddir = $ENV{'builddir'}; 87 my $srcdir = $ENV{'srcdir'}; 88 my $testdir = "$builddir/$test"; 89 90 if (! -d $testdir) { 91 die "No test directory: \"$testdir\"\n"; 92 } 93 94 if ($server_arg && ! -d "$testdir/$server_arg") { 95 die "No server directory: \"$testdir/$server_arg\"\n"; 96 } 97 98 my $NAMED = $ENV{'NAMED'}; 99 my $DIG = $ENV{'DIG'}; 100 my $PERL = $ENV{'PERL'}; 101 my $PYTHON = $ENV{'PYTHON'}; 102 103 # Start the server(s) 104 105 my @ns; 106 my @ans; 107 108 if ($server_arg) { 109 if ($server_arg =~ /^ns/) { 110 push(@ns, $server_arg); 111 } elsif ($server_arg =~ /^ans/) { 112 push(@ans, $server_arg); 113 } else { 114 print "$0: ns or ans directory expected"; 115 print "I:$test:failed"; 116 } 117 } else { 118 # Determine which servers need to be started for this test. 119 opendir DIR, $testdir or die "unable to read test directory: \"$test\" ($OS_ERROR)\n"; 120 my @files = sort readdir DIR; 121 closedir DIR; 122 123 @ns = grep /^ns[0-9]*$/, @files; 124 @ans = grep /^ans[0-9]*$/, @files; 125 } 126 127 # Start the servers we found. 128 129 foreach my $name(@ns) { 130 my $instances_so_far = count_running_lines($name); 131 &check_ns_port($name); 132 &start_ns_server($name, $options_arg); 133 &verify_ns_server($name, $instances_so_far); 134 } 135 136 foreach my $name(@ans) { 137 &start_ans_server($name); 138 } 139 140 # Subroutines 141 142 sub read_ns_port { 143 my ( $server ) = @_; 144 my $port = $queryport; 145 my $options = ""; 146 147 if ($server) { 148 my $file = $testdir . "/" . $server . "/named.port"; 149 150 if (-e $file) { 151 open(my $fh, "<", $file) or die "unable to read ports file \"$file\" ($OS_ERROR)"; 152 153 my $line = <$fh>; 154 155 if ($line) { 156 chomp $line; 157 $port = $line; 158 } 159 } 160 } 161 return ($port); 162 } 163 164 sub check_ns_port { 165 my ( $server ) = @_; 166 my $options = ""; 167 my $port = read_ns_port($server); 168 169 if ($server =~ /(\d+)$/) { 170 $options = "-i $1"; 171 } 172 173 my $tries = 0; 174 175 while (1) { 176 my $return = system("$PERL $srcdir/testsock.pl -p $port $options"); 177 178 if ($return == 0) { 179 last; 180 } 181 182 $tries++; 183 184 if ($tries > 4) { 185 print "$0: could not bind to server addresses, still running?\n"; 186 print "I:$test:server sockets not available\n"; 187 print "I:$test:failed\n"; 188 189 system("$PERL $srcdir/stop.pl $test"); # Is this the correct behavior? 190 191 exit 1; 192 } 193 194 print "I:$test:Couldn't bind to socket (yet)\n"; 195 sleep 2; 196 } 197 } 198 199 sub start_server { 200 my ( $server, $command, $pid_file ) = @_; 201 202 chdir "$testdir/$server" or die "unable to chdir \"$testdir/$server\" ($OS_ERROR)\n"; 203 204 # start the server 205 my $child = `$command`; 206 chomp($child); 207 208 # wait up to 90 seconds for the server to start and to write the 209 # pid file otherwise kill this server and any others that have 210 # already been started 211 my $tries = 0; 212 while (!-s $pid_file) { 213 if (++$tries > 900) { 214 print "I:$test:Couldn't start server $command (pid=$child)\n"; 215 print "I:$test:failed\n"; 216 kill "ABRT", $child if ("$child" ne ""); 217 chdir "$testdir"; 218 system "$PERL $srcdir/stop.pl $test"; 219 exit 1; 220 } 221 sleep 0.1; 222 } 223 224 # go back to the top level directory 225 chdir $builddir; 226 } 227 228 sub construct_ns_command { 229 my ( $server, $options ) = @_; 230 231 my $command; 232 233 if ($taskset) { 234 $command = "taskset $taskset $NAMED "; 235 } elsif ($ENV{'USE_RR'}) { 236 $ENV{'_RR_TRACE_DIR'} = "."; 237 $command = "$ENV{'TOP_BUILDDIR'}/libtool --mode=execute rr record --chaos $NAMED "; 238 } else { 239 $command = "$NAMED "; 240 } 241 242 my $args_file = $testdir . "/" . $server . "/" . "named.args"; 243 244 if ($options) { 245 $command .= $options; 246 } elsif (-e $args_file) { 247 open(my $fh, "<", $args_file) or die "unable to read args_file \"$args_file\" ($OS_ERROR)\n"; 248 249 while(my $line=<$fh>) { 250 next if ($line =~ /^\s*$/); #discard blank lines 251 next if ($line =~ /^\s*#/); #discard comment lines 252 253 chomp $line; 254 255 $line =~ s/#.*$//; 256 257 $command .= $line; 258 259 last; 260 } 261 } else { 262 $command .= "-D $test-$server "; 263 $command .= "-m record "; 264 265 foreach my $t_option( 266 "dropedns", "ednsformerr", "ednsnotimp", "ednsrefused", 267 "cookiealwaysvalid", "noaa", "noedns", "nosoa", 268 "maxudp512", "maxudp1460", 269 ) { 270 if (-e "$testdir/$server/named.$t_option") { 271 $command .= "-T $t_option " 272 } 273 } 274 275 $command .= "-c named.conf -d 99 -g -T maxcachesize=2097152"; 276 } 277 278 if (-e "$testdir/$server/named.notcp") { 279 $command .= " -T notcp" 280 } 281 282 if ($restart) { 283 $command .= " >>named.run 2>&1 &"; 284 } else { 285 $command .= " >named.run 2>&1 &"; 286 } 287 288 # get the shell to report the pid of the server ($!) 289 $command .= " echo \$!"; 290 291 return $command; 292 } 293 294 sub start_ns_server { 295 my ( $server, $options ) = @_; 296 297 my $cleanup_files; 298 my $command; 299 my $pid_file; 300 301 $cleanup_files = "{./*.jnl,./*.bk,./*.st,./named.run}"; 302 303 $command = construct_ns_command($server, $options); 304 305 $pid_file = "named.pid"; 306 307 if ($clean) { 308 unlink glob $cleanup_files; 309 } 310 311 start_server($server, $command, $pid_file); 312 } 313 314 sub construct_ans_command { 315 my ( $server, $options ) = @_; 316 317 my $command; 318 my $n; 319 320 if ($server =~ /^ans(\d+)/) { 321 $n = $1; 322 } else { 323 die "unable to parse server number from name \"$server\"\n"; 324 } 325 326 if (-e "$testdir/$server/ans.py") { 327 $ENV{'PYTHONPATH'} = $testdir . ":" . $builddir; 328 $command = "$PYTHON -u ans.py 10.53.0.$n $queryport"; 329 } elsif (-e "$testdir/$server/ans.pl") { 330 $command = "$PERL ans.pl"; 331 } else { 332 $command = "$PERL $srcdir/ans.pl 10.53.0.$n"; 333 } 334 335 if ($options) { 336 $command .= $options; 337 } 338 339 if ($restart) { 340 $command .= " >>ans.run 2>&1 &"; 341 } else { 342 $command .= " >ans.run 2>&1 &"; 343 } 344 345 # get the shell to report the pid of the server ($!) 346 $command .= " echo \$!"; 347 348 return $command; 349 } 350 351 sub start_ans_server { 352 my ( $server, $options ) = @_; 353 354 my $cleanup_files; 355 my $command; 356 my $pid_file; 357 358 $cleanup_files = "{./ans.run}"; 359 $command = construct_ans_command($server, $options); 360 $pid_file = "ans.pid"; 361 362 if ($clean) { 363 unlink glob $cleanup_files; 364 } 365 366 start_server($server, $command, $pid_file); 367 } 368 369 sub count_running_lines { 370 my ( $server ) = @_; 371 372 my $runfile = "$testdir/$server/named.run"; 373 374 # the shell *ought* to have created the file immediately, but this 375 # logic allows the creation to be delayed without issues 376 if (open(my $fh, "<", $runfile)) { 377 # the two non-whitespace blobs should be the date and time 378 # but we don't care about them really, only that they are there 379 return scalar(grep /^\S+ \S+ running\R/, <$fh>); 380 } else { 381 return 0; 382 } 383 } 384 385 sub verify_ns_server { 386 my ( $server, $instances_so_far ) = @_; 387 388 my $tries = 0; 389 390 while (count_running_lines($server) < $instances_so_far + 1) { 391 $tries++; 392 393 if ($tries >= 30) { 394 print "I:$test:server $server seems to have not started\n"; 395 print "I:$test:failed\n"; 396 397 system("$PERL $srcdir/stop.pl $test"); 398 399 exit 1; 400 } 401 402 sleep 2; 403 } 404 405 $tries = 0; 406 407 my $port = read_ns_port($server); 408 my $tcp = "+tcp"; 409 my $n; 410 411 if ($server =~ /^ns(\d+)/) { 412 $n = $1; 413 } else { 414 die "unable to parse server number from name \"$server\"\n"; 415 } 416 417 if (-e "$testdir/$server/named.notcp") { 418 $tcp = ""; 419 } 420 421 my $ip = "10.53.0.$n"; 422 if (-e "$testdir/$server/named.ipv6-only") { 423 $ip = "fd92:7065:b8e:ffff::$n"; 424 } 425 426 while (1) { 427 my $return = system("$DIG $tcp +noadd +nosea +nostat +noquest +nocomm +nocmd +noedns -p $port version.bind. chaos txt \@$ip > /dev/null"); 428 429 last if ($return == 0); 430 431 $tries++; 432 433 if ($tries >= 30) { 434 print "I:$test:no response from $server\n"; 435 print "I:$test:failed\n"; 436 437 system("$PERL $srcdir/stop.pl $test"); 438 439 exit 1; 440 } 441 442 sleep 2; 443 } 444 } 445