#!/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 \
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 <file>.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 <at>PORT<at> 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
}