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