Before opening new IPv4/IPv6 server sockets, chronyd will check for matching reusable sockets passed from the service manager (for example, passed via systemd socket activation: https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html) and use those instead. Aside from IPV6_V6ONLY (which cannot be set on already-bound sockets), the daemon sets the same socket options on reusable sockets as it would on sockets it opens itself. Unit tests test the correct parsing of the LISTEN_FDS environment variable. Add 011-systemd system test to test socket activation for DGRAM and STREAM sockets (both IPv4 and IPv6). The tests use the systemd-socket-activate test tool, which has some limitations requiring workarounds discussed in inline comments.
375 lines
8.7 KiB
Text
375 lines
8.7 KiB
Text
# Copyright (C) Miroslav Lichvar 2009
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of version 2 of the GNU General Public License as
|
|
# published by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along
|
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
export LC_ALL=C
|
|
export PATH=${CHRONY_PATH:-../..}:$PATH
|
|
|
|
TEST_DIR=${TEST_DIR:-$(pwd)/tmp}
|
|
TEST_LIBDIR=${TEST_LIBDIR:-$TEST_DIR}
|
|
TEST_LOGDIR=${TEST_LOGDIR:-$TEST_DIR}
|
|
TEST_RUNDIR=${TEST_RUNDIR:-$TEST_DIR}
|
|
TEST_SCFILTER=${TEST_SCFILTER:-0}
|
|
|
|
test_start() {
|
|
check_chronyd_features NTP CMDMON || test_skip "NTP/CMDMON support disabled"
|
|
|
|
[ "${#TEST_DIR}" -ge 5 ] || test_skip "invalid TEST_DIR"
|
|
|
|
rm -rf "$TEST_DIR"
|
|
mkdir -p "$TEST_DIR" && chmod 700 "$TEST_DIR" || test_skip "could not create $TEST_DIR"
|
|
|
|
[ -d "$TEST_LIBDIR" ] || test_skip "missing $TEST_LIBDIR"
|
|
[ -d "$TEST_LOGDIR" ] || test_skip "missing $TEST_LOGDIR"
|
|
[ -d "$TEST_RUNDIR" ] || test_skip "missing $TEST_RUNDIR"
|
|
|
|
rm -f "$TEST_LIBDIR"/* "$TEST_LOGDIR"/* "$TEST_RUNDIR"/*
|
|
|
|
if [ "$user" != "root" ]; then
|
|
id -u "$user" > /dev/null 2> /dev/null || test_skip "missing user $user"
|
|
chown "$user:$(id -g "$user")" "$TEST_DIR" || test_skip "could not chown $TEST_DIR"
|
|
su "$user" -s /bin/sh -c "touch $TEST_DIR/test" 2> /dev/null || \
|
|
test_skip "$user cannot access $TEST_DIR"
|
|
rm "$TEST_DIR/test"
|
|
else
|
|
chown 0:0 "$TEST_DIR" || test_skip "could not chown $TEST_DIR"
|
|
fi
|
|
|
|
echo "Testing $*:"
|
|
}
|
|
|
|
test_pass() {
|
|
echo "PASS"
|
|
exit 0
|
|
}
|
|
|
|
test_fail() {
|
|
echo "FAIL"
|
|
exit 1
|
|
}
|
|
|
|
test_skip() {
|
|
local msg=$1
|
|
|
|
[ -n "$msg" ] && echo "SKIP ($msg)" || echo "SKIP"
|
|
exit 9
|
|
}
|
|
|
|
test_ok() {
|
|
pad_line
|
|
echo -e "\tOK"
|
|
return 0
|
|
}
|
|
|
|
test_bad() {
|
|
pad_line
|
|
echo -e "\tBAD"
|
|
return 1
|
|
}
|
|
|
|
test_error() {
|
|
pad_line
|
|
echo -e "\tERROR"
|
|
return 1
|
|
}
|
|
|
|
chronyd=$(command -v chronyd)
|
|
chronyc=$(command -v chronyc)
|
|
|
|
[ $EUID -eq 0 ] || test_skip "not root"
|
|
|
|
[ -x "$chronyd" ] || test_skip "chronyd not found"
|
|
[ -x "$chronyc" ] || test_skip "chronyc not found"
|
|
|
|
if netstat -aln > /dev/null 2> /dev/null; then
|
|
port_list_command="netstat -aln"
|
|
elif ss -atun > /dev/null 2> /dev/null; then
|
|
port_list_command="ss -atun"
|
|
else
|
|
test_skip "missing netstat or ss"
|
|
fi
|
|
|
|
# Default test testings
|
|
default_minimal_config=0
|
|
default_extra_chronyd_directives=""
|
|
default_extra_chronyd_options=""
|
|
default_clock_control=0
|
|
default_server=127.0.0.1
|
|
default_server_name=127.0.0.1
|
|
default_server_options=""
|
|
default_user=root
|
|
|
|
# Initialize test settings from their defaults
|
|
for defoptname in ${!default_*}; do
|
|
optname=${defoptname#default_}
|
|
[ -z "${!optname}" ] && declare "$optname"="${!defoptname}"
|
|
done
|
|
|
|
msg_length=0
|
|
pad_line() {
|
|
local line_length=56
|
|
[ $msg_length -lt $line_length ] && \
|
|
printf "%$((line_length - msg_length))s" ""
|
|
msg_length=0
|
|
}
|
|
|
|
# Print aligned message
|
|
test_message() {
|
|
local level=$1 eol=$2
|
|
shift 2
|
|
local msg="$*"
|
|
|
|
while [ "$level" -gt 0 ]; do
|
|
echo -n " "
|
|
level=$((level - 1))
|
|
msg_length=$((msg_length + 2))
|
|
done
|
|
echo -n "$msg"
|
|
|
|
msg_length=$((msg_length + ${#msg}))
|
|
if [ "$eol" -ne 0 ]; then
|
|
echo
|
|
msg_length=0
|
|
fi
|
|
}
|
|
|
|
# Check if chronyd has specified features
|
|
check_chronyd_features() {
|
|
local feature features
|
|
|
|
features=$($chronyd -v | sed 's/.*(\(.*\)).*/\1/')
|
|
|
|
for feature; do
|
|
echo "$features" | grep -q "+$feature" || return 1
|
|
done
|
|
}
|
|
|
|
# Print test settings which differ from default value
|
|
print_nondefaults() {
|
|
local defoptname optname
|
|
|
|
test_message 1 1 "non-default settings:"
|
|
for defoptname in ${!default_*}; do
|
|
optname=${defoptname#default_}
|
|
[ "${!defoptname}" = "${!optname}" ] || \
|
|
test_message 2 1 "$optname"=${!optname}
|
|
done
|
|
}
|
|
|
|
get_conffile() {
|
|
echo "$TEST_DIR/chronyd.conf"
|
|
}
|
|
|
|
get_pidfile() {
|
|
echo "$TEST_RUNDIR/chronyd.pid"
|
|
}
|
|
|
|
get_logfile() {
|
|
echo "$TEST_LOGDIR/chronyd.log"
|
|
}
|
|
|
|
get_cmdsocket() {
|
|
echo "$TEST_RUNDIR/chronyd.sock"
|
|
}
|
|
|
|
# Find a free port in the 10000-20000 range (their use is racy)
|
|
get_free_port() {
|
|
local port
|
|
|
|
while true; do
|
|
port=$((RANDOM % 10000 + 10000))
|
|
$port_list_command | grep -q '^\(tcp\|udp\).*[:.]'"$port " && continue
|
|
break
|
|
done
|
|
|
|
echo $port
|
|
}
|
|
|
|
generate_chrony_conf() {
|
|
local ntpport cmdport
|
|
|
|
ntpport=$(get_free_port)
|
|
cmdport=$(get_free_port)
|
|
|
|
echo "0.0 10000" > "$TEST_LIBDIR/driftfile"
|
|
echo "1 MD5 abcdefghijklmnopq" > "$TEST_DIR/keys"
|
|
chown "$user:$(id -g "$user")" "$TEST_LIBDIR/driftfile" "$TEST_DIR/keys"
|
|
echo "0.0" > "$TEST_DIR/tempcomp"
|
|
|
|
(
|
|
echo "pidfile $(get_pidfile)"
|
|
echo "bindcmdaddress $(get_cmdsocket)"
|
|
echo "port $ntpport"
|
|
echo "cmdport $cmdport"
|
|
|
|
echo "$extra_chronyd_directives"
|
|
|
|
[ "$minimal_config" -ne 0 ] && exit 0
|
|
|
|
echo "allow"
|
|
echo "cmdallow"
|
|
echo "local"
|
|
|
|
echo "server $server_name port $ntpport minpoll -6 maxpoll -6 $server_options"
|
|
|
|
[ "$server" = "127.0.0.1" ] && echo "bindacqaddress $server"
|
|
echo "bindaddress 127.0.0.1"
|
|
echo "bindcmdaddress 127.0.0.1"
|
|
echo "dumpdir $TEST_RUNDIR"
|
|
echo "logdir $TEST_LOGDIR"
|
|
echo "log tempcomp rawmeasurements refclocks statistics tracking rtc"
|
|
echo "logbanner 0"
|
|
echo "smoothtime 100.0 0.001"
|
|
echo "leapsectz right/UTC"
|
|
echo "dscp 46"
|
|
|
|
echo "include /dev/null"
|
|
echo "keyfile $TEST_DIR/keys"
|
|
echo "driftfile $TEST_LIBDIR/driftfile"
|
|
echo "tempcomp $TEST_DIR/tempcomp 0.1 0 0 0 0"
|
|
|
|
) > "$(get_conffile)"
|
|
}
|
|
|
|
get_chronyd_options() {
|
|
[ "$clock_control" -eq 0 ] && echo "-x"
|
|
echo "-l $(get_logfile)"
|
|
echo "-f $(get_conffile)"
|
|
echo "-u $user"
|
|
echo "-F $TEST_SCFILTER"
|
|
echo "$extra_chronyd_options"
|
|
}
|
|
|
|
# Start a chronyd instance
|
|
start_chronyd() {
|
|
local pid pidfile=$(get_pidfile)
|
|
|
|
print_nondefaults
|
|
test_message 1 0 "starting chronyd"
|
|
|
|
generate_chrony_conf
|
|
|
|
trap stop_chronyd EXIT
|
|
|
|
rm -f "$TEST_LOGDIR"/*.log
|
|
|
|
$CHRONYD_WRAPPER "$chronyd" $(get_chronyd_options) > "$TEST_DIR/chronyd.out" 2>&1
|
|
|
|
[ $? -eq 0 ] && [ -f "$pidfile" ] && ps -p "$(cat "$pidfile")" > /dev/null && test_ok || test_error
|
|
}
|
|
|
|
wait_for_sync() {
|
|
local prev_length
|
|
|
|
test_message 1 0 "waiting for synchronization"
|
|
prev_length=$msg_length
|
|
|
|
for i in $(seq 1 10); do
|
|
run_chronyc "ntpdata $server" > /dev/null 2>&1 || break
|
|
if check_chronyc_output "Total RX +: [1-9]" > /dev/null 2>&1; then
|
|
msg_length=$prev_length
|
|
test_ok
|
|
return
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
msg_length=$prev_length
|
|
test_error
|
|
}
|
|
|
|
# Stop the chronyd instance
|
|
stop_chronyd() {
|
|
local pid pidfile
|
|
|
|
pidfile=$(get_pidfile)
|
|
[ -f "$pidfile" ] || return 0
|
|
|
|
pid=$(cat "$pidfile")
|
|
|
|
test_message 1 0 "stopping chronyd"
|
|
|
|
if ! kill "$pid" 2> /dev/null; then
|
|
test_error
|
|
return
|
|
fi
|
|
|
|
# Wait for the process to terminate (we cannot use "wait")
|
|
while ps -p "$pid" > /dev/null; do
|
|
sleep 0.1
|
|
done
|
|
|
|
test_ok
|
|
}
|
|
|
|
# Check chronyd log for expected and unexpected messages
|
|
check_chronyd_messages() {
|
|
local logfile=$(get_logfile)
|
|
|
|
test_message 1 0 "checking chronyd messages"
|
|
|
|
grep -q 'chronyd exiting' "$logfile" && \
|
|
([ "$clock_control" -eq 0 ] || ! grep -q 'Disabled control of system clock' "$logfile") && \
|
|
([ "$clock_control" -ne 0 ] || grep -q 'Disabled control of system clock' "$logfile") && \
|
|
([ "$minimal_config" -ne 0 ] || grep -q 'Frequency .* read from' "$logfile") && \
|
|
grep -q 'chronyd exiting' "$logfile" && \
|
|
! (grep -v '^.\{19\}Z D:' "$logfile" | grep -q 'Could not') && \
|
|
! grep -q 'Disabled command socket' "$logfile" && \
|
|
test_ok || test_bad
|
|
}
|
|
|
|
# Check the number of messages matching a pattern in a specified file
|
|
check_chronyd_message_count() {
|
|
local count pattern=$1 min=$2 max=$3 logfile=$(get_logfile)
|
|
|
|
test_message 1 0 "checking message \"$pattern\""
|
|
|
|
count=$(grep "$pattern" "$(get_logfile)" | wc -l)
|
|
|
|
[ "$min" -le "$count" ] && [ "$count" -le "$max" ] && test_ok || test_bad
|
|
}
|
|
|
|
# Check the logs and dump file for measurements and a clock update
|
|
check_chronyd_files() {
|
|
test_message 1 0 "checking chronyd files"
|
|
|
|
grep -q " $server .* 111 111 1110 " "$TEST_LOGDIR/measurements.log" && \
|
|
[ -f "$TEST_LOGDIR/tempcomp.log" ] && [ "$(wc -l < "$TEST_LOGDIR/tempcomp.log")" -ge 2 ] && \
|
|
test_ok || test_bad
|
|
}
|
|
|
|
# Run a chronyc command
|
|
run_chronyc() {
|
|
local host=$chronyc_host options="-n -m"
|
|
|
|
test_message 1 0 "running chronyc $([ -n "$host" ] && echo "@$host ")$*"
|
|
|
|
if [ -z "$host" ]; then
|
|
host="$(get_cmdsocket)"
|
|
else
|
|
options="$options -p $(grep cmdport "$(get_conffile)" | awk '{print $2}')"
|
|
fi
|
|
|
|
$CHRONYC_WRAPPER "$chronyc" -h "$host" $options "$@" > "$TEST_DIR/chronyc.out" && \
|
|
test_ok || test_error
|
|
}
|
|
|
|
# Compare chronyc output with specified pattern
|
|
check_chronyc_output() {
|
|
local pattern=$1
|
|
|
|
test_message 1 0 "checking chronyc output"
|
|
|
|
[[ "$(cat "$TEST_DIR/chronyc.out")" =~ $pattern ]] && test_ok || test_bad
|
|
}
|