#!/bin/sh # # Copyright (C) Internet Systems Consortium, Inc. ("ISC") # # SPDX-License-Identifier: MPL-2.0 # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, you can obtain one at https://mozilla.org/MPL/2.0/. # # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. testsock6() { if test -n "$PERL" && $PERL -e "use IO::Socket::INET6;" 2> /dev/null then $PERL "$TOP/bin/tests/system/testsock6.pl" "$@" else false fi } export LANG=C . ${TOP}/version # # Common lists of system tests to run. # # The following tests are hard-coded to use ports 5300 and 9953. For # this reason, these must be run sequentially. # # Sequential tests that only run on unix/linux should be added to # SEQUENTIAL_UNIX in conf.sh.in; those that only run on windows should # be added to SEQUENTIAL_WINDOWS in conf.sh.win32. # SEQUENTIAL_COMMON="ecdsa eddsa tkey" # # These tests can use ports assigned by the caller (other than 5300 # and 9953). Because separate blocks of ports can be used for teach # test, these tests can be run in parallel. # # Parallel tests that only run on unix/linux should be added to # PARALLEL_UNIX in conf.sh.in; those that only run on windows should # be added to PARALLEL_WINDOWS in conf.sh.win32. # # Note: some of the longer-running tests such as serve-stale and # rpzrecurse are scheduled first, in order to get more benefit from # parallelism. # PARALLEL_COMMON="dnssec rpzrecurse serve-stale dupsigs \ acl \ additional \ addzone \ allow-query \ auth \ autosign \ builtin \ cacheclean \ case \ catz \ cds \ chain \ checkconf \ checkds \ checknames \ checkzone \ database \ digdelv \ dlz \ dlzexternal \ dns64 \ dscp \ dsdigest \ dyndb \ ednscompliance \ emptyzones \ fetchlimit \ filter-aaaa \ formerr \ forward \ geoip2 \ glue \ idna \ inline \ integrity \ ixfr \ journal \ kasp \ keepalive \ keymgr2kasp \ legacy \ limits \ masterfile \ masterformat \ metadata \ mirror \ mkeys \ names \ notify \ nsec3 \ nslookup \ nsupdate \ nzd2nzf \ padding \ pending \ pipelined \ qmin \ reclimit \ redirect \ resolver \ rndc \ rootkeysentinel \ rpz \ rrchecker \ rrl \ rrsetorder \ rsabigexponent \ runtime \ sfcache \ shutdown \ smartsign \ sortlist \ spf \ staticstub \ statistics \ statschannel \ stress \ stub \ synthfromdnssec \ timeouts \ tcp \ tools \ tsig \ tsiggss \ ttl \ unknown \ upforwd \ verify \ views \ wildcard \ xfer \ xferquota \ zero \ zonechecks" # # Set up color-coded test output # if [ ${SYSTEMTEST_FORCE_COLOR:-0} -eq 1 ] || test -t 1 && type tput > /dev/null 2>&1 && tput setaf 7 > /dev/null 2>&1 ; then export COLOR_END=`tput setaf 4` # blue export COLOR_FAIL=`tput setaf 1` # red export COLOR_INFO=`tput bold` # bold export COLOR_NONE=`tput sgr0` export COLOR_PASS=`tput setaf 2` # green export COLOR_START=`tput setaf 4` # blue export COLOR_WARN=`tput setaf 3` # yellow else # set to empty strings so printf succeeds export COLOR_END='' export COLOR_FAIL='' export COLOR_INFO='' export COLOR_NONE='' export COLOR_PASS='' export COLOR_START='' export COLOR_WARN='' fi export SYSTESTDIR="`basename $PWD`" if type printf > /dev/null 2>&1 then echofail () { printf "${COLOR_FAIL}%s${COLOR_NONE}\n" "$*" } echowarn () { printf "${COLOR_WARN}%s${COLOR_NONE}\n" "$*" } echopass () { printf "${COLOR_PASS}%s${COLOR_NONE}\n" "$*" } echoinfo () { printf "${COLOR_INFO}%s${COLOR_NONE}\n" "$*" } echostart () { printf "${COLOR_START}%s${COLOR_NONE}\n" "$*" } echoend () { printf "${COLOR_END}%s${COLOR_NONE}\n" "$*" } echo_i() { printf '%s\n' "$*" | while IFS= read -r __LINE ; do echoinfo "I:$SYSTESTDIR:$__LINE" done } echo_ic() { printf '%s\n' "$*" | while IFS= read -r __LINE ; do echoinfo "I:$SYSTESTDIR: $__LINE" done } echo_d() { printf '%s\n' "$*" | while IFS= read -r __LINE ; do echoinfo "D:$SYSTESTDIR:$__LINE" done } else echofail () { echo "$*" } echowarn () { echo "$*" } echopass () { echo "$*" } echoinfo () { echo "$*" } echostart () { echo "$*" } echoend () { echo "$*" } echo_i() { echo "$@" | while IFS= read -r __LINE ; do echoinfo "I:$SYSTESTDIR:$__LINE" done } echo_ic() { echo "$@" | while IFS= read -r __LINE ; do echoinfo "I:$SYSTESTDIR: $__LINE" done } echo_d() { echo "$@" | while IFS= read -r __LINE ; do echoinfo "D:$SYSTESTDIR:$__LINE" done } fi cat_i() { while IFS= read -r __LINE ; do echoinfo "I:$SYSTESTDIR:$__LINE" done } cat_d() { while IFS= read -r __LINE ; do echoinfo "D:$SYSTESTDIR:$__LINE" done } digcomp() { output=`$PERL $SYSTEMTESTTOP/digcomp.pl "$@"` result=$? [ -n "$output" ] && { echo "digcomp failed:"; echo "$output"; } | cat_i return $result } start_server() { $PERL "$TOP_SRCDIR/bin/tests/system/start.pl" "$SYSTESTDIR" "$@" } stop_server() { $PERL "$TOP_SRCDIR/bin/tests/system/stop.pl" "$SYSTESTDIR" "$@" } send() { $PERL "$TOP_SRCDIR/bin/tests/system/send.pl" "$@" } # # Useful variables in test scripts # # The following script sets the following algorithm-related variables. These # are selected randomly at runtime from a list of supported algorithms. The # randomization is deterministic and remains stable for a period of time for a # given platform. # # Default algorithm for testing. # DEFAULT_ALGORITHM # DEFAULT_ALGORITHM_NUMBER # DEFAULT_BITS # # This is an alternative algorithm for test cases that require more than one # algorithm (for example algorithm rollover). Must be different from # DEFAULT_ALGORITHM. # ALTERNATIVE_ALGORITHM # ALTERNATIVE_ALGORITHM_NUMBER # ALTERNATIVE_BITS # # This is an algorithm that is used for tests against the "disable-algorithms" # configuration option. Must be different from above algorithms. # DISABLED_ALGORITHM # DISABLED_ALGORITHM_NUMBER # DISABLED_BITS # # There are multiple algoritms sets to choose from (see get_algorithms.py). To # override the default choice, set the ALGORITHM_SET env var (see mkeys system # test for example). if test -x "$PYTHON" && test -x "$KEYGEN"; then eval "$($PYTHON "$TOP_SRCDIR/bin/tests/system/get_algorithms.py")" else # 9.16 workarounds # - for ./configure which calls bin/tests/system/cleanall.sh, which # includes this file before $KEYGEN is compiled # - for our Windows CI which lacks Python DEFAULT_ALGORITHM=ECDSAP256SHA256 DEFAULT_ALGORITHM_NUMBER=13 DEFAULT_BITS=256 ALTERNATIVE_ALGORITHM=RSASHA256 ALTERNATIVE_ALGORITHM_NUMBER=8 ALTERNATIVE_BITS=1280 DISABLED_ALGORITHM=ECDSAP384SHA384 DISABLED_ALGORITHM_NUMBER=14 DISABLED_BITS=384 fi # Default HMAC algorithm. export DEFAULT_HMAC=hmac-sha256 # # Useful functions in test scripts # # assert_int_equal: compare two integer variables, $1 and $2 # # If $1 and $2 are equal, return 0; if $1 and $2 are not equal, report # the error using the description of the tested variable provided in $3 # and return 1. assert_int_equal() { found="$1" expected="$2" description="$3" if [ "${expected}" -ne "${found}" ]; then echo_i "incorrect ${description}: got ${found}, expected ${expected}" return 1 fi return 0 } # keyfile_to_keys_section: helper function for keyfile_to_*_keys() which # converts keyfile data into a key-style trust anchor configuration # section using the supplied parameters keyfile_to_keys() { section_name=$1 key_prefix=$2 shift shift echo "$section_name {" for keyname in $*; do awk '!/^; /{ printf "\t\""$1"\" " printf "'"$key_prefix "'" printf $4 " " $5 " " $6 " \"" for (i=7; i<=NF; i++) printf $i printf "\";\n" }' $keyname.key done echo "};" } # keyfile_to_dskeys_section: helper function for keyfile_to_*_dskeys() # converts keyfile data into a DS-style trust anchor configuration # section using the supplied parameters keyfile_to_dskeys() { section_name=$1 key_prefix=$2 shift shift echo "$section_name {" for keyname in $*; do $DSFROMKEY $keyname.key | \ awk '!/^; /{ printf "\t\""$1"\" " printf "'"$key_prefix "'" printf $4 " " $5 " " $6 " \"" for (i=7; i<=NF; i++) printf $i printf "\";\n" }' done echo "};" } # keyfile_to_trusted_keys: convert key data contained in the keyfile(s) # provided to a "trust-keys" section suitable for including in a # resolver's configuration file keyfile_to_trusted_keys() { keyfile_to_keys "trusted-keys" "" $* } # keyfile_to_static_keys: convert key data contained in the keyfile(s) # provided to a *static-key* "trust-anchors" section suitable for including in # a resolver's configuration file keyfile_to_static_keys() { keyfile_to_keys "trust-anchors" "static-key" $* } # keyfile_to_initial_keys: convert key data contained in the keyfile(s) # provided to an *initial-key* "trust-anchors" section suitable for including # in a resolver's configuration file keyfile_to_initial_keys() { keyfile_to_keys "trust-anchors" "initial-key" $* } # keyfile_to_static_ds_keys: convert key data contained in the keyfile(s) # provided to a *static-ds* "trust-anchors" section suitable for including in a # resolver's configuration file keyfile_to_static_ds() { keyfile_to_dskeys "trust-anchors" "static-ds" $* } # keyfile_to_initial_ds_keys: convert key data contained in the keyfile(s) # provided to an *initial-ds* "trust-anchors" section suitable for including # in a resolver's configuration file keyfile_to_initial_ds() { keyfile_to_dskeys "trust-anchors" "initial-ds" $* } # keyfile_to_key_id: convert a key file name to a key ID # # For a given key file name (e.g. "Kexample.+013+06160") provided as $1, # print the key ID with leading zeros stripped ("6160" for the # aforementioned example). keyfile_to_key_id() { echo "$1" | sed "s/.*+0\{0,4\}//" } # private_type_record: write a private type record recording the state of the # signing process # # For a given zone ($1), algorithm number ($2) and key file ($3), print the # private type record with default type value of 65534, indicating that the # signing process for this key is completed. private_type_record() { _zone=$1 _algorithm=$2 _keyfile=$3 _id=$(keyfile_to_key_id "$_keyfile") printf "%s. 0 IN TYPE65534 %s 5 %02x%04x0000\n" "$_zone" "\\#" "$_algorithm" "$_id" } # nextpart*() - functions for reading files incrementally # # These functions aim to facilitate looking for (or waiting for) # messages which may be logged more than once throughout the lifetime of # a given named instance by outputting just the part of the file which # has been appended since the last time we read it. # # Calling some of these functions causes temporary *.prev files to be # created that need to be cleaned up manually (usually by a given system # test's clean.sh script). # # Note that unlike other nextpart*() functions, nextpartread() is not # meant to be directly used in system tests; its sole purpose is to # reduce code duplication below. # # A quick usage example: # # $ echo line1 > named.log # $ echo line2 >> named.log # $ nextpart named.log # line1 # line2 # $ echo line3 >> named.log # $ nextpart named.log # line3 # $ nextpart named.log # $ echo line4 >> named.log # $ nextpartpeek named.log # line4 # $ nextpartpeek named.log # line4 # $ nextpartreset named.log # $ nextpartpeek named.log # line1 # line2 # line3 # line4 # $ nextpart named.log # line1 # line2 # line3 # line4 # $ nextpart named.log # $ # nextpartreset: reset the marker used by nextpart() and nextpartpeek() # so that it points to the start of the given file nextpartreset() { echo "0" > $1.prev } # nextpartread: read everything that's been appended to a file since the # last time nextpart() was called and print it to stdout, print the # total number of lines read from that file so far to file descriptor 3 nextpartread() { [ -f $1.prev ] || nextpartreset $1 prev=`cat $1.prev` awk "NR > $prev "'{ print } END { print NR > "/dev/stderr" }' $1 2>&3 } # nextpart: read everything that's been appended to a file since the # last time nextpart() was called nextpart() { nextpartread $1 3> $1.prev.tmp mv $1.prev.tmp $1.prev } # nextpartpeek: read everything that's been appended to a file since the # last time nextpart() was called nextpartpeek() { nextpartread $1 3> /dev/null } # _search_log: look for message $1 in file $2 with nextpart(). _search_log() ( msg="$1" file="$2" nextpart "$file" | grep -F -e "$msg" > /dev/null ) # _search_log_peek: look for message $1 in file $2 with nextpartpeek(). _search_log_peek() ( msg="$1" file="$2" nextpartpeek "$file" | grep -F -e "$msg" > /dev/null ) # wait_for_log: wait until message $2 in file $3 appears. Bail out after # $1 seconds. This needs to be used in conjunction with a prior call to # nextpart() or nextpartreset() on the same file to guarantee the offset is # set correctly. Tests using wait_for_log() are responsible for cleaning up # the created .prev files. wait_for_log() ( timeout="$1" msg="$2" file="$3" retry_quiet "$timeout" _search_log "$msg" "$file" && return 0 echo_i "exceeded time limit waiting for '$msg' in $file" return 1 ) # wait_for_log_peek: similar to wait_for_log() but peeking, so the file offset # does not change. wait_for_log_peek() ( timeout="$1" msg="$2" file="$3" retry_quiet "$timeout" _search_log_peek "$msg" "$file" && return 0 echo_i "exceeded time limit waiting for '$msg' in $file" return 1 ) # _retry: keep running a command until it succeeds, up to $1 times, with # one-second intervals, optionally printing a message upon every attempt _retry() { __retries="${1}" shift while :; do if "$@"; then return 0 fi __retries=$((__retries-1)) if [ "${__retries}" -gt 0 ]; then if [ "${__retry_quiet}" -ne 1 ]; then echo_i "retrying" fi sleep 1 else return 1 fi done } # retry: call _retry() in verbose mode retry() { __retry_quiet=0 _retry "$@" } # retry_quiet: call _retry() in silent mode retry_quiet() { __retry_quiet=1 _retry "$@" } # _repeat: keep running command up to $1 times, unless it fails _repeat() ( __retries="${1}" shift while :; do if ! "$@"; then return 1 fi __retries=$((__retries-1)) if [ "${__retries}" -le 0 ]; then break fi done return 0 ) rndc_reload() { $RNDC -c ../common/rndc.conf -s $2 -p ${CONTROLPORT} reload $3 2>&1 | sed 's/^/'"I:$SYSTESTDIR:$1"' /' # reloading single zone is synchronous, if we're reloading whole server # we need to wait for reload to finish if [ -z "$3" ]; then for __try in 0 1 2 3 4 5 6 7 8 9; do $RNDC -c ../common/rndc.conf -s $2 -p ${CONTROLPORT} status | grep "reload/reconfig in progress" > /dev/null || break sleep 1 done fi } rndc_reconfig() { $RNDC -c ../common/rndc.conf -s $2 -p ${CONTROLPORT} reconfig 2>&1 | sed 's/^/'"I:$SYSTESTDIR:$1"' /' for __try in 0 1 2 3 4 5 6 7 8 9; do $RNDC -c ../common/rndc.conf -s $2 -p ${CONTROLPORT} status | grep "reload/reconfig in progress" > /dev/null || break sleep 1 done } # rndc_dumpdb: call "rndc dumpdb [...]" and wait until it completes # # The first argument is the name server instance to send the command to, in the # form of "nsX" (where "X" is the instance number), e.g. "ns5". The remaining # arguments, if any, are appended to the rndc command line after "dumpdb". # # Control channel configuration for the name server instance to send the # command to must match the contents of bin/tests/system/common/rndc.conf. # # rndc output is stored in a file called rndc.out.test${n}; the "n" variable is # required to be set by the calling tests.sh script. # # Return 0 if the dump completes successfully; return 1 if rndc returns an exit # code other than 0 or if the "; Dump complete" string does not appear in the # dump within 10 seconds. rndc_dumpdb() { __ret=0 __dump_complete=0 __server="${1}" __ip="10.53.0.$(echo "${__server}" | tr -c -d "0-9")" shift ${RNDC} -c ../common/rndc.conf -p "${CONTROLPORT}" -s "${__ip}" dumpdb "$@" > "rndc.out.test${n}" 2>&1 || __ret=1 for _ in 0 1 2 3 4 5 6 7 8 9 do if grep '^; Dump complete$' "${__server}/named_dump.db" > /dev/null; then mv "${__server}/named_dump.db" "${__server}/named_dump.db.test${n}" __dump_complete=1 break fi sleep 1 done if [ ${__dump_complete} -eq 0 ]; then echo_i "timed out waiting for 'rndc dumpdb' to finish" __ret=1 fi return ${__ret} } # get_dig_xfer_stats: extract transfer statistics from dig output stored # in $1, converting them to a format used by some system tests. get_dig_xfer_stats() { LOGFILE="$1" sed -n "s/^;; XFR size: .*messages \([0-9][0-9]*\).*/messages=\1/p" "${LOGFILE}" sed -n "s/^;; XFR size: \([0-9][0-9]*\) records.*/records=\1/p" "${LOGFILE}" sed -n "s/^;; XFR size: .*bytes \([0-9][0-9]*\).*/bytes=\1/p" "${LOGFILE}" } # get_named_xfer_stats: from named log file $1, extract transfer # statistics for the last transfer for peer $2 and zone $3 (from a log # message which has to contain the string provided in $4), converting # them to a format used by some system tests. get_named_xfer_stats() { LOGFILE="$1" PEER="`echo $2 | sed 's/\./\\\\./g'`" ZONE="`echo $3 | sed 's/\./\\\\./g'`" MESSAGE="$4" grep " ${PEER}#.*${MESSAGE}:" "${LOGFILE}" | \ sed -n "s/.* '${ZONE}\/.* \([0-9][0-9]*\) messages.*/messages=\1/p" | tail -1 grep " ${PEER}#.*${MESSAGE}:" "${LOGFILE}" | \ sed -n "s/.* '${ZONE}\/.* \([0-9][0-9]*\) records.*/records=\1/p" | tail -1 grep " ${PEER}#.*${MESSAGE}:" "${LOGFILE}" | \ sed -n "s/.* '${ZONE}\/.* \([0-9][0-9]*\) bytes.*/bytes=\1/p" | tail -1 } # copy_setports - Copy Configuration File and Replace Ports # # Convenience function to copy a configuration file, replacing the tokens # QUERYPORT, CONTROLPORT and EXTRAPORT[1-8] with the values of the equivalent # environment variables. (These values are set by "run.sh", which calls the # scripts invoking this function.) # # Usage: # copy_setports infile outfile # copy_setports() { # The indirect method of handling the substitution of the PORT variables # (defining "atsign" then substituting for it in the "sed" statement) is # required to prevent the "Configure" script (in the win32utils/ directory) # from replacing the PORT substitution tokens when it processes # this file and produces conf.sh. atsign="@" sed -e "s/${atsign}PORT${atsign}/${PORT}/g" \ -e "s/${atsign}EXTRAPORT1${atsign}/${EXTRAPORT1}/g" \ -e "s/${atsign}EXTRAPORT2${atsign}/${EXTRAPORT2}/g" \ -e "s/${atsign}EXTRAPORT3${atsign}/${EXTRAPORT3}/g" \ -e "s/${atsign}EXTRAPORT4${atsign}/${EXTRAPORT4}/g" \ -e "s/${atsign}EXTRAPORT5${atsign}/${EXTRAPORT5}/g" \ -e "s/${atsign}EXTRAPORT6${atsign}/${EXTRAPORT6}/g" \ -e "s/${atsign}EXTRAPORT7${atsign}/${EXTRAPORT7}/g" \ -e "s/${atsign}EXTRAPORT8${atsign}/${EXTRAPORT8}/g" \ -e "s/${atsign}CONTROLPORT${atsign}/${CONTROLPORT}/g" \ -e "s/${atsign}DEFAULT_ALGORITHM${atsign}/${DEFAULT_ALGORITHM}/g" \ -e "s/${atsign}DEFAULT_ALGORITHM_NUMBER${atsign}/${DEFAULT_ALGORITHM_NUMBER}/g" \ -e "s/${atsign}DEFAULT_BITS${atsign}/${DEFAULT_BITS}/g" \ -e "s/${atsign}ALTERNATIVE_ALGORITHM${atsign}/${ALTERNATIVE_ALGORITHM}/g" \ -e "s/${atsign}ALTERNATIVE_ALGORITHM_NUMBER${atsign}/${ALTERNATIVE_ALGORITHM_NUMBER}/g" \ -e "s/${atsign}ALTERNATIVE_BITS${atsign}/${ALTERNATIVE_BITS}/g" \ -e "s/${atsign}DEFAULT_HMAC${atsign}/${DEFAULT_HMAC}/g" \ -e "s/${atsign}DISABLED_ALGORITHM${atsign}/${DISABLED_ALGORITHM}/g" \ -e "s/${atsign}DISABLED_ALGORITHM_NUMBER${atsign}/${DISABLED_ALGORITHM_NUMBER}/g" \ -e "s/${atsign}DISABLED_BITS${atsign}/${DISABLED_BITS}/g" \ $1 > $2 }