1 #!/bin/sh 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 # When sourcing the script outside the pytest environment (e.g. during helper 15 # script development), the env variables have to be loaded. 16 if [ -z "$TOP_SRCDIR" ]; then 17 SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd | sed -E 's|(.*bin/tests/system).*|\1|') 18 eval "$(PYTHONPATH="$SCRIPT_DIR:$PYTHONPATH" /usr/bin/env python3 -m isctest)" 19 fi 20 21 testsock6() { 22 if test -n "$PERL" && $PERL -e "use IO::Socket::IP;" 2>/dev/null; then 23 $PERL "$TOP_SRCDIR/bin/tests/system/testsock6.pl" "$@" 24 else 25 false 26 fi 27 } 28 29 echofail() { 30 echo "$*" 31 } 32 echowarn() { 33 echo "$*" 34 } 35 echopass() { 36 echo "$*" 37 } 38 echoinfo() { 39 echo "$*" 40 } 41 echostart() { 42 echo "$*" 43 } 44 echoend() { 45 echo "$*" 46 } 47 48 echo_i() { 49 echo "$@" | while IFS= read -r __LINE; do 50 echoinfo "I:$__LINE" 51 done 52 } 53 54 echo_ic() { 55 echo "$@" | while IFS= read -r __LINE; do 56 echoinfo "I: $__LINE" 57 done 58 } 59 60 echo_d() { 61 echo "$@" | while IFS= read -r __LINE; do 62 echoinfo "D:$__LINE" 63 done 64 } 65 66 cat_i() { 67 while IFS= read -r __LINE; do 68 echoinfo "I:$__LINE" 69 done 70 } 71 72 cat_d() { 73 while IFS= read -r __LINE; do 74 echoinfo "D:$__LINE" 75 done 76 } 77 78 digcomp() { 79 { 80 output=$($PERL $TOP_SRCDIR/bin/tests/system/digcomp.pl "$@") 81 result=$? 82 } || true 83 [ -n "$output" ] && { 84 echo "digcomp failed:" 85 echo "$output" 86 } | cat_i 87 return $result 88 } 89 90 start_server() { 91 $PERL "$TOP_SRCDIR/bin/tests/system/start.pl" "$SYSTESTDIR" "$@" 92 } 93 94 stop_server() { 95 $PERL "$TOP_SRCDIR/bin/tests/system/stop.pl" "$SYSTESTDIR" "$@" 96 } 97 98 send() { 99 $PERL "$TOP_SRCDIR/bin/tests/system/send.pl" "$@" 100 } 101 102 # 103 # Useful functions in test scripts 104 # 105 106 # assert_int_equal: compare two integer variables, $1 and $2 107 # 108 # If $1 and $2 are equal, return 0; if $1 and $2 are not equal, report 109 # the error using the description of the tested variable provided in $3 110 # and return 1. 111 assert_int_equal() { 112 found="$1" 113 expected="$2" 114 description="$3" 115 116 if [ "${expected}" -ne "${found}" ]; then 117 echo_i "incorrect ${description}: got ${found}, expected ${expected}" 118 return 1 119 fi 120 121 return 0 122 } 123 124 # keyfile_to_keys_section: helper function for keyfile_to_*_keys() which 125 # converts keyfile data into a key-style trust anchor configuration 126 # section using the supplied parameters 127 keyfile_to_keys() { 128 section_name=$1 129 key_prefix=$2 130 shift 131 shift 132 echo "$section_name {" 133 for keyname in $*; do 134 awk '!/^; /{ 135 printf "\t\""$1"\" " 136 printf "'"$key_prefix "'" 137 printf $4 " " $5 " " $6 " \"" 138 for (i=7; i<=NF; i++) printf $i 139 printf "\";\n" 140 }' $keyname.key 141 done 142 echo "};" 143 } 144 145 # keyfile_to_dskeys_section: helper function for keyfile_to_*_dskeys() 146 # converts keyfile data into a DS-style trust anchor configuration 147 # section using the supplied parameters 148 keyfile_to_dskeys() { 149 section_name=$1 150 key_prefix=$2 151 shift 152 shift 153 echo "$section_name {" 154 for keyname in $*; do 155 $DSFROMKEY $keyname.key \ 156 | awk '!/^; /{ 157 printf "\t\""$1"\" " 158 printf "'"$key_prefix "'" 159 printf $4 " " $5 " " $6 " \"" 160 for (i=7; i<=NF; i++) printf $i 161 printf "\";\n" 162 }' 163 done 164 echo "};" 165 } 166 167 # keyfile_to_trusted_keys: convert key data contained in the keyfile(s) 168 # provided to a "trust-keys" section suitable for including in a 169 # resolver's configuration file 170 keyfile_to_trusted_keys() { 171 keyfile_to_keys "trusted-keys" "" $* 172 } 173 174 # keyfile_to_static_keys: convert key data contained in the keyfile(s) 175 # provided to a *static-key* "trust-anchors" section suitable for including in 176 # a resolver's configuration file 177 keyfile_to_static_keys() { 178 keyfile_to_keys "trust-anchors" "static-key" $* 179 } 180 181 # keyfile_to_initial_keys: convert key data contained in the keyfile(s) 182 # provided to an *initial-key* "trust-anchors" section suitable for including 183 # in a resolver's configuration file 184 keyfile_to_initial_keys() { 185 keyfile_to_keys "trust-anchors" "initial-key" $* 186 } 187 188 # keyfile_to_static_ds_keys: convert key data contained in the keyfile(s) 189 # provided to a *static-ds* "trust-anchors" section suitable for including in a 190 # resolver's configuration file 191 keyfile_to_static_ds() { 192 keyfile_to_dskeys "trust-anchors" "static-ds" $* 193 } 194 195 # keyfile_to_initial_ds_keys: convert key data contained in the keyfile(s) 196 # provided to an *initial-ds* "trust-anchors" section suitable for including 197 # in a resolver's configuration file 198 keyfile_to_initial_ds() { 199 keyfile_to_dskeys "trust-anchors" "initial-ds" $* 200 } 201 202 # keyfile_to_key_id: convert a key file name to a key ID 203 # 204 # For a given key file name (e.g. "Kexample.+013+06160") provided as $1, 205 # print the key ID with leading zeros stripped ("6160" for the 206 # aforementioned example). 207 keyfile_to_key_id() { 208 echo "$1" | sed "s/.*+0\{0,4\}//" 209 } 210 211 # private_type_record: write a private type record recording the state of the 212 # signing process 213 # 214 # For a given zone ($1), algorithm number ($2) and key file ($3), print the 215 # private type record with default type value of 65534, indicating that the 216 # signing process for this key is completed. 217 private_type_record() { 218 _zone=$1 219 _algorithm=$2 220 _keyfile=$3 221 222 _id=$(keyfile_to_key_id "$_keyfile") 223 224 printf "%s. 0 IN TYPE65534 %s 5 %02x%04x0000\n" "$_zone" "\\#" "$_algorithm" "$_id" 225 } 226 227 # nextpart*() - functions for reading files incrementally 228 # 229 # These functions aim to facilitate looking for (or waiting for) 230 # messages which may be logged more than once throughout the lifetime of 231 # a given named instance by outputting just the part of the file which 232 # has been appended since the last time we read it. 233 # 234 # Calling some of these functions causes temporary *.prev files to be 235 # created. 236 # 237 # Note that unlike other nextpart*() functions, nextpartread() is not 238 # meant to be directly used in system tests; its sole purpose is to 239 # reduce code duplication below. 240 # 241 # A quick usage example: 242 # 243 # $ echo line1 > named.log 244 # $ echo line2 >> named.log 245 # $ nextpart named.log 246 # line1 247 # line2 248 # $ echo line3 >> named.log 249 # $ nextpart named.log 250 # line3 251 # $ nextpart named.log 252 # $ echo line4 >> named.log 253 # $ nextpartpeek named.log 254 # line4 255 # $ nextpartpeek named.log 256 # line4 257 # $ nextpartreset named.log 258 # $ nextpartpeek named.log 259 # line1 260 # line2 261 # line3 262 # line4 263 # $ nextpart named.log 264 # line1 265 # line2 266 # line3 267 # line4 268 # $ nextpart named.log 269 # $ 270 271 # nextpartreset: reset the marker used by nextpart() and nextpartpeek() 272 # so that it points to the start of the given file 273 nextpartreset() { 274 echo "0" >$1.prev 275 } 276 277 # nextpartread: read everything that's been appended to a file since the 278 # last time nextpart() was called and print it to stdout, print the 279 # total number of lines read from that file so far to file descriptor 3 280 nextpartread() { 281 [ -f $1.prev ] || nextpartreset $1 282 prev=$(cat $1.prev) 283 awk "NR > $prev "'{ print } 284 END { print NR > "/dev/stderr" }' $1 2>&3 285 } 286 287 # nextpart: read everything that's been appended to a file since the 288 # last time nextpart() was called 289 nextpart() { 290 nextpartread $1 3>$1.prev.tmp 291 mv $1.prev.tmp $1.prev 292 } 293 294 # nextpartpeek: read everything that's been appended to a file since the 295 # last time nextpart() was called 296 nextpartpeek() { 297 nextpartread $1 3>/dev/null 298 } 299 300 # _search_log: look for message $1 in file $2 with nextpart(). 301 _search_log() ( 302 msg="$1" 303 file="$2" 304 nextpart "$file" | grep -F -e "$msg" >/dev/null 305 ) 306 307 # _search_log_re: same as _search_log but the message is an grep -E regex 308 _search_log_re() ( 309 msg="$1" 310 file="$2" 311 nextpart "$file" | grep -E -e "$msg" >/dev/null 312 ) 313 314 # _search_log_peek: look for message $1 in file $2 with nextpartpeek(). 315 _search_log_peek() ( 316 msg="$1" 317 file="$2" 318 nextpartpeek "$file" | grep -F -e "$msg" >/dev/null 319 ) 320 321 # wait_for_log: wait until message $2 in file $3 appears. Bail out after 322 # $1 seconds. This needs to be used in conjunction with a prior call to 323 # nextpart() or nextpartreset() on the same file to guarantee the offset is 324 # set correctly. Tests using wait_for_log() are responsible for cleaning up 325 # the created <file>.prev files. 326 wait_for_log() ( 327 timeout="$1" 328 msg="$2" 329 file="$3" 330 retry_quiet "$timeout" _search_log "$msg" "$file" && return 0 331 echo_i "exceeded time limit waiting for literal '$msg' in $file" 332 return 1 333 ) 334 335 # wait_for_log_re: same as wait_for_log, but the message is an grep -E regex 336 wait_for_log_re() ( 337 timeout="$1" 338 msg="$2" 339 file="$3" 340 retry_quiet "$timeout" _search_log_re "$msg" "$file" && return 0 341 echo_i "exceeded time limit waiting for regex '$msg' in $file" 342 return 1 343 ) 344 345 # wait_for_log_peek: similar to wait_for_log() but peeking, so the file offset 346 # does not change. 347 wait_for_log_peek() ( 348 timeout="$1" 349 msg="$2" 350 file="$3" 351 retry_quiet "$timeout" _search_log_peek "$msg" "$file" && return 0 352 echo_i "exceeded time limit waiting for literal '$msg' in $file" 353 return 1 354 ) 355 356 # _retry: keep running a command until it succeeds, up to $1 times, with 357 # one-second intervals, optionally printing a message upon every attempt 358 _retry() { 359 __retries="${1}" 360 shift 361 362 while :; do 363 if "$@"; then 364 return 0 365 fi 366 __retries=$((__retries - 1)) 367 if [ "${__retries}" -gt 0 ]; then 368 if [ "${__retry_quiet}" -ne 1 ]; then 369 echo_i "retrying" 370 fi 371 sleep 1 372 else 373 return 1 374 fi 375 done 376 } 377 378 # retry: call _retry() in verbose mode 379 retry() { 380 __retry_quiet=0 381 _retry "$@" 382 } 383 384 # retry_quiet: call _retry() in silent mode 385 retry_quiet() { 386 __retry_quiet=1 387 _retry "$@" 388 } 389 390 # _repeat: keep running command up to $1 times, unless it fails 391 _repeat() ( 392 __retries="${1}" 393 shift 394 while :; do 395 if ! "$@"; then 396 return 1 397 fi 398 __retries=$((__retries - 1)) 399 if [ "${__retries}" -le 0 ]; then 400 break 401 fi 402 done 403 return 0 404 ) 405 406 _times() { 407 awk "BEGIN{ for(i = 1; i <= $1; i++) print i}" 408 } 409 410 rndc_reload() { 411 $RNDC -c ../_common/rndc.conf -s $2 -p ${CONTROLPORT} reload $3 2>&1 | sed 's/^/'"I:$1"' /' 412 # reloading single zone is synchronous, if we're reloading whole server 413 # we need to wait for reload to finish 414 if [ -z "$3" ]; then 415 for _ in $(_times 10); do 416 $RNDC -c ../_common/rndc.conf -s $2 -p ${CONTROLPORT} status | grep "reload/reconfig in progress" >/dev/null || break 417 sleep 1 418 done 419 fi 420 } 421 422 rndc_reconfig() { 423 seconds=${3:-10} 424 $RNDC -c ../_common/rndc.conf -s "$2" -p "${CONTROLPORT}" reconfig 2>&1 | sed 's/^/'"I:$1"' /' 425 for _ in $(_times "$seconds"); do 426 "$RNDC" -c ../_common/rndc.conf -s "$2" -p "${CONTROLPORT}" status | grep "reload/reconfig in progress" >/dev/null || break 427 sleep 1 428 done 429 } 430 431 # rndc_dumpdb: call "rndc dumpdb [...]" and wait until it completes 432 # 433 # The first argument is the name server instance to send the command to, in the 434 # form of "nsX" (where "X" is the instance number), e.g. "ns5". The remaining 435 # arguments, if any, are appended to the rndc command line after "dumpdb". 436 # 437 # Control channel configuration for the name server instance to send the 438 # command to must match the contents of bin/tests/system/_common/rndc.conf. 439 # 440 # rndc output is stored in a file called rndc.out.test${n}; the "n" variable is 441 # required to be set by the calling tests.sh script. 442 # 443 # Return 0 if the dump completes successfully; return 1 if rndc returns an exit 444 # code other than 0 or if the "; Dump complete" string does not appear in the 445 # dump within 10 seconds. 446 rndc_dumpdb() { 447 __ret=0 448 __dump_complete=0 449 __server="${1}" 450 __ip="10.53.0.$(echo "${__server}" | tr -c -d '[:digit:]')" 451 452 shift 453 ${RNDC} -c ../_common/rndc.conf -p "${CONTROLPORT}" -s "${__ip}" dumpdb "$@" >"rndc.out.test${n}" 2>&1 || __ret=1 454 455 for _ in 0 1 2 3 4 5 6 7 8 9; do 456 if grep '^; Dump complete$' "${__server}/named_dump.db" >/dev/null; then 457 mv "${__server}/named_dump.db" "${__server}/named_dump.db.test${n}" 458 __dump_complete=1 459 break 460 fi 461 sleep 1 462 done 463 464 if [ ${__dump_complete} -eq 0 ]; then 465 echo_i "timed out waiting for 'rndc dumpdb' to finish" 466 __ret=1 467 fi 468 469 return ${__ret} 470 } 471 472 # get_dig_xfer_stats: extract transfer statistics from dig output stored 473 # in $1, converting them to a format used by some system tests. 474 get_dig_xfer_stats() { 475 LOGFILE="$1" 476 sed -n "s/^;; XFR size: .*messages \([0-9][0-9]*\).*/messages=\1/p" "${LOGFILE}" 477 sed -n "s/^;; XFR size: \([0-9][0-9]*\) records.*/records=\1/p" "${LOGFILE}" 478 sed -n "s/^;; XFR size: .*bytes \([0-9][0-9]*\).*/bytes=\1/p" "${LOGFILE}" 479 } 480 481 # get_named_xfer_stats: from named log file $1, extract transfer 482 # statistics for the last transfer for peer $2 and zone $3 (from a log 483 # message which has to contain the string provided in $4), converting 484 # them to a format used by some system tests. 485 get_named_xfer_stats() { 486 LOGFILE="$1" 487 PEER="$(echo $2 | sed 's/\./\\./g')" 488 ZONE="$(echo $3 | sed 's/\./\\./g')" 489 MESSAGE="$4" 490 grep " ${PEER}#.*${MESSAGE}:" "${LOGFILE}" \ 491 | sed -n "s/.* '${ZONE}\/.* \([0-9][0-9]*\) messages.*/messages=\1/p" | tail -1 492 grep " ${PEER}#.*${MESSAGE}:" "${LOGFILE}" \ 493 | sed -n "s/.* '${ZONE}\/.* \([0-9][0-9]*\) records.*/records=\1/p" | tail -1 494 grep " ${PEER}#.*${MESSAGE}:" "${LOGFILE}" \ 495 | sed -n "s/.* '${ZONE}\/.* \([0-9][0-9]*\) bytes.*/bytes=\1/p" | tail -1 496 } 497 498 grep_v() { grep -v "$@" || test $? = 1; } 499