]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
test: resolve: add integration tests for browsing services 22532/head
authorVishal Chillara Srinivas <vishal.chillarasrinivas@philips.com>
Thu, 17 Jul 2025 14:11:21 +0000 (19:41 +0530)
committerVishal Chillara Srinivas <vishal.chillarasrinivas@philips.com>
Thu, 17 Jul 2025 14:11:22 +0000 (19:41 +0530)
Co-authored-by: Frantisek Sumsal <frantisek@sumsal.cz>
Co-authored-by: Vishwanath Chandapur <vishwanath.chandapur@philips.com>
test/integration-tests/TEST-89-RESOLVED-MDNS/meson.build [new file with mode: 0644]
test/integration-tests/meson.build
test/units/TEST-89-RESOLVED-MDNS.service [new file with mode: 0644]
test/units/TEST-89-RESOLVED-MDNS.sh [new file with mode: 0755]

diff --git a/test/integration-tests/TEST-89-RESOLVED-MDNS/meson.build b/test/integration-tests/TEST-89-RESOLVED-MDNS/meson.build
new file mode 100644 (file)
index 0000000..9cca5c7
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+integration_tests += [
+        integration_test_template + {
+                'name' : 'TEST-89-RESOLVED-MDNS',
+        },
+]
index 5fb92b172afe949d004fa6b00fccc340175ba244..19cd5b5310296c0e3ee0b3390a18ff384f283bef 100644 (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
diff --git a/test/units/TEST-89-RESOLVED-MDNS.service b/test/units/TEST-89-RESOLVED-MDNS.service
new file mode 100644 (file)
index 0000000..e7ed92c
--- /dev/null
@@ -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
diff --git a/test/units/TEST-89-RESOLVED-MDNS.sh b/test/units/TEST-89-RESOLVED-MDNS.sh
new file mode 100755 (executable)
index 0000000..b6e2005
--- /dev/null
@@ -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