Home | History | Annotate | Line # | Download | only in system
      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