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