test: resolve: add integration tests for browsing services

Co-authored-by: Frantisek Sumsal <frantisek@sumsal.cz>
Co-authored-by: Vishwanath Chandapur <vishwanath.chandapur@philips.com>
This commit is contained in:
Vishal Chillara Srinivas
2025-07-17 19:41:21 +05:30
parent 8458b7fb91
commit 7eb276dced
4 changed files with 270 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
integration_tests += [
integration_test_template + {
'name' : 'TEST-89-RESOLVED-MDNS',
},
]

View File

@@ -101,6 +101,7 @@ foreach dirname : [
'TEST-86-MULTI-PROFILE-UKI',
'TEST-87-AUX-UTILS-VM',
'TEST-88-UPGRADE',
'TEST-89-RESOLVED-MDNS',
]
subdir(dirname)
endforeach

View File

@@ -0,0 +1,8 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=TEST-89-RESOLVED-MDNS
[Service]
ExecStartPre=rm -f /failed /testok
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
Type=oneshot

View File

@@ -0,0 +1,254 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# shellcheck source=test/units/test-control.sh
. "$(dirname "$0")"/test-control.sh
SERVICE_TYPE_COUNT=10
SERVICE_COUNT=20
CONTAINER_ZONE="test-$RANDOM"
CONTAINER_1="test-mdns-1"
CONTAINER_2="test-mdns-2"
# Prepare containers
create_container() {
local container="${1:?}"
local stype sid svc
# Prepare container's /etc
#
# Since we also need the various test suite related dropins from the host's /etc,
# we'll overlay our customizations on top of that
mkdir -p "/var/lib/machines/$container/etc/systemd/dnssd"
# Create 20 test services for each service type (_testServiceX._udp) and number them sequentially,
# i.e. create services 0-19 for _testService0._udp, services 20-39 for _testService1._udp, and so on
for stype in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
for sid in $(seq 0 $((SERVICE_COUNT - 1))); do
svc=$((stype * SERVICE_COUNT + sid))
cat >"/var/lib/machines/$container/etc/systemd/dnssd/test-service-$container-$svc.dnssd" <<EOF
[Service]
Name=Test Service $svc on %H
Type=_testService$stype._udp
Port=98010
TxtText=DC=Device PN=123456 SN=1234567890
EOF
done
done
# To make things fast, spawn the container with a transient version of what's currently the host's
# rootfs, with a couple of tweaks to make the container unique enough
mkdir -p "/run/systemd/system/systemd-nspawn@$container.service.d"
cat >"/run/systemd/system/systemd-nspawn@$container.service.d/override.conf" <<EOF
[Service]
ExecStart=
ExecStart=systemd-nspawn --quiet --link-journal=try-guest --keep-unit --machine=%i --boot \
--volatile=yes --directory=/ \
--inaccessible=/etc/machine-id \
--inaccessible=/etc/hostname \
--resolv-conf=replace-stub \
--network-zone=$CONTAINER_ZONE \
--overlay=/etc:/var/lib/machines/$container/etc::/etc \
--hostname=$container
EOF
}
check_both() {
local service_id="${1:?}"
local result_file="${2:?}"
# We should get 20 services per container, 40 total
if [[ "$(wc -l <"$result_file")" -ge 40 ]]; then
# Check if the services we got are the correct ones
for i in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
svc=$((service_id * SERVICE_COUNT + i))
if ! grep "Test Service $svc on $CONTAINER_1" "$result_file" ||
! grep "Test Service $svc on $CONTAINER_2" "$result_file"; then
return 1
fi
done
# We got all records and all of them are what we expect
return 0
fi
return 1
}
check_first() {
local service_id="${1:?}"
local result_file="${2:?}"
# We should get 20 services per container
if [[ "$(wc -l <"$result_file")" -ge 20 ]]; then
# Check if the services we got are the correct ones
for i in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
svc=$((service_id * SERVICE_COUNT + i))
if ! grep "Test Service $svc on $CONTAINER_1" "$result_file"; then
return 1
fi
# This check assumes the second container is unreachable, so this shouldn't happen
if grep "Test Service $svc on $CONTAINER_2" "$result_file"; then
echo >&2 "Found a record from an unreachable container"
cat "$result_file"
exit 1
fi
done
# We got all records and all of them are what we expect
return 0
fi
return 1
}
run_and_check_services() {
local service_id="${1:?}"
local check_func="${2:?}"
local unit_name="varlinkctl-$service_id-$SRANDOM.service"
local i out_file parameters service_type svc tmp_file
out_file="$(mktemp)"
error_file="$(mktemp)"
tmp_file="$(mktemp)"
service_type="_testService$service_id._udp"
parameters="{ \"domain\": \"$service_type.local\", \"type\": \"\", \"ifindex\": ${BRIDGE_INDEX:?}, \"flags\": 16785432 }"
systemd-run --unit="$unit_name" --service-type=exec -p StandardOutput="file:$out_file" -p StandardError="file:$error_file" \
varlinkctl call --more /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.BrowseServices "$parameters"
# shellcheck disable=SC2064
# Note: unregister the trap once it's fired, otherwise it'll get propagated to functions that call this
# one, *sigh*
trap "trap - RETURN; systemctl stop $unit_name" RETURN
for _ in {0..14}; do
# The response format, for reference (it's JSON-SEQ):
#
# {
# "browser_service_data": [
# {
# "updateFlag": true,
# "family": 10,
# "name": "Test Service 13 on test-mdns-1",
# "type": "_testService0._udp",
# "domain": "local",
# "interface": 3
# },
# ...
# ]
# }
if [[ -s "$out_file" ]]; then
# Extract the service name from each valid record...
# jq --slurp --raw-output \
# ".[].browser_service_data[] | select(.updateFlag == true and .type == \"$service_type\" and .family == 10).name" "$out_file" | sort | tee "$tmp_file"
grep -o '"name":"[^"]*"' "$out_file" | sed 's/"name":"//;s/"//g' | sort | tee "$tmp_file"
# ...and compare them with what we expect
if "$check_func" "$service_id" "$tmp_file"; then
return 0
fi
fi
sleep 2
done
cat "$out_file"
cat "$error_file"
return 1
}
testcase_all_sequential() {
: "Test each service type (sequentially)"
resolvectl flush-caches
for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
run_and_check_services "$id" check_both
done
echo testcase_end
}
testcase_all_parallel() {
: "Test each service type (in parallel)"
resolvectl flush-caches
for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
run_and_check_services "$id" check_both &
done
wait
}
testcase_single_service_multiple_times() {
: "Test one service type multiple times"
resolvectl flush-caches
for _ in {0..4}; do
run_and_check_services 4 check_both
done
}
testcase_second_unreachable() {
: "Test each service type while the second container is unreachable"
systemd-run -M "$CONTAINER_2" --wait --pipe -- networkctl down host0
resolvectl flush-caches
for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
run_and_check_services "$id" check_first
done
: "Test each service type after bringing the second container back up again"
systemd-run -M "$CONTAINER_2" --wait --pipe -- networkctl up host0
systemd-run -M "$CONTAINER_2" --wait --pipe -- \
/usr/lib/systemd/systemd-networkd-wait-online --ipv4 --ipv6 --interface=host0 --operational-state=degraded --timeout=30
for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
run_and_check_services "$id" check_both
done
}
: "Setup host & containers"
# Note: create the drop-in intentionally under /run/ and copy it manually into the containers
mkdir -p /run/systemd/resolved.conf.d/
cat >/run/systemd/resolved.conf.d/99-mdns-llmnr.conf <<EOF
[Resolve]
MulticastDNS=yes
LLMNR=yes
EOF
systemctl unmask systemd-resolved.service systemd-networkd.{service,socket} systemd-machined.service
systemctl enable --now systemd-resolved.service systemd-networkd.{socket,service} systemd-machined.service
systemctl reload systemd-resolved.service systemd-networkd.service
for container in "$CONTAINER_1" "$CONTAINER_2"; do
create_container "$container"
mkdir -p "/var/lib/machines/$container/etc/systemd/resolved.conf.d/"
cp /run/systemd/resolved.conf.d/99-mdns-llmnr.conf "/var/lib/machines/$container/etc/systemd/resolved.conf.d/"
touch "/var/lib/machines/$container/etc/hostname"
systemctl daemon-reload
machinectl start "$container"
# Wait for the system bus to start...
timeout 30s bash -xec "while ! systemd-run -M '$container' --wait --pipe true; do sleep 1; done"
# ...and from there wait for the machine bootup to finish. We don't really care if the container
# boots up in a degraded state, hence the `:`
timeout 30s systemd-run -M "$container" --wait --pipe -- systemctl --wait is-system-running || :
# Wait until the veth interface is configured and turn on mDNS and LLMNR
systemd-run -M "$container" --wait --pipe -- \
/usr/lib/systemd/systemd-networkd-wait-online --ipv4 --ipv6 --interface=host0 --operational-state=degraded --timeout=30
systemd-run -M "$container" --wait --pipe -- resolvectl mdns host0 yes
systemd-run -M "$container" --wait --pipe -- resolvectl llmnr host0 yes
systemd-run -M "$container" --wait --pipe -- networkctl status --no-pager
systemd-run -M "$container" --wait --pipe -- resolvectl status --no-pager
[[ "$(systemd-run -M "$container" --wait --pipe -- resolvectl mdns host0)" =~ :\ yes$ ]]
[[ "$(systemd-run -M "$container" --wait --pipe -- resolvectl llmnr host0)" =~ :\ yes$ ]]
done
BRIDGE_INDEX="$(<"/sys/class/net/vz-$CONTAINER_ZONE/ifindex")"
machinectl list
resolvectl mdns "vz-$CONTAINER_ZONE" on
resolvectl llmnr "vz-$CONTAINER_ZONE" on
networkctl status
resolvectl status
# Run the actual test cases (functions prefixed by testcase_)
run_testcases
touch /testok