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 19use strict; 20use warnings; 21 22use Cwd ':DEFAULT', 'abs_path'; 23use English '-no_match_vars'; 24use Getopt::Long; 25use 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 66my $usage = "usage: $0 [--noclean] [--restart] [--port <port>] [--taskset <cpus>] test-directory [server-directory [server-options]]"; 67my $clean = 1; 68my $restart = 0; 69my $queryport = 5300; 70my $taskset = ""; 71 72GetOptions( 73 'clean!' => \$clean, 74 'restart!' => \$restart, 75 'port=i' => \$queryport, 76 'taskset=s' => \$taskset, 77) or die "$usage\n"; 78 79my( $test, $server_arg, $options_arg ) = @ARGV; 80 81if (!$test) { 82 die "$usage\n"; 83} 84 85# Global variables 86my $topdir = abs_path($ENV{'SYSTEMTESTTOP'}); 87my $testdir = abs_path($topdir . "/" . $test); 88 89if (! -d $testdir) { 90 die "No test directory: \"$testdir\"\n"; 91} 92 93if ($server_arg && ! -d "$testdir/$server_arg") { 94 die "No server directory: \"$testdir/$server_arg\"\n"; 95} 96 97my $NAMED = $ENV{'NAMED'}; 98my $DIG = $ENV{'DIG'}; 99my $PERL = $ENV{'PERL'}; 100my $PYTHON = $ENV{'PYTHON'}; 101 102# Start the server(s) 103 104my @ns; 105my @ans; 106 107if ($server_arg) { 108 if ($server_arg =~ /^ns/) { 109 push(@ns, $server_arg); 110 } elsif ($server_arg =~ /^ans/) { 111 push(@ans, $server_arg); 112 } else { 113 print "$0: ns or ans directory expected"; 114 print "I:$test:failed"; 115 } 116} else { 117 # Determine which servers need to be started for this test. 118 opendir DIR, $testdir or die "unable to read test directory: \"$test\" ($OS_ERROR)\n"; 119 my @files = sort readdir DIR; 120 closedir DIR; 121 122 @ns = grep /^ns[0-9]*$/, @files; 123 @ans = grep /^ans[0-9]*$/, @files; 124} 125 126# Start the servers we found. 127 128foreach my $name(@ns) { 129 my $instances_so_far = count_running_lines($name); 130 &check_ns_port($name); 131 &start_ns_server($name, $options_arg); 132 &verify_ns_server($name, $instances_so_far); 133} 134 135foreach my $name(@ans) { 136 &start_ans_server($name); 137} 138 139# Subroutines 140 141sub read_ns_port { 142 my ( $server ) = @_; 143 my $port = $queryport; 144 my $options = ""; 145 146 if ($server) { 147 my $file = $testdir . "/" . $server . "/named.port"; 148 149 if (-e $file) { 150 open(my $fh, "<", $file) or die "unable to read ports file \"$file\" ($OS_ERROR)"; 151 152 my $line = <$fh>; 153 154 if ($line) { 155 chomp $line; 156 $port = $line; 157 } 158 } 159 } 160 return ($port); 161} 162 163sub check_ns_port { 164 my ( $server ) = @_; 165 my $options = ""; 166 my $port = read_ns_port($server); 167 168 if ($server =~ /(\d+)$/) { 169 $options = "-i $1"; 170 } 171 172 my $tries = 0; 173 174 while (1) { 175 my $return = system("$PERL $topdir/testsock.pl -p $port $options"); 176 177 if ($return == 0) { 178 last; 179 } 180 181 $tries++; 182 183 if ($tries > 4) { 184 print "$0: could not bind to server addresses, still running?\n"; 185 print "I:$test:server sockets not available\n"; 186 print "I:$test:failed\n"; 187 188 system("$PERL $topdir/stop.pl $test"); # Is this the correct behavior? 189 190 exit 1; 191 } 192 193 print "I:$test:Couldn't bind to socket (yet)\n"; 194 sleep 2; 195 } 196} 197 198sub start_server { 199 my ( $server, $command, $pid_file ) = @_; 200 201 chdir "$testdir/$server" or die "unable to chdir \"$testdir/$server\" ($OS_ERROR)\n"; 202 203 # start the server 204 my $child = `$command`; 205 chomp($child); 206 207 # wait up to 14 seconds for the server to start and to write the 208 # pid file otherwise kill this server and any others that have 209 # already been started 210 my $tries = 0; 211 while (!-s $pid_file) { 212 if (++$tries > 140) { 213 print "I:$test:Couldn't start server $command (pid=$child)\n"; 214 print "I:$test:failed\n"; 215 kill "ABRT", $child if ("$child" ne ""); 216 chdir "$testdir"; 217 system "$PERL $topdir/stop.pl $test"; 218 exit 1; 219 } 220 sleep 0.1; 221 } 222 223 # go back to the top level directory 224 chdir $topdir; 225} 226 227sub construct_ns_command { 228 my ( $server, $options ) = @_; 229 230 my $command; 231 232 if ($ENV{'USE_VALGRIND'}) { 233 $command = "valgrind -q --gen-suppressions=all --num-callers=48 --fullpath-after= --log-file=named-$server-valgrind-%p.log "; 234 235 if ($ENV{'USE_VALGRIND'} eq 'helgrind') { 236 $command .= "--tool=helgrind "; 237 } else { 238 $command .= "--tool=memcheck --track-origins=yes --leak-check=full "; 239 } 240 241 $command .= "$NAMED -m none -M external "; 242 } else { 243 if ($taskset) { 244 $command = "taskset $taskset $NAMED "; 245 } else { 246 $command = "$NAMED "; 247 } 248 } 249 250 my $args_file = $testdir . "/" . $server . "/" . "named.args"; 251 252 if ($options) { 253 $command .= $options; 254 } elsif (-e $args_file) { 255 open(my $fh, "<", $args_file) or die "unable to read args_file \"$args_file\" ($OS_ERROR)\n"; 256 257 while(my $line=<$fh>) { 258 next if ($line =~ /^\s*$/); #discard blank lines 259 next if ($line =~ /^\s*#/); #discard comment lines 260 261 chomp $line; 262 263 $line =~ s/#.*$//; 264 265 $command .= $line; 266 267 last; 268 } 269 } else { 270 $command .= "-D $test-$server "; 271 $command .= "-X named.lock "; 272 $command .= "-m record,size,mctx "; 273 274 foreach my $t_option( 275 "dropedns", "ednsformerr", "ednsnotimp", "ednsrefused", 276 "noaa", "noedns", "nosoa", "maxudp512", "maxudp1460", 277 ) { 278 if (-e "$testdir/$server/named.$t_option") { 279 $command .= "-T $t_option " 280 } 281 } 282 283 $command .= "-c named.conf -d 99 -g -U 4 -T maxcachesize=2097152"; 284 } 285 286 if (-e "$testdir/$server/named.notcp") { 287 $command .= " -T notcp" 288 } 289 290 if ($restart) { 291 $command .= " >>named.run 2>&1 &"; 292 } else { 293 $command .= " >named.run 2>&1 &"; 294 } 295 296 # get the shell to report the pid of the server ($!) 297 $command .= " echo \$!"; 298 299 return $command; 300} 301 302sub start_ns_server { 303 my ( $server, $options ) = @_; 304 305 my $cleanup_files; 306 my $command; 307 my $pid_file; 308 309 $cleanup_files = "{./*.jnl,./*.bk,./*.st,./named.run}"; 310 311 $command = construct_ns_command($server, $options); 312 313 $pid_file = "named.pid"; 314 315 if ($clean) { 316 unlink glob $cleanup_files; 317 } 318 319 start_server($server, $command, $pid_file); 320} 321 322sub construct_ans_command { 323 my ( $server, $options ) = @_; 324 325 my $command; 326 my $n; 327 328 if ($server =~ /^ans(\d+)/) { 329 $n = $1; 330 } else { 331 die "unable to parse server number from name \"$server\"\n"; 332 } 333 334 if (-e "$testdir/$server/ans.py") { 335 $command = "$PYTHON -u ans.py 10.53.0.$n $queryport"; 336 } elsif (-e "$testdir/$server/ans.pl") { 337 $command = "$PERL ans.pl"; 338 } else { 339 $command = "$PERL $topdir/ans.pl 10.53.0.$n"; 340 } 341 342 if ($options) { 343 $command .= $options; 344 } 345 346 if ($restart) { 347 $command .= " >>ans.run 2>&1 &"; 348 } else { 349 $command .= " >ans.run 2>&1 &"; 350 } 351 352 # get the shell to report the pid of the server ($!) 353 $command .= " echo \$!"; 354 355 return $command; 356} 357 358sub start_ans_server { 359 my ( $server, $options ) = @_; 360 361 my $cleanup_files; 362 my $command; 363 my $pid_file; 364 365 $cleanup_files = "{./ans.run}"; 366 $command = construct_ans_command($server, $options); 367 $pid_file = "ans.pid"; 368 369 if ($clean) { 370 unlink glob $cleanup_files; 371 } 372 373 start_server($server, $command, $pid_file); 374} 375 376sub count_running_lines { 377 my ( $server ) = @_; 378 379 my $runfile = "$testdir/$server/named.run"; 380 381 # the shell *ought* to have created the file immediately, but this 382 # logic allows the creation to be delayed without issues 383 if (open(my $fh, "<", $runfile)) { 384 # the two non-whitespace blobs should be the date and time 385 # but we don't care about them really, only that they are there 386 return scalar(grep /^\S+ \S+ running\R/, <$fh>); 387 } else { 388 return 0; 389 } 390} 391 392sub verify_ns_server { 393 my ( $server, $instances_so_far ) = @_; 394 395 my $tries = 0; 396 397 while (count_running_lines($server) < $instances_so_far + 1) { 398 $tries++; 399 400 if ($tries >= 30) { 401 print "I:$test:server $server seems to have not started\n"; 402 print "I:$test:failed\n"; 403 404 system("$PERL $topdir/stop.pl $test"); 405 406 exit 1; 407 } 408 409 sleep 2; 410 } 411 412 $tries = 0; 413 414 my $port = read_ns_port($server); 415 my $tcp = "+tcp"; 416 my $n; 417 418 if ($server =~ /^ns(\d+)/) { 419 $n = $1; 420 } else { 421 die "unable to parse server number from name \"$server\"\n"; 422 } 423 424 if (-e "$testdir/$server/named.notcp") { 425 $tcp = ""; 426 } 427 428 my $ip = "10.53.0.$n"; 429 if (-e "$testdir/$server/named.ipv6-only") { 430 $ip = "fd92:7065:b8e:ffff::$n"; 431 } 432 433 while (1) { 434 my $return = system("$DIG $tcp +noadd +nosea +nostat +noquest +nocomm +nocmd +noedns -p $port version.bind. chaos txt \@$ip > /dev/null"); 435 436 last if ($return == 0); 437 438 $tries++; 439 440 if ($tries >= 30) { 441 print "I:$test:no response from $server\n"; 442 print "I:$test:failed\n"; 443 444 system("$PERL $topdir/stop.pl $test"); 445 446 exit 1; 447 } 448 449 sleep 2; 450 } 451} 452