]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
test: split the resolved test suite into separate test cases
authorDaniel Rusek <drusek@redhat.com>
Thu, 6 Jun 2024 21:44:38 +0000 (23:44 +0200)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 2 Jul 2024 08:08:53 +0000 (10:08 +0200)
Although being far from ideal and the first two test cases have to be run
before the setup phase otherwise they will fail, it still makes the test
suite look much better and easier to read

test/units/TEST-75-RESOLVED.sh

index a4417ce57510280a27ca3400388c1b69573e020c..4f387203e2eda5ecea7fec5bfdc9ff8dde742049 100755 (executable)
@@ -11,6 +11,8 @@
 set -eux
 set -o pipefail
 
+# shellcheck source=test/units/test-control.sh
+. "$(dirname "$0")"/test-control.sh
 # shellcheck source=test/units/util.sh
 . "$(dirname "$0")"/util.sh
 
@@ -65,143 +67,25 @@ restart_resolved() {
     systemctl service-log-level systemd-resolved.service debug
 }
 
-# Test for resolvectl, resolvconf
-systemctl unmask systemd-resolved.service
-systemctl enable --now systemd-resolved.service
-systemctl service-log-level systemd-resolved.service debug
-ip link add hoge type dummy
-ip link add hoge.foo type dummy
-resolvectl dns hoge 10.0.0.1 10.0.0.2
-resolvectl dns hoge.foo 10.0.0.3 10.0.0.4
-assert_in '10.0.0.1 10.0.0.2' "$(resolvectl dns hoge)"
-assert_in '10.0.0.3 10.0.0.4' "$(resolvectl dns hoge.foo)"
-resolvectl dns hoge 10.0.1.1 10.0.1.2
-resolvectl dns hoge.foo 10.0.1.3 10.0.1.4
-assert_in '10.0.1.1 10.0.1.2' "$(resolvectl dns hoge)"
-assert_in '10.0.1.3 10.0.1.4' "$(resolvectl dns hoge.foo)"
-if ! RESOLVCONF=$(command -v resolvconf 2>/dev/null); then
-    TMPDIR=$(mktemp -d -p /tmp resolvconf-tests.XXXXXX)
-    RESOLVCONF="$TMPDIR"/resolvconf
-    ln -s "$(command -v resolvectl 2>/dev/null)" "$RESOLVCONF"
-fi
-echo nameserver 10.0.2.1 10.0.2.2 | "$RESOLVCONF" -a hoge
-echo nameserver 10.0.2.3 10.0.2.4 | "$RESOLVCONF" -a hoge.foo
-assert_in '10.0.2.1 10.0.2.2' "$(resolvectl dns hoge)"
-assert_in '10.0.2.3 10.0.2.4' "$(resolvectl dns hoge.foo)"
-echo nameserver 10.0.3.1 10.0.3.2 | "$RESOLVCONF" -a hoge.inet.ipsec.192.168.35
-echo nameserver 10.0.3.3 10.0.3.4 | "$RESOLVCONF" -a hoge.foo.dhcp
-assert_in '10.0.3.1 10.0.3.2' "$(resolvectl dns hoge)"
-assert_in '10.0.3.3 10.0.3.4' "$(resolvectl dns hoge.foo)"
-
-# Tests for _localdnsstub and _localdnsproxy
-assert_in '127.0.0.53' "$(resolvectl query _localdnsstub)"
-assert_in '_localdnsstub' "$(resolvectl query 127.0.0.53)"
-assert_in '127.0.0.54' "$(resolvectl query _localdnsproxy)"
-assert_in '_localdnsproxy' "$(resolvectl query 127.0.0.54)"
-
-assert_in '127.0.0.53' "$(dig @127.0.0.53 _localdnsstub)"
-assert_in '_localdnsstub' "$(dig @127.0.0.53 -x 127.0.0.53)"
-assert_in '127.0.0.54' "$(dig @127.0.0.53 _localdnsproxy)"
-assert_in '_localdnsproxy' "$(dig @127.0.0.53 -x 127.0.0.54)"
+setup() {
+    : "SETUP BEGIN"
 
-# Tests for mDNS and LLMNR settings
-mkdir -p /run/systemd/resolved.conf.d
-{
-    echo "[Resolve]"
-    echo "MulticastDNS=no"
-    echo "LLMNR=no"
-} >/run/systemd/resolved.conf.d/mdns-llmnr.conf
-restart_resolved
-# make sure networkd is not running.
-systemctl stop systemd-networkd.service
-assert_in 'no' "$(resolvectl mdns hoge)"
-assert_in 'no' "$(resolvectl llmnr hoge)"
-# Tests that reloading works
-{
-    echo "[Resolve]"
-    echo "MulticastDNS=yes"
-    echo "LLMNR=yes"
-} >/run/systemd/resolved.conf.d/mdns-llmnr.conf
-systemctl reload systemd-resolved.service
-# defaults to yes (both the global and per-link settings are yes)
-assert_in 'yes' "$(resolvectl mdns hoge)"
-assert_in 'yes' "$(resolvectl llmnr hoge)"
-# set per-link setting
-resolvectl mdns hoge yes
-resolvectl llmnr hoge yes
-assert_in 'yes' "$(resolvectl mdns hoge)"
-assert_in 'yes' "$(resolvectl llmnr hoge)"
-resolvectl mdns hoge resolve
-resolvectl llmnr hoge resolve
-assert_in 'resolve' "$(resolvectl mdns hoge)"
-assert_in 'resolve' "$(resolvectl llmnr hoge)"
-resolvectl mdns hoge no
-resolvectl llmnr hoge no
-assert_in 'no' "$(resolvectl mdns hoge)"
-assert_in 'no' "$(resolvectl llmnr hoge)"
-# downgrade global setting to resolve
-{
-    echo "[Resolve]"
-    echo "MulticastDNS=resolve"
-    echo "LLMNR=resolve"
-} >/run/systemd/resolved.conf.d/mdns-llmnr.conf
-systemctl reload systemd-resolved.service
-# set per-link setting
-resolvectl mdns hoge yes
-resolvectl llmnr hoge yes
-assert_in 'resolve' "$(resolvectl mdns hoge)"
-assert_in 'resolve' "$(resolvectl llmnr hoge)"
-resolvectl mdns hoge resolve
-resolvectl llmnr hoge resolve
-assert_in 'resolve' "$(resolvectl mdns hoge)"
-assert_in 'resolve' "$(resolvectl llmnr hoge)"
-resolvectl mdns hoge no
-resolvectl llmnr hoge no
-assert_in 'no' "$(resolvectl mdns hoge)"
-assert_in 'no' "$(resolvectl llmnr hoge)"
-# downgrade global setting to no
-{
-    echo "[Resolve]"
-    echo "MulticastDNS=no"
-    echo "LLMNR=no"
-} >/run/systemd/resolved.conf.d/mdns-llmnr.conf
-systemctl reload systemd-resolved.service
-# set per-link setting
-resolvectl mdns hoge yes
-resolvectl llmnr hoge yes
-assert_in 'no' "$(resolvectl mdns hoge)"
-assert_in 'no' "$(resolvectl llmnr hoge)"
-resolvectl mdns hoge resolve
-resolvectl llmnr hoge resolve
-assert_in 'no' "$(resolvectl mdns hoge)"
-assert_in 'no' "$(resolvectl llmnr hoge)"
-resolvectl mdns hoge no
-resolvectl llmnr hoge no
-assert_in 'no' "$(resolvectl mdns hoge)"
-assert_in 'no' "$(resolvectl llmnr hoge)"
-
-# Cleanup
-rm -f /run/systemd/resolved.conf.d/mdns-llmnr.conf
-ip link del hoge
-ip link del hoge.foo
-
-### SETUP ###
-# Configure network
-hostnamectl hostname ns1.unsigned.test
-cat >>/etc/hosts <<EOF
+    : "Setup - Configure network"
+    hostnamectl hostname ns1.unsigned.test
+    cat >>/etc/hosts <<EOF
 10.0.0.1               ns1.unsigned.test
 fd00:dead:beef:cafe::1 ns1.unsigned.test
 
 127.128.0.5     localhost5 localhost5.localdomain localhost5.localdomain4 localhost.localdomain5 localhost5.localdomain5
 EOF
 
-mkdir -p /run/systemd/network
-cat >/run/systemd/network/10-dns0.netdev <<EOF
+    mkdir -p /run/systemd/network
+    cat >/run/systemd/network/10-dns0.netdev <<EOF
 [NetDev]
 Name=dns0
 Kind=dummy
 EOF
-cat >/run/systemd/network/10-dns0.network <<EOF
+    cat >/run/systemd/network/10-dns0.network <<EOF
 [Match]
 Name=dns0
 
@@ -213,12 +97,12 @@ DNSSEC=allow-downgrade
 DNS=10.0.0.1
 DNS=fd00:dead:beef:cafe::1
 EOF
-cat >/run/systemd/network/10-dns1.netdev <<EOF
+    cat >/run/systemd/network/10-dns1.netdev <<EOF
 [NetDev]
 Name=dns1
 Kind=dummy
 EOF
-cat >/run/systemd/network/10-dns1.network <<EOF
+    cat >/run/systemd/network/10-dns1.network <<EOF
 [Match]
 Name=dns1
 
@@ -227,497 +111,659 @@ IPv6AcceptRA=no
 Address=10.99.0.1/24
 DNSSEC=no
 EOF
-systemctl edit --stdin --full --runtime --force "resolved-dummy-server.service" <<EOF
+    systemctl edit --stdin --full --runtime --force "resolved-dummy-server.service" <<EOF
 [Service]
 Type=notify
 Environment=SYSTEMD_LOG_LEVEL=debug
 ExecStart=/usr/lib/systemd/tests/unit-tests/manual/test-resolved-dummy-server 10.99.0.1:53
 EOF
 
-DNS_ADDRESSES=(
-    "10.0.0.1"
-    "fd00:dead:beef:cafe::1"
-)
+    DNS_ADDRESSES=(
+        "10.0.0.1"
+        "fd00:dead:beef:cafe::1"
+    )
 
-mkdir -p /run/systemd/resolved.conf.d
-{
-    echo "[Resolve]"
-    echo "FallbackDNS="
-    echo "DNSSEC=allow-downgrade"
-    echo "DNSOverTLS=opportunistic"
-} >/run/systemd/resolved.conf.d/test.conf
-ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
-# Override the default NTA list, which turns off DNSSEC validation for (among
-# others) the test. domain
-mkdir -p "/etc/dnssec-trust-anchors.d/"
-echo local >/etc/dnssec-trust-anchors.d/local.negative
-
-# Copy over our knot configuration
-mkdir -p /var/lib/knot/zones/ /etc/knot/
-cp -rfv /usr/lib/systemd/tests/testdata/knot-data/zones/* /var/lib/knot/zones/
-cp -fv /usr/lib/systemd/tests/testdata/knot-data/knot.conf /etc/knot/knot.conf
-chgrp -R knot /etc/knot/ /var/lib/knot/
-chmod -R ug+rwX /var/lib/knot/
-chmod -R g+r /etc/knot/
-
-# Sign the root zone
-keymgr . generate algorithm=ECDSAP256SHA256 ksk=yes zsk=yes
-# Create a trust anchor for resolved with our root zone
-keymgr . ds | sed 's/ DS/ IN DS/g' >/etc/dnssec-trust-anchors.d/root.positive
-# Create a bind-compatible trust anchor (for delv)
-# Note: the trust-anchors directive is relatively new, so use the original
-#       managed-keys one until it's widespread enough
-{
-    echo 'managed-keys {'
-    keymgr . dnskey | sed -r 's/^\. DNSKEY ([0-9]+ [0-9]+ [0-9]+) (.+)$/. static-key \1 "\2";/g'
-    echo '};'
-} >/etc/bind.keys
-# Create an /etc/bind/bind.keys symlink, which is used by delv on Ubuntu
-mkdir -p /etc/bind
-ln -svf /etc/bind.keys /etc/bind/bind.keys
-
-# Start the services
-systemctl unmask systemd-networkd
-systemctl restart systemd-networkd
-/usr/lib/systemd/systemd-networkd-wait-online --interface=dns1:routable --timeout=60
-systemctl reload systemd-resolved
-systemctl start resolved-dummy-server
-
-# Create knot's runtime dir, since from certain version it's provided only by
-# the package and not created by tmpfiles/systemd
-if [[ ! -d /run/knot ]]; then
-    mkdir -p /run/knot
-    chown -R knot:knot /run/knot
-fi
-systemctl start knot
-# Wait a bit for the keys to propagate
-sleep 4
-
-systemctl status resolved-dummy-server
-networkctl status
-resolvectl status
-resolvectl log-level debug
-
-# Start monitoring queries
-systemd-run -u resolvectl-monitor.service -p Type=notify resolvectl monitor
-systemd-run -u resolvectl-monitor-json.service -p Type=notify resolvectl monitor --json=short
-
-# FIXME: knot, unfortunately, incorrectly complains about missing zone files for zones
-#        that are forwarded using the `dnsproxy` module. Until the issue is resolved,
-#        let's fall back to pre-processing the `zone-check` output a bit before checking it
-#
-# See: https://gitlab.nic.cz/knot/knot-dns/-/issues/913
-run knotc zone-check || :
-sed -i '/forwarded.test./d' "$RUN_OUT"
-[[ ! -s "$RUN_OUT" ]]
-# We need to manually propagate the DS records of onlinesign.test. to the parent
-# zone, since they're generated online
-knotc zone-begin test.
-if knotc zone-get test. onlinesign.test. ds | grep .; then
-    # Drop any old DS records, if present (e.g. on test re-run)
-    knotc zone-unset test. onlinesign.test. ds
-fi
-# Propagate the new DS records
-while read -ra line; do
-    knotc zone-set test. "${line[0]}" 600 "${line[@]:1}"
-done < <(keymgr onlinesign.test. ds)
-knotc zone-commit test.
-
-knotc reload
-sleep 2
-
-### SETUP END ###
-
-: "--- nss-resolve/nss-myhostname tests"
-# Sanity check
-TIMESTAMP=$(date '+%F %T')
-# Issue: https://github.com/systemd/systemd/issues/23951
-# With IPv6 enabled
-run getent -s resolve ahosts ns1.unsigned.test
-grep -qE "^fd00:dead:beef:cafe::1\s+STREAM\s+ns1\.unsigned\.test" "$RUN_OUT"
-monitor_check_rr "$TIMESTAMP" "ns1.unsigned.test IN AAAA fd00:dead:beef:cafe::1"
-# With IPv6 disabled
-# Issue: https://github.com/systemd/systemd/issues/23951
-disable_ipv6
-run getent -s resolve ahosts ns1.unsigned.test
-grep -qE "^10\.0\.0\.1\s+STREAM\s+ns1\.unsigned\.test" "$RUN_OUT"
-(! grep -qE "fd00:dead:beef:cafe::1" "$RUN_OUT")
-monitor_check_rr "$TIMESTAMP" "ns1.unsigned.test IN A 10.0.0.1"
-enable_ipv6
-
-# Issue: https://github.com/systemd/systemd/issues/18812
-# PR: https://github.com/systemd/systemd/pull/18896
-# Follow-up issue: https://github.com/systemd/systemd/issues/23152
-# Follow-up PR: https://github.com/systemd/systemd/pull/23161
-# With IPv6 enabled
-run getent -s resolve ahosts localhost
-grep -qE "^::1\s+STREAM\s+localhost" "$RUN_OUT"
-run getent -s myhostname ahosts localhost
-grep -qE "^::1\s+STREAM\s+localhost" "$RUN_OUT"
-# With IPv6 disabled
-disable_ipv6
-run getent -s resolve ahosts localhost
-grep -qE "^127\.0\.0\.1\s+STREAM\s+localhost" "$RUN_OUT"
-(! grep -qE "::1" "$RUN_OUT")
-run getent -s myhostname ahosts localhost
-grep -qE "^127\.0\.0\.1\s+STREAM\s+localhost" "$RUN_OUT"
-enable_ipv6
-
-# Issue: https://github.com/systemd/systemd/issues/25088
-run getent -s resolve hosts 127.128.0.5
-grep -qEx '127\.128\.0\.5\s+localhost5(\s+localhost5?\.localdomain[45]?){4}' "$RUN_OUT"
-[ "$(wc -l <"$RUN_OUT")" -eq 1 ]
-
-# Issue: https://github.com/systemd/systemd/issues/20158
-run dig +noall +answer +additional localhost5.
-grep -qEx 'localhost5\.\s+0\s+IN\s+A\s+127\.128\.0\.5' "$RUN_OUT"
-[ "$(wc -l <"$RUN_OUT")" -eq 1 ]
-run dig +noall +answer +additional localhost5.localdomain4.
-grep -qEx 'localhost5\.localdomain4\.\s+0\s+IN\s+CNAME\s+localhost5\.' "$RUN_OUT"
-grep -qEx 'localhost5\.\s+0\s+IN\s+A\s+127\.128\.0\.5' "$RUN_OUT"
-[ "$(wc -l <"$RUN_OUT")" -eq 2 ]
-
-: "--- Basic resolved tests ---"
-# Issue: https://github.com/systemd/systemd/issues/22229
-# PR: https://github.com/systemd/systemd/pull/22231
-FILTERED_NAMES=(
-    "0.in-addr.arpa"
-    "255.255.255.255.in-addr.arpa"
-    "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa"
-    "hello.invalid"
-    "hello.alt"
-)
+    mkdir -p /run/systemd/resolved.conf.d
+    {
+        echo "[Resolve]"
+        echo "FallbackDNS="
+        echo "DNSSEC=allow-downgrade"
+        echo "DNSOverTLS=opportunistic"
+    } >/run/systemd/resolved.conf.d/test.conf
+    ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
+    # Override the default NTA list, which turns off DNSSEC validation for (among
+    # others) the test. domain
+    mkdir -p "/etc/dnssec-trust-anchors.d/"
+    echo local >/etc/dnssec-trust-anchors.d/local.negative
+
+    # Copy over our knot configuration
+    mkdir -p /var/lib/knot/zones/ /etc/knot/
+    cp -rfv /usr/lib/systemd/tests/testdata/knot-data/zones/* /var/lib/knot/zones/
+    cp -fv /usr/lib/systemd/tests/testdata/knot-data/knot.conf /etc/knot/knot.conf
+    chgrp -R knot /etc/knot/ /var/lib/knot/
+    chmod -R ug+rwX /var/lib/knot/
+    chmod -R g+r /etc/knot/
+
+    : "Setup - Sign the root zone"
+    keymgr . generate algorithm=ECDSAP256SHA256 ksk=yes zsk=yes
+    # Create a trust anchor for resolved with our root zone
+    keymgr . ds | sed 's/ DS/ IN DS/g' >/etc/dnssec-trust-anchors.d/root.positive
+    # Create a bind-compatible trust anchor (for delv)
+    # Note: the trust-anchors directive is relatively new, so use the original
+    #       managed-keys one until it's widespread enough
+    {
+        echo 'managed-keys {'
+        keymgr . dnskey | sed -r 's/^\. DNSKEY ([0-9]+ [0-9]+ [0-9]+) (.+)$/. static-key \1 "\2";/g'
+        echo '};'
+    } >/etc/bind.keys
+    # Create an /etc/bind/bind.keys symlink, which is used by delv on Ubuntu
+    mkdir -p /etc/bind
+    ln -svf /etc/bind.keys /etc/bind/bind.keys
+
+    # Start the services
+    systemctl unmask systemd-networkd
+    systemctl restart systemd-networkd
+    /usr/lib/systemd/systemd-networkd-wait-online --interface=dns1:routable --timeout=60
+    systemctl reload systemd-resolved
+    systemctl start resolved-dummy-server
+
+    # Create knot's runtime dir, since from certain version it's provided only by
+    # the package and not created by tmpfiles/systemd
+    if [[ ! -d /run/knot ]]; then
+        mkdir -p /run/knot
+        chown -R knot:knot /run/knot
+    fi
+    systemctl start knot
+    # Wait a bit for the keys to propagate
+    sleep 4
+
+    systemctl status resolved-dummy-server
+    networkctl status
+    resolvectl status
+    resolvectl log-level debug
+
+    : "Setup - Start monitoring queries"
+    systemd-run -u resolvectl-monitor.service -p Type=notify resolvectl monitor
+    systemd-run -u resolvectl-monitor-json.service -p Type=notify resolvectl monitor --json=short
+
+    : "Setup - Check if all the zones are valid"
+    # FIXME: knot, unfortunately, incorrectly complains about missing zone files for zones
+    #        that are forwarded using the `dnsproxy` module. Until the issue is resolved,
+    #        let's fall back to pre-processing the `zone-check` output a bit before checking it
+    #
+    # See: https://gitlab.nic.cz/knot/knot-dns/-/issues/913
+    run knotc zone-check || :
+    sed -i '/forwarded.test./d' "$RUN_OUT"
+    [[ ! -s "$RUN_OUT" ]]
+    # We need to manually propagate the DS records of onlinesign.test. to the parent
+    # zone, since they're generated online
+    knotc zone-begin test.
+    if knotc zone-get test. onlinesign.test. ds | grep .; then
+        # Drop any old DS records, if present (e.g. on test re-run)
+        knotc zone-unset test. onlinesign.test. ds
+    fi
+
+    : "Setup - Propagate the new DS records"
+    while read -ra line; do
+        knotc zone-set test. "${line[0]}" 600 "${line[@]:1}"
+    done < <(keymgr onlinesign.test. ds)
+    knotc zone-commit test.
+
+    knotc reload
+    sleep 2
+
+    : "SETUP END"
+}
+
+# Test for resolvectl, resolvconf
+manual_testcase_01_resolvectl() {
+    ip link add hoge type dummy
+    ip link add hoge.foo type dummy
+
+    # Cleanup
+    # shellcheck disable=SC2317
+    cleanup() {
+        rm -f /run/systemd/resolved.conf.d/mdns-llmnr.conf
+        ip link del hoge
+        ip link del hoge.foo
+    }
+
+    trap cleanup RETURN
+
+    resolvectl dns hoge 10.0.0.1 10.0.0.2
+    resolvectl dns hoge.foo 10.0.0.3 10.0.0.4
+    assert_in '10.0.0.1 10.0.0.2' "$(resolvectl dns hoge)"
+    assert_in '10.0.0.3 10.0.0.4' "$(resolvectl dns hoge.foo)"
+    resolvectl dns hoge 10.0.1.1 10.0.1.2
+    resolvectl dns hoge.foo 10.0.1.3 10.0.1.4
+    assert_in '10.0.1.1 10.0.1.2' "$(resolvectl dns hoge)"
+    assert_in '10.0.1.3 10.0.1.4' "$(resolvectl dns hoge.foo)"
+    if ! RESOLVCONF=$(command -v resolvconf 2>/dev/null); then
+        TMPDIR=$(mktemp -d -p /tmp resolvconf-tests.XXXXXX)
+        RESOLVCONF="$TMPDIR"/resolvconf
+        ln -s "$(command -v resolvectl 2>/dev/null)" "$RESOLVCONF"
+    fi
+    echo nameserver 10.0.2.1 10.0.2.2 | "$RESOLVCONF" -a hoge
+    echo nameserver 10.0.2.3 10.0.2.4 | "$RESOLVCONF" -a hoge.foo
+    assert_in '10.0.2.1 10.0.2.2' "$(resolvectl dns hoge)"
+    assert_in '10.0.2.3 10.0.2.4' "$(resolvectl dns hoge.foo)"
+    echo nameserver 10.0.3.1 10.0.3.2 | "$RESOLVCONF" -a hoge.inet.ipsec.192.168.35
+    echo nameserver 10.0.3.3 10.0.3.4 | "$RESOLVCONF" -a hoge.foo.dhcp
+    assert_in '10.0.3.1 10.0.3.2' "$(resolvectl dns hoge)"
+    assert_in '10.0.3.3 10.0.3.4' "$(resolvectl dns hoge.foo)"
+
+    # Tests for _localdnsstub and _localdnsproxy
+    assert_in '127.0.0.53' "$(resolvectl query _localdnsstub)"
+    assert_in '_localdnsstub' "$(resolvectl query 127.0.0.53)"
+    assert_in '127.0.0.54' "$(resolvectl query _localdnsproxy)"
+    assert_in '_localdnsproxy' "$(resolvectl query 127.0.0.54)"
+
+    assert_in '127.0.0.53' "$(dig @127.0.0.53 _localdnsstub)"
+    assert_in '_localdnsstub' "$(dig @127.0.0.53 -x 127.0.0.53)"
+    assert_in '127.0.0.54' "$(dig @127.0.0.53 _localdnsproxy)"
+    assert_in '_localdnsproxy' "$(dig @127.0.0.53 -x 127.0.0.54)"
+}
+
+# Tests for mDNS and LLMNR settings
+manual_testcase_02_mdns_llmnr() {
+    ip link add hoge type dummy
+    ip link add hoge.foo type dummy
+
+    # Cleanup
+    cleanup() {
+        rm -f /run/systemd/resolved.conf.d/mdns-llmnr.conf
+        ip link del hoge
+        ip link del hoge.foo
+    }
 
-for name in "${FILTERED_NAMES[@]}"; do
-    (! run host "$name")
-    grep -qF "NXDOMAIN" "$RUN_OUT"
-done
-
-# Follow-up
-# Issue: https://github.com/systemd/systemd/issues/22401
-# PR: https://github.com/systemd/systemd/pull/22414
-run dig +noall +authority +comments SRV .
-grep -qF "status: NOERROR" "$RUN_OUT"
-grep -qE "IN\s+SOA\s+ns1\.unsigned\.test\." "$RUN_OUT"
-
-run resolvectl query -t SVCB svcb.test
-grep -qF 'alpn="dot"' "$RUN_OUT"
-grep -qF "ipv4hint=10.0.0.1" "$RUN_OUT"
-
-run resolvectl query -t HTTPS https.test
-grep -qF 'alpn="h2,h3"' "$RUN_OUT"
-
-: "--- ZONE: unsigned.test. ---"
-run dig @ns1.unsigned.test +short unsigned.test A unsigned.test AAAA
-grep -qF "10.0.0.101" "$RUN_OUT"
-grep -qF "fd00:dead:beef:cafe::101" "$RUN_OUT"
-run resolvectl query unsigned.test
-grep -qF "10.0.0.10" "$RUN_OUT"
-grep -qF "fd00:dead:beef:cafe::101" "$RUN_OUT"
-grep -qF "authenticated: no" "$RUN_OUT"
-run dig @ns1.unsigned.test +short MX unsigned.test
-grep -qF "15 mail.unsigned.test." "$RUN_OUT"
-run resolvectl query --legend=no -t MX unsigned.test
-grep -qF "unsigned.test IN MX 15 mail.unsigned.test" "$RUN_OUT"
-
-
-: "--- ZONE: signed.test (static DNSSEC) ---"
-# Check the trust chain (with and without systemd-resolved in between
-# Issue: https://github.com/systemd/systemd/issues/22002
-# PR: https://github.com/systemd/systemd/pull/23289
-run_delv @ns1.unsigned.test signed.test
-grep -qF "; fully validated" "$RUN_OUT"
-run_delv signed.test
-grep -qF "; fully validated" "$RUN_OUT"
-
-for addr in "${DNS_ADDRESSES[@]}"; do
-    run_delv "@$addr" -t A mail.signed.test
+    trap cleanup RETURN
+
+    mkdir -p /run/systemd/resolved.conf.d
+    {
+        echo "[Resolve]"
+        echo "MulticastDNS=no"
+        echo "LLMNR=no"
+    } >/run/systemd/resolved.conf.d/mdns-llmnr.conf
+    restart_resolved
+    # make sure networkd is not running.
+    systemctl stop systemd-networkd.service
+    assert_in 'no' "$(resolvectl mdns hoge)"
+    assert_in 'no' "$(resolvectl llmnr hoge)"
+    # Tests that reloading works
+    {
+        echo "[Resolve]"
+        echo "MulticastDNS=yes"
+        echo "LLMNR=yes"
+    } >/run/systemd/resolved.conf.d/mdns-llmnr.conf
+    systemctl reload systemd-resolved.service
+    # defaults to yes (both the global and per-link settings are yes)
+    assert_in 'yes' "$(resolvectl mdns hoge)"
+    assert_in 'yes' "$(resolvectl llmnr hoge)"
+    # set per-link setting
+    resolvectl mdns hoge yes
+    resolvectl llmnr hoge yes
+    assert_in 'yes' "$(resolvectl mdns hoge)"
+    assert_in 'yes' "$(resolvectl llmnr hoge)"
+    resolvectl mdns hoge resolve
+    resolvectl llmnr hoge resolve
+    assert_in 'resolve' "$(resolvectl mdns hoge)"
+    assert_in 'resolve' "$(resolvectl llmnr hoge)"
+    resolvectl mdns hoge no
+    resolvectl llmnr hoge no
+    assert_in 'no' "$(resolvectl mdns hoge)"
+    assert_in 'no' "$(resolvectl llmnr hoge)"
+    # downgrade global setting to resolve
+    {
+        echo "[Resolve]"
+        echo "MulticastDNS=resolve"
+        echo "LLMNR=resolve"
+    } >/run/systemd/resolved.conf.d/mdns-llmnr.conf
+    systemctl reload systemd-resolved.service
+    # set per-link setting
+    resolvectl mdns hoge yes
+    resolvectl llmnr hoge yes
+    assert_in 'resolve' "$(resolvectl mdns hoge)"
+    assert_in 'resolve' "$(resolvectl llmnr hoge)"
+    resolvectl mdns hoge resolve
+    resolvectl llmnr hoge resolve
+    assert_in 'resolve' "$(resolvectl mdns hoge)"
+    assert_in 'resolve' "$(resolvectl llmnr hoge)"
+    resolvectl mdns hoge no
+    resolvectl llmnr hoge no
+    assert_in 'no' "$(resolvectl mdns hoge)"
+    assert_in 'no' "$(resolvectl llmnr hoge)"
+    # downgrade global setting to no
+    {
+        echo "[Resolve]"
+        echo "MulticastDNS=no"
+        echo "LLMNR=no"
+    } >/run/systemd/resolved.conf.d/mdns-llmnr.conf
+    systemctl reload systemd-resolved.service
+    # set per-link setting
+    resolvectl mdns hoge yes
+    resolvectl llmnr hoge yes
+    assert_in 'no' "$(resolvectl mdns hoge)"
+    assert_in 'no' "$(resolvectl llmnr hoge)"
+    resolvectl mdns hoge resolve
+    resolvectl llmnr hoge resolve
+    assert_in 'no' "$(resolvectl mdns hoge)"
+    assert_in 'no' "$(resolvectl llmnr hoge)"
+    resolvectl mdns hoge no
+    resolvectl llmnr hoge no
+    assert_in 'no' "$(resolvectl mdns hoge)"
+    assert_in 'no' "$(resolvectl llmnr hoge)"
+}
+
+testcase_03_23951() {
+    : "--- nss-resolve/nss-myhostname tests"
+    # Sanity check
+    TIMESTAMP=$(date '+%F %T')
+    # Issue: https://github.com/systemd/systemd/issues/23951
+    # With IPv6 enabled
+    run getent -s resolve ahosts ns1.unsigned.test
+    grep -qE "^fd00:dead:beef:cafe::1\s+STREAM\s+ns1\.unsigned\.test" "$RUN_OUT"
+    monitor_check_rr "$TIMESTAMP" "ns1.unsigned.test IN AAAA fd00:dead:beef:cafe::1"
+    # With IPv6 disabled
+    # Issue: https://github.com/systemd/systemd/issues/23951
+    disable_ipv6
+    run getent -s resolve ahosts ns1.unsigned.test
+    grep -qE "^10\.0\.0\.1\s+STREAM\s+ns1\.unsigned\.test" "$RUN_OUT"
+    (! grep -qE "fd00:dead:beef:cafe::1" "$RUN_OUT")
+    monitor_check_rr "$TIMESTAMP" "ns1.unsigned.test IN A 10.0.0.1"
+    enable_ipv6
+}
+
+testcase_04_18812() {
+    # Issue: https://github.com/systemd/systemd/issues/18812
+    # PR: https://github.com/systemd/systemd/pull/18896
+    # Follow-up issue: https://github.com/systemd/systemd/issues/23152
+    # Follow-up PR: https://github.com/systemd/systemd/pull/23161
+    # With IPv6 enabled
+    run getent -s resolve ahosts localhost
+    grep -qE "^::1\s+STREAM\s+localhost" "$RUN_OUT"
+    run getent -s myhostname ahosts localhost
+    grep -qE "^::1\s+STREAM\s+localhost" "$RUN_OUT"
+    # With IPv6 disabled
+    disable_ipv6
+    run getent -s resolve ahosts localhost
+    grep -qE "^127\.0\.0\.1\s+STREAM\s+localhost" "$RUN_OUT"
+    (! grep -qE "::1" "$RUN_OUT")
+    run getent -s myhostname ahosts localhost
+    grep -qE "^127\.0\.0\.1\s+STREAM\s+localhost" "$RUN_OUT"
+    enable_ipv6
+}
+
+testcase_05_25088() {
+    # Issue: https://github.com/systemd/systemd/issues/25088
+    run getent -s resolve hosts 127.128.0.5
+    grep -qEx '127\.128\.0\.5\s+localhost5(\s+localhost5?\.localdomain[45]?){4}' "$RUN_OUT"
+    [ "$(wc -l <"$RUN_OUT")" -eq 1 ]
+}
+
+testcase_06_20158() {
+    # Issue: https://github.com/systemd/systemd/issues/20158
+    run dig +noall +answer +additional localhost5.
+    grep -qEx 'localhost5\.\s+0\s+IN\s+A\s+127\.128\.0\.5' "$RUN_OUT"
+    [ "$(wc -l <"$RUN_OUT")" -eq 1 ]
+    run dig +noall +answer +additional localhost5.localdomain4.
+    grep -qEx 'localhost5\.localdomain4\.\s+0\s+IN\s+CNAME\s+localhost5\.' "$RUN_OUT"
+    grep -qEx 'localhost5\.\s+0\s+IN\s+A\s+127\.128\.0\.5' "$RUN_OUT"
+    [ "$(wc -l <"$RUN_OUT")" -eq 2 ]
+}
+
+testcase_07_22229() {
+    : "--- Basic resolved tests ---"
+    # Issue: https://github.com/systemd/systemd/issues/22229
+    # PR: https://github.com/systemd/systemd/pull/22231
+    FILTERED_NAMES=(
+        "0.in-addr.arpa"
+        "255.255.255.255.in-addr.arpa"
+        "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa"
+        "hello.invalid"
+        "hello.alt"
+    )
+
+    for name in "${FILTERED_NAMES[@]}"; do
+        (! run host "$name")
+        grep -qF "NXDOMAIN" "$RUN_OUT"
+    done
+
+    # Follow-up
+    # Issue: https://github.com/systemd/systemd/issues/22401
+    # PR: https://github.com/systemd/systemd/pull/22414
+    run dig +noall +authority +comments SRV .
+    grep -qF "status: NOERROR" "$RUN_OUT"
+    grep -qE "IN\s+SOA\s+ns1\.unsigned\.test\." "$RUN_OUT"
+}
+
+testcase_08_resolved() {
+    run resolvectl query -t SVCB svcb.test
+    grep -qF 'alpn="dot"' "$RUN_OUT"
+    grep -qF "ipv4hint=10.0.0.1" "$RUN_OUT"
+
+    run resolvectl query -t HTTPS https.test
+    grep -qF 'alpn="h2,h3"' "$RUN_OUT"
+
+    : "--- ZONE: unsigned.test. ---"
+    run dig @ns1.unsigned.test +short unsigned.test A unsigned.test AAAA
+    grep -qF "10.0.0.101" "$RUN_OUT"
+    grep -qF "fd00:dead:beef:cafe::101" "$RUN_OUT"
+    run resolvectl query unsigned.test
+    grep -qF "10.0.0.10" "$RUN_OUT"
+    grep -qF "fd00:dead:beef:cafe::101" "$RUN_OUT"
+    grep -qF "authenticated: no" "$RUN_OUT"
+    run dig @ns1.unsigned.test +short MX unsigned.test
+    grep -qF "15 mail.unsigned.test." "$RUN_OUT"
+    run resolvectl query --legend=no -t MX unsigned.test
+    grep -qF "unsigned.test IN MX 15 mail.unsigned.test" "$RUN_OUT"
+
+    : "--- ZONE: signed.test (static DNSSEC) ---"
+    # Check the trust chain (with and without systemd-resolved in between
+    # Issue: https://github.com/systemd/systemd/issues/22002
+    # PR: https://github.com/systemd/systemd/pull/23289
+    run_delv @ns1.unsigned.test signed.test
     grep -qF "; fully validated" "$RUN_OUT"
-    run_delv "@$addr" -t AAAA mail.signed.test
+    run_delv signed.test
     grep -qF "; fully validated" "$RUN_OUT"
-done
-run resolvectl query mail.signed.test
-grep -qF "10.0.0.11" "$RUN_OUT"
-grep -qF "fd00:dead:beef:cafe::11" "$RUN_OUT"
-grep -qF "authenticated: yes" "$RUN_OUT"
-
-run dig +short signed.test
-grep -qF "10.0.0.10" "$RUN_OUT"
-run resolvectl query signed.test
-grep -qF "signed.test: 10.0.0.10" "$RUN_OUT"
-grep -qF "authenticated: yes" "$RUN_OUT"
-run dig @ns1.unsigned.test +short MX signed.test
-grep -qF "10 mail.signed.test." "$RUN_OUT"
-run resolvectl query --legend=no -t MX signed.test
-grep -qF "signed.test IN MX 10 mail.signed.test" "$RUN_OUT"
-# Check a non-existent domain
-run dig +dnssec this.does.not.exist.signed.test
-grep -qF "status: NXDOMAIN" "$RUN_OUT"
-# Check a wildcard record
-run resolvectl query -t TXT this.should.be.authenticated.wild.signed.test
-grep -qF 'this.should.be.authenticated.wild.signed.test IN TXT "this is a wildcard"' "$RUN_OUT"
-grep -qF "authenticated: yes" "$RUN_OUT"
-# Check SRV support
-run resolvectl service _mysvc._tcp signed.test
-grep -qF "myservice.signed.test:1234" "$RUN_OUT"
-grep -qF "10.0.0.20" "$RUN_OUT"
-grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT"
-grep -qF "authenticated: yes" "$RUN_OUT"
-
-# Test service resolve over Varlink
-run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}'
-grep -qF '"services":[{"priority":10,"weight":5,"port":1234,"hostname":"myservice.signed.test","canonicalName":"myservice.signed.test","addresses":[{"ifindex":' "$RUN_OUT"
-grep -qF '"family":10,"address":[253,0,222,173,190,239,202,254,0,0,0,0,0,0,0,23]' "$RUN_OUT"
-grep -qF '"family":2,"address":[10,0,0,20]' "$RUN_OUT"
-grep -qF '}]}],"txt":["This is TXT for myservice"],"canonical":{"name":null,"type":"_mysvc._tcp","domain":"signed.test"},"flags":' "$RUN_OUT"
-
-# without name
-run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"type":"_mysvc._tcp","domain":"signed.test"}'
-# without txt (SD_RESOLVE_NO_TXT)
-run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"type":"_mysvc._tcp","domain":"signed.test","flags":64}'
-(! grep -qF '"txt"' "$RUN_OUT")
-# without address (SD_RESOLVE_NO_ADDRESS)
-run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"type":"_mysvc._tcp","domain":"signed.test","flags":128}'
-(! grep -qF '"addresses"' "$RUN_OUT")
-# without txt and address
-run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"type":"_mysvc._tcp","domain":"signed.test","flags":192}'
-(! grep -qF '"txt"' "$RUN_OUT")
-(! grep -qF '"addresses"' "$RUN_OUT")
-
-(! run resolvectl service _invalidsvc._udp signed.test)
-grep -qE "invalidservice\.signed\.test' not found" "$RUN_OUT"
-run resolvectl service _untrustedsvc._udp signed.test
-grep -qF "myservice.untrusted.test:1111" "$RUN_OUT"
-grep -qF "10.0.0.123" "$RUN_OUT"
-grep -qF "fd00:dead:beef:cafe::123" "$RUN_OUT"
-grep -qF "authenticated: yes" "$RUN_OUT"
-# Check OPENPGPKEY support
-run_delv -t OPENPGPKEY 5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test
-grep -qF "; fully validated" "$RUN_OUT"
-run resolvectl openpgp mr.smith@signed.test
-grep -qF "5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test" "$RUN_OUT"
-grep -qF "authenticated: yes" "$RUN_OUT"
-# Check zone transfers (AXFR/IXFR)
-# Note: since resolved doesn't support zone transfers, let's just make sure it
-#       simply refuses such requests without choking on them
-# See: https://github.com/systemd/systemd/pull/30809#issuecomment-1880102804
-run dig @ns1.unsigned.test AXFR signed.test
-grep -qE "SOA\s+ns1.unsigned.test. root.unsigned.test." "$RUN_OUT"
-run dig AXFR signed.test
-grep -qF "; Transfer failed" "$RUN_OUT"
-run dig @ns1.unsigned.test IXFR=43 signed.test
-grep -qE "SOA\s+ns1.unsigned.test. root.unsigned.test." "$RUN_OUT"
-run dig IXFR=43 signed.test
-grep -qF "; Transfer failed" "$RUN_OUT"
-
-# DNSSEC validation with multiple records of the same type for the same name
-# Issue: https://github.com/systemd/systemd/issues/22002
-# PR: https://github.com/systemd/systemd/pull/23289
-check_domain() {
-    local domain="${1:?}"
-    local record="${2:?}"
-    local message="${3:?}"
-    local addr
 
     for addr in "${DNS_ADDRESSES[@]}"; do
-        run_delv "@$addr" -t "$record" "$domain"
-        grep -qF "$message" "$RUN_OUT"
+        run_delv "@$addr" -t A mail.signed.test
+        grep -qF "; fully validated" "$RUN_OUT"
+        run_delv "@$addr" -t AAAA mail.signed.test
+        grep -qF "; fully validated" "$RUN_OUT"
     done
+    run resolvectl query mail.signed.test
+    grep -qF "10.0.0.11" "$RUN_OUT"
+    grep -qF "fd00:dead:beef:cafe::11" "$RUN_OUT"
+    grep -qF "authenticated: yes" "$RUN_OUT"
 
-    run_delv -t "$record" "$domain"
-    grep -qF "$message" "$RUN_OUT"
+    run dig +short signed.test
+    grep -qF "10.0.0.10" "$RUN_OUT"
+    run resolvectl query signed.test
+    grep -qF "signed.test: 10.0.0.10" "$RUN_OUT"
+    grep -qF "authenticated: yes" "$RUN_OUT"
+    run dig @ns1.unsigned.test +short MX signed.test
+    grep -qF "10 mail.signed.test." "$RUN_OUT"
+    run resolvectl query --legend=no -t MX signed.test
+    grep -qF "signed.test IN MX 10 mail.signed.test" "$RUN_OUT"
+    # Check a non-existent domain
+    run dig +dnssec this.does.not.exist.signed.test
+    grep -qF "status: NXDOMAIN" "$RUN_OUT"
+    # Check a wildcard record
+    run resolvectl query -t TXT this.should.be.authenticated.wild.signed.test
+    grep -qF 'this.should.be.authenticated.wild.signed.test IN TXT "this is a wildcard"' "$RUN_OUT"
+    grep -qF "authenticated: yes" "$RUN_OUT"
+    # Check SRV support
+    run resolvectl service _mysvc._tcp signed.test
+    grep -qF "myservice.signed.test:1234" "$RUN_OUT"
+    grep -qF "10.0.0.20" "$RUN_OUT"
+    grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT"
+    grep -qF "authenticated: yes" "$RUN_OUT"
 
-    run resolvectl query "$domain"
+    # Test service resolve over Varlink
+    run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}'
+    grep -qF '"services":[{"priority":10,"weight":5,"port":1234,"hostname":"myservice.signed.test","canonicalName":"myservice.signed.test","addresses":[{"ifindex":' "$RUN_OUT"
+    grep -qF '"family":10,"address":[253,0,222,173,190,239,202,254,0,0,0,0,0,0,0,23]' "$RUN_OUT"
+    grep -qF '"family":2,"address":[10,0,0,20]' "$RUN_OUT"
+    grep -qF '}]}],"txt":["This is TXT for myservice"],"canonical":{"name":null,"type":"_mysvc._tcp","domain":"signed.test"},"flags":' "$RUN_OUT"
+
+    # without name
+    run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"type":"_mysvc._tcp","domain":"signed.test"}'
+    # without txt (SD_RESOLVE_NO_TXT)
+    run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"type":"_mysvc._tcp","domain":"signed.test","flags":64}'
+    (! grep -qF '"txt"' "$RUN_OUT")
+    # without address (SD_RESOLVE_NO_ADDRESS)
+    run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"type":"_mysvc._tcp","domain":"signed.test","flags":128}'
+    (! grep -qF '"addresses"' "$RUN_OUT")
+    # without txt and address
+    run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"type":"_mysvc._tcp","domain":"signed.test","flags":192}'
+    (! grep -qF '"txt"' "$RUN_OUT")
+    (! grep -qF '"addresses"' "$RUN_OUT")
+
+    (! run resolvectl service _invalidsvc._udp signed.test)
+    grep -qE "invalidservice\.signed\.test' not found" "$RUN_OUT"
+    run resolvectl service _untrustedsvc._udp signed.test
+    grep -qF "myservice.untrusted.test:1111" "$RUN_OUT"
+    grep -qF "10.0.0.123" "$RUN_OUT"
+    grep -qF "fd00:dead:beef:cafe::123" "$RUN_OUT"
     grep -qF "authenticated: yes" "$RUN_OUT"
-}
+    # Check OPENPGPKEY support
+    run_delv -t OPENPGPKEY 5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test
+    grep -qF "; fully validated" "$RUN_OUT"
+    run resolvectl openpgp mr.smith@signed.test
+    grep -qF "5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test" "$RUN_OUT"
+    grep -qF "authenticated: yes" "$RUN_OUT"
+    # Check zone transfers (AXFR/IXFR)
+    # Note: since resolved doesn't support zone transfers, let's just make sure it
+    #       simply refuses such requests without choking on them
+    # See: https://github.com/systemd/systemd/pull/30809#issuecomment-1880102804
+    run dig @ns1.unsigned.test AXFR signed.test
+    grep -qE "SOA\s+ns1.unsigned.test. root.unsigned.test." "$RUN_OUT"
+    run dig AXFR signed.test
+    grep -qF "; Transfer failed" "$RUN_OUT"
+    run dig @ns1.unsigned.test IXFR=43 signed.test
+    grep -qE "SOA\s+ns1.unsigned.test. root.unsigned.test." "$RUN_OUT"
+    run dig IXFR=43 signed.test
+    grep -qF "; Transfer failed" "$RUN_OUT"
+
+    # DNSSEC validation with multiple records of the same type for the same name
+    # Issue: https://github.com/systemd/systemd/issues/22002
+    # PR: https://github.com/systemd/systemd/pull/23289
+    check_domain() {
+        local domain="${1:?}"
+        local record="${2:?}"
+        local message="${3:?}"
+        local addr
+
+        for addr in "${DNS_ADDRESSES[@]}"; do
+            run_delv "@$addr" -t "$record" "$domain"
+            grep -qF "$message" "$RUN_OUT"
+        done
+
+        run_delv -t "$record" "$domain"
+        grep -qF "$message" "$RUN_OUT"
+
+        run resolvectl query "$domain"
+        grep -qF "authenticated: yes" "$RUN_OUT"
+    }
+
+    check_domain "dupe.signed.test"       "A"    "; fully validated"
+    check_domain "dupe.signed.test"       "AAAA" "; negative response, fully validated"
+    check_domain "dupe-ipv6.signed.test"  "AAAA" "; fully validated"
+    check_domain "dupe-ipv6.signed.test"  "A"    "; negative response, fully validated"
+    check_domain "dupe-mixed.signed.test" "A"    "; fully validated"
+    check_domain "dupe-mixed.signed.test" "AAAA" "; fully validated"
+
+    # Test resolution of CNAME chains
+    TIMESTAMP=$(date '+%F %T')
+    run resolvectl query -t A cname-chain.signed.test
+    grep -qF "follow14.final.signed.test IN A 10.0.0.14" "$RUN_OUT"
+    grep -qF "authenticated: yes" "$RUN_OUT"
+
+    monitor_check_rr "$TIMESTAMP" "follow10.so.close.signed.test IN CNAME follow11.yet.so.far.signed.test"
+    monitor_check_rr "$TIMESTAMP" "follow11.yet.so.far.signed.test IN CNAME follow12.getting.hot.signed.test"
+    monitor_check_rr "$TIMESTAMP" "follow12.getting.hot.signed.test IN CNAME follow13.almost.final.signed.test"
+    monitor_check_rr "$TIMESTAMP" "follow13.almost.final.signed.test IN CNAME follow14.final.signed.test"
+    monitor_check_rr "$TIMESTAMP" "follow14.final.signed.test IN A 10.0.0.14"
+
+    # Non-existing RR + CNAME chain
+    #run dig +dnssec AAAA cname-chain.signed.test
+    #grep -qF "status: NOERROR" "$RUN_OUT"
+    #grep -qE "^follow14\.final\.signed\.test\..+IN\s+NSEC\s+" "$RUN_OUT"
+
+
+    : "--- ZONE: onlinesign.test (dynamic DNSSEC) ---"
+    # Check the trust chain (with and without systemd-resolved in between
+    # Issue: https://github.com/systemd/systemd/issues/22002
+    # PR: https://github.com/systemd/systemd/pull/23289
+    run_delv @ns1.unsigned.test sub.onlinesign.test
+    grep -qF "; fully validated" "$RUN_OUT"
+    run_delv sub.onlinesign.test
+    grep -qF "; fully validated" "$RUN_OUT"
+
+    run dig +short sub.onlinesign.test
+    grep -qF "10.0.0.133" "$RUN_OUT"
+    run resolvectl query sub.onlinesign.test
+    grep -qF "sub.onlinesign.test: 10.0.0.133" "$RUN_OUT"
+    grep -qF "authenticated: yes" "$RUN_OUT"
+    run dig @ns1.unsigned.test +short TXT onlinesign.test
+    grep -qF '"hello from onlinesign"' "$RUN_OUT"
+    run resolvectl query --legend=no -t TXT onlinesign.test
+    grep -qF 'onlinesign.test IN TXT "hello from onlinesign"' "$RUN_OUT"
 
-check_domain "dupe.signed.test"       "A"    "; fully validated"
-check_domain "dupe.signed.test"       "AAAA" "; negative response, fully validated"
-check_domain "dupe-ipv6.signed.test"  "AAAA" "; fully validated"
-check_domain "dupe-ipv6.signed.test"  "A"    "; negative response, fully validated"
-check_domain "dupe-mixed.signed.test" "A"    "; fully validated"
-check_domain "dupe-mixed.signed.test" "AAAA" "; fully validated"
-
-# Test resolution of CNAME chains
-TIMESTAMP=$(date '+%F %T')
-run resolvectl query -t A cname-chain.signed.test
-grep -qF "follow14.final.signed.test IN A 10.0.0.14" "$RUN_OUT"
-grep -qF "authenticated: yes" "$RUN_OUT"
-
-monitor_check_rr "$TIMESTAMP" "follow10.so.close.signed.test IN CNAME follow11.yet.so.far.signed.test"
-monitor_check_rr "$TIMESTAMP" "follow11.yet.so.far.signed.test IN CNAME follow12.getting.hot.signed.test"
-monitor_check_rr "$TIMESTAMP" "follow12.getting.hot.signed.test IN CNAME follow13.almost.final.signed.test"
-monitor_check_rr "$TIMESTAMP" "follow13.almost.final.signed.test IN CNAME follow14.final.signed.test"
-monitor_check_rr "$TIMESTAMP" "follow14.final.signed.test IN A 10.0.0.14"
-
-# Non-existing RR + CNAME chain
-#run dig +dnssec AAAA cname-chain.signed.test
-#grep -qF "status: NOERROR" "$RUN_OUT"
-#grep -qE "^follow14\.final\.signed\.test\..+IN\s+NSEC\s+" "$RUN_OUT"
-
-
-: "--- ZONE: onlinesign.test (dynamic DNSSEC) ---"
-# Check the trust chain (with and without systemd-resolved in between
-# Issue: https://github.com/systemd/systemd/issues/22002
-# PR: https://github.com/systemd/systemd/pull/23289
-run_delv @ns1.unsigned.test sub.onlinesign.test
-grep -qF "; fully validated" "$RUN_OUT"
-run_delv sub.onlinesign.test
-grep -qF "; fully validated" "$RUN_OUT"
-
-run dig +short sub.onlinesign.test
-grep -qF "10.0.0.133" "$RUN_OUT"
-run resolvectl query sub.onlinesign.test
-grep -qF "sub.onlinesign.test: 10.0.0.133" "$RUN_OUT"
-grep -qF "authenticated: yes" "$RUN_OUT"
-run dig @ns1.unsigned.test +short TXT onlinesign.test
-grep -qF '"hello from onlinesign"' "$RUN_OUT"
-run resolvectl query --legend=no -t TXT onlinesign.test
-grep -qF 'onlinesign.test IN TXT "hello from onlinesign"' "$RUN_OUT"
-
-for addr in "${DNS_ADDRESSES[@]}"; do
-    run_delv "@$addr" -t A dual.onlinesign.test
+    for addr in "${DNS_ADDRESSES[@]}"; do
+        run_delv "@$addr" -t A dual.onlinesign.test
+        grep -qF "10.0.0.135" "$RUN_OUT"
+        run_delv "@$addr" -t AAAA dual.onlinesign.test
+        grep -qF "fd00:dead:beef:cafe::135" "$RUN_OUT"
+        run_delv "@$addr" -t ANY ipv6.onlinesign.test
+        grep -qF "fd00:dead:beef:cafe::136" "$RUN_OUT"
+    done
+    run resolvectl query dual.onlinesign.test
     grep -qF "10.0.0.135" "$RUN_OUT"
-    run_delv "@$addr" -t AAAA dual.onlinesign.test
     grep -qF "fd00:dead:beef:cafe::135" "$RUN_OUT"
-    run_delv "@$addr" -t ANY ipv6.onlinesign.test
+    grep -qF "authenticated: yes" "$RUN_OUT"
+    run resolvectl query ipv6.onlinesign.test
     grep -qF "fd00:dead:beef:cafe::136" "$RUN_OUT"
-done
-run resolvectl query dual.onlinesign.test
-grep -qF "10.0.0.135" "$RUN_OUT"
-grep -qF "fd00:dead:beef:cafe::135" "$RUN_OUT"
-grep -qF "authenticated: yes" "$RUN_OUT"
-run resolvectl query ipv6.onlinesign.test
-grep -qF "fd00:dead:beef:cafe::136" "$RUN_OUT"
-grep -qF "authenticated: yes" "$RUN_OUT"
-
-# Check a non-existent domain
-# Note: mod-onlinesign utilizes Minimally Covering NSEC Records, hence the
-#       different response than with "standard" DNSSEC
-run dig +dnssec this.does.not.exist.onlinesign.test
-grep -qF "status: NOERROR" "$RUN_OUT"
-grep -qF "NSEC \\000.this.does.not.exist.onlinesign.test." "$RUN_OUT"
-# Check a wildcard record
-run resolvectl query -t TXT this.should.be.authenticated.wild.onlinesign.test
-grep -qF 'this.should.be.authenticated.wild.onlinesign.test IN TXT "this is an onlinesign wildcard"' "$RUN_OUT"
-grep -qF "authenticated: yes" "$RUN_OUT"
-
-# Resolve via dbus method
-TIMESTAMP=$(date '+%F %T')
-run busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager ResolveHostname 'isit' 0 secondsub.onlinesign.test 0 0
-grep -qF '10 0 0 134 "secondsub.onlinesign.test"' "$RUN_OUT"
-monitor_check_rr "$TIMESTAMP" "secondsub.onlinesign.test IN A 10.0.0.134"
-
-
-: "--- ZONE: untrusted.test (DNSSEC without propagated DS records) ---"
-# Issue: https://github.com/systemd/systemd/issues/23955
-# FIXME
-resolvectl flush-caches
-#run dig +short untrusted.test A untrusted.test AAAA
-#grep -qF "10.0.0.121" "$RUN_OUT"
-#grep -qF "fd00:dead:beef:cafe::121" "$RUN_OUT"
-run resolvectl query untrusted.test
-grep -qF "untrusted.test:" "$RUN_OUT"
-grep -qF "10.0.0.121" "$RUN_OUT"
-grep -qF "fd00:dead:beef:cafe::121" "$RUN_OUT"
-grep -qF "authenticated: no" "$RUN_OUT"
-run resolvectl service _mysvc._tcp untrusted.test
-grep -qF "myservice.untrusted.test:1234" "$RUN_OUT"
-grep -qF "10.0.0.123" "$RUN_OUT"
-grep -qF "fd00:dead:beef:cafe::123" "$RUN_OUT"
-
-# Issue: https://github.com/systemd/systemd/issues/19472
-# 1) Query for a non-existing RR should return NOERROR + NSEC (?), not NXDOMAIN
-# FIXME: re-enable once the issue is resolved
-#run dig +dnssec AAAA untrusted.test
-#grep -qF "status: NOERROR" "$RUN_OUT"
-#grep -qE "^untrusted\.test\..+IN\s+NSEC\s+" "$RUN_OUT"
-## 2) Query for a non-existing name should return NXDOMAIN, not SERVFAIL
-#run dig +dnssec this.does.not.exist.untrusted.test
-#grep -qF "status: NXDOMAIN" "$RUN_OUT"
-
-: "--- ZONE: forwarded.test (queries forwarded to our dummy test server) ---"
-JOURNAL_CURSOR="$(mktemp)"
-journalctl -n0 -q --cursor-file="$JOURNAL_CURSOR"
-
-# See "test-resolved-dummy-server.c" for the server part
-(! run resolvectl query nope.forwarded.test)
-grep -qF "nope.forwarded.test" "$RUN_OUT"
-grep -qF "not found" "$RUN_OUT"
-
-# SERVFAIL + EDE code 6: DNSSEC Bogus
-(! run resolvectl query edns-bogus-dnssec.forwarded.test)
-grep -qE "^edns-bogus-dnssec.forwarded.test:.+: upstream-failure \(DNSSEC Bogus\)" "$RUN_OUT"
-# Same thing, but over Varlink
-(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-bogus-dnssec.forwarded.test"}')
-grep -qF "io.systemd.Resolve.DNSSECValidationFailed" "$RUN_OUT"
-grep -qF '{"result":"upstream-failure","extendedDNSErrorCode":6}' "$RUN_OUT"
-journalctl --sync
-journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(DNSSEC Bogus\). Lookup failed."
-
-# SERVFAIL + EDE code 16: Censored + extra text
-(! run resolvectl query edns-extra-text.forwarded.test)
-grep -qE "^edns-extra-text.forwarded.test.+: SERVFAIL \(Censored: Nothing to see here!\)" "$RUN_OUT"
-(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-extra-text.forwarded.test"}')
-grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
-grep -qF '{"rcode":2,"extendedDNSErrorCode":16,"extendedDNSErrorMessage":"Nothing to see here!"}' "$RUN_OUT"
-journalctl --sync
-journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Censored: Nothing to see here!\)"
-
-# SERVFAIL + EDE code 0: Other + extra text
-(! run resolvectl query edns-code-zero.forwarded.test)
-grep -qE "^edns-code-zero.forwarded.test:.+: SERVFAIL \(Other: 🐱\)" "$RUN_OUT"
-(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-code-zero.forwarded.test"}')
-grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
-grep -qF '{"rcode":2,"extendedDNSErrorCode":0,"extendedDNSErrorMessage":"🐱"}' "$RUN_OUT"
-journalctl --sync
-journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Other: 🐱\)"
-
-# SERVFAIL + invalid EDE code
-(! run resolvectl query edns-invalid-code.forwarded.test)
-grep -qE "^edns-invalid-code.forwarded.test:.+: SERVFAIL \([0-9]+\)" "$RUN_OUT"
-(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code.forwarded.test"}')
-grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
-grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+}' "$RUN_OUT"
-journalctl --sync
-journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+\)"
-
-# SERVFAIL + invalid EDE code + extra text
-(! run resolvectl query edns-invalid-code-with-extra-text.forwarded.test)
-grep -qE '^edns-invalid-code-with-extra-text.forwarded.test:.+: SERVFAIL \([0-9]+: Hello \[#\]\$%~ World\)' "$RUN_OUT"
-(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code-with-extra-text.forwarded.test"}')
-grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
-grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+,"extendedDNSErrorMessage":"Hello \[#\]\$%~ World"}' "$RUN_OUT"
-journalctl --sync
-journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+: Hello \[\#\]\\$%~ World\)"
-
-### Test resolvectl show-cache
-run resolvectl show-cache
-run resolvectl show-cache --json=short
-run resolvectl show-cache --json=pretty
-
-# Issue: https://github.com/systemd/systemd/issues/29580 (part #1)
-dig @127.0.0.54 signed.test
-
-systemctl stop resolvectl-monitor.service
-systemctl stop resolvectl-monitor-json.service
-
-# Issue: https://github.com/systemd/systemd/issues/29580 (part #2)
-#
-# Check for any warnings regarding malformed messages
-(! journalctl -u resolvectl-monitor.service -u reseolvectl-monitor-json.service -p warning --grep malformed)
-# Verify that all queries recorded by `resolvectl monitor --json` produced a valid JSON
-# with expected fields
-journalctl -p info -o cat _SYSTEMD_UNIT="resolvectl-monitor-json.service" | while read -r line; do
-    # Check that both "question" and "answer" fields are arrays
+    grep -qF "authenticated: yes" "$RUN_OUT"
+
+    # Check a non-existent domain
+    # Note: mod-onlinesign utilizes Minimally Covering NSEC Records, hence the
+    #       different response than with "standard" DNSSEC
+    run dig +dnssec this.does.not.exist.onlinesign.test
+    grep -qF "status: NOERROR" "$RUN_OUT"
+    grep -qF "NSEC \\000.this.does.not.exist.onlinesign.test." "$RUN_OUT"
+    # Check a wildcard record
+    run resolvectl query -t TXT this.should.be.authenticated.wild.onlinesign.test
+    grep -qF 'this.should.be.authenticated.wild.onlinesign.test IN TXT "this is an onlinesign wildcard"' "$RUN_OUT"
+    grep -qF "authenticated: yes" "$RUN_OUT"
+
+    # Resolve via dbus method
+    TIMESTAMP=$(date '+%F %T')
+    run busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager ResolveHostname 'isit' 0 secondsub.onlinesign.test 0 0
+    grep -qF '10 0 0 134 "secondsub.onlinesign.test"' "$RUN_OUT"
+    monitor_check_rr "$TIMESTAMP" "secondsub.onlinesign.test IN A 10.0.0.134"
+
+
+    : "--- ZONE: untrusted.test (DNSSEC without propagated DS records) ---"
+    # Issue: https://github.com/systemd/systemd/issues/23955
+    # FIXME
+    resolvectl flush-caches
+    #run dig +short untrusted.test A untrusted.test AAAA
+    #grep -qF "10.0.0.121" "$RUN_OUT"
+    #grep -qF "fd00:dead:beef:cafe::121" "$RUN_OUT"
+    run resolvectl query untrusted.test
+    grep -qF "untrusted.test:" "$RUN_OUT"
+    grep -qF "10.0.0.121" "$RUN_OUT"
+    grep -qF "fd00:dead:beef:cafe::121" "$RUN_OUT"
+    grep -qF "authenticated: no" "$RUN_OUT"
+    run resolvectl service _mysvc._tcp untrusted.test
+    grep -qF "myservice.untrusted.test:1234" "$RUN_OUT"
+    grep -qF "10.0.0.123" "$RUN_OUT"
+    grep -qF "fd00:dead:beef:cafe::123" "$RUN_OUT"
+
+    # Issue: https://github.com/systemd/systemd/issues/19472
+    # 1) Query for a non-existing RR should return NOERROR + NSEC (?), not NXDOMAIN
+    # FIXME: re-enable once the issue is resolved
+    #run dig +dnssec AAAA untrusted.test
+    #grep -qF "status: NOERROR" "$RUN_OUT"
+    #grep -qE "^untrusted\.test\..+IN\s+NSEC\s+" "$RUN_OUT"
+    ## 2) Query for a non-existing name should return NXDOMAIN, not SERVFAIL
+    #run dig +dnssec this.does.not.exist.untrusted.test
+    #grep -qF "status: NXDOMAIN" "$RUN_OUT"
+
+    : "--- ZONE: forwarded.test (queries forwarded to our dummy test server) ---"
+    JOURNAL_CURSOR="$(mktemp)"
+    journalctl -n0 -q --cursor-file="$JOURNAL_CURSOR"
+
+    # See "test-resolved-dummy-server.c" for the server part
+    (! run resolvectl query nope.forwarded.test)
+    grep -qF "nope.forwarded.test" "$RUN_OUT"
+    grep -qF "not found" "$RUN_OUT"
+
+    # SERVFAIL + EDE code 6: DNSSEC Bogus
+    (! run resolvectl query edns-bogus-dnssec.forwarded.test)
+    grep -qE "^edns-bogus-dnssec.forwarded.test:.+: upstream-failure \(DNSSEC Bogus\)" "$RUN_OUT"
+    # Same thing, but over Varlink
+    (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-bogus-dnssec.forwarded.test"}')
+    grep -qF "io.systemd.Resolve.DNSSECValidationFailed" "$RUN_OUT"
+    grep -qF '{"result":"upstream-failure","extendedDNSErrorCode":6}' "$RUN_OUT"
+    journalctl --sync
+    journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(DNSSEC Bogus\). Lookup failed."
+
+    # SERVFAIL + EDE code 16: Censored + extra text
+    (! run resolvectl query edns-extra-text.forwarded.test)
+    grep -qE "^edns-extra-text.forwarded.test.+: SERVFAIL \(Censored: Nothing to see here!\)" "$RUN_OUT"
+    (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-extra-text.forwarded.test"}')
+    grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+    grep -qF '{"rcode":2,"extendedDNSErrorCode":16,"extendedDNSErrorMessage":"Nothing to see here!"}' "$RUN_OUT"
+    journalctl --sync
+    journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Censored: Nothing to see here!\)"
+
+    # SERVFAIL + EDE code 0: Other + extra text
+    (! run resolvectl query edns-code-zero.forwarded.test)
+    grep -qE "^edns-code-zero.forwarded.test:.+: SERVFAIL \(Other: 🐱\)" "$RUN_OUT"
+    (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-code-zero.forwarded.test"}')
+    grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+    grep -qF '{"rcode":2,"extendedDNSErrorCode":0,"extendedDNSErrorMessage":"🐱"}' "$RUN_OUT"
+    journalctl --sync
+    journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Other: 🐱\)"
+
+    # SERVFAIL + invalid EDE code
+    (! run resolvectl query edns-invalid-code.forwarded.test)
+    grep -qE "^edns-invalid-code.forwarded.test:.+: SERVFAIL \([0-9]+\)" "$RUN_OUT"
+    (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code.forwarded.test"}')
+    grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+    grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+}' "$RUN_OUT"
+    journalctl --sync
+    journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+\)"
+
+    # SERVFAIL + invalid EDE code + extra text
+    (! run resolvectl query edns-invalid-code-with-extra-text.forwarded.test)
+    grep -qE '^edns-invalid-code-with-extra-text.forwarded.test:.+: SERVFAIL \([0-9]+: Hello \[#\]\$%~ World\)' "$RUN_OUT"
+    (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code-with-extra-text.forwarded.test"}')
+    grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+    grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+,"extendedDNSErrorMessage":"Hello \[#\]\$%~ World"}' "$RUN_OUT"
+    journalctl --sync
+    journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+: Hello \[\#\]\\$%~ World\)"
+}
+
+testcase_09_resolvectl_showcache() {
+    ### Test resolvectl show-cache
+    run resolvectl show-cache
+    run resolvectl show-cache --json=short
+    run resolvectl show-cache --json=pretty
+}
+
+testcase_10_resolvectl_json() {
+    # Issue: https://github.com/systemd/systemd/issues/29580 (part #1)
+    dig @127.0.0.54 signed.test
+
+    systemctl stop resolvectl-monitor.service
+    systemctl stop resolvectl-monitor-json.service
+
+    # Issue: https://github.com/systemd/systemd/issues/29580 (part #2)
     #
-    # The expression is slightly more complicated due to the fact that the "answer" field is optional,
-    # so we need to select it only if it's present, otherwise the type == "array" check would fail
-    echo "$line" | jq -e '[. | .question, (select(has("answer")) | .answer) | type == "array"] | all'
-done
+    # Check for any warnings regarding malformed messages
+    (! journalctl -u resolvectl-monitor.service -u reseolvectl-monitor-json.service -p warning --grep malformed)
+    # Verify that all queries recorded by `resolvectl monitor --json` produced a valid JSON
+    # with expected fields
+    journalctl -p info -o cat _SYSTEMD_UNIT="resolvectl-monitor-json.service" | while read -r line; do
+        # Check that both "question" and "answer" fields are arrays
+        #
+        # The expression is slightly more complicated due to the fact that the "answer" field is optional,
+        # so we need to select it only if it's present, otherwise the type == "array" check would fail
+        echo "$line" | jq -e '[. | .question, (select(has("answer")) | .answer) | type == "array"] | all'
+    done
+}
 
 # Test serve stale feature and NFTSet= if nftables is installed
-if command -v nft >/dev/null; then
+testcase_11_nft() {
+    if ! command -v nft >/dev/null; then
+        echo "nftables is not installed. Skipped serve stale feature and NFTSet= tests."
+        return 0
+    fi
+
     ### Test without serve stale feature ###
     NFT_FILTER_NAME=dns_port_filter
 
@@ -833,70 +879,85 @@ if command -v nft >/dev/null; then
     rm -rf /run/systemd/system/system.slice.d
 
     nft flush ruleset
-else
-    echo "nftables is not installed. Skipped serve stale feature and NFTSet= tests."
-fi
+}
+
+# Test resolvectl show-server-state
+testcase_12_resolvectl2() {
+    run resolvectl show-server-state
+    grep -qF "10.0.0.1" "$RUN_OUT"
+    grep -qF "Interface" "$RUN_OUT"
+
+    run resolvectl show-server-state --json=short
+    grep -qF "10.0.0.1" "$RUN_OUT"
+    grep -qF "Interface" "$RUN_OUT"
+
+    run resolvectl show-server-state --json=pretty
+    grep -qF "10.0.0.1" "$RUN_OUT"
+    grep -qF "Interface" "$RUN_OUT"
+
+    ### Test resolvectl statistics ###
+    run resolvectl statistics
+    grep -qF "Transactions" "$RUN_OUT"
+    grep -qF "Cache" "$RUN_OUT"
+    grep -qF "Failure Transactions" "$RUN_OUT"
+    grep -qF "DNSSEC Verdicts" "$RUN_OUT"
+
+    run resolvectl statistics --json=short
+    grep -qF "transactions" "$RUN_OUT"
+    grep -qF "cache" "$RUN_OUT"
+    grep -qF "dnssec" "$RUN_OUT"
+
+    run resolvectl statistics --json=pretty
+    grep -qF "transactions" "$RUN_OUT"
+    grep -qF "cache" "$RUN_OUT"
+    grep -qF "dnssec" "$RUN_OUT"
+
+    ### Test resolvectl reset-statistics ###
+    run resolvectl reset-statistics
+
+    run resolvectl reset-statistics --json=pretty
+
+    run resolvectl reset-statistics --json=short
+
+    test "$(resolvectl --json=short query -t AAAA localhost)" == '{"key":{"class":1,"type":28,"name":"localhost"},"address":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]}'
+    test "$(resolvectl --json=short query -t A localhost)" == '{"key":{"class":1,"type":1,"name":"localhost"},"address":[127,0,0,1]}'
+
+    # Test ResolveRecord RR resolving via Varlink
+    test "$(varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveRecord '{"name":"localhost","type":1}' --json=short | jq -rc 'del(.rrs | .[] | .ifindex)')" == '{"rrs":[{"rr":{"key":{"class":1,"type":1,"name":"localhost"},"address":[127,0,0,1]},"raw":"CWxvY2FsaG9zdAAAAQABAAAAAAAEfwAAAQ=="}],"flags":786945}'
+    test "$(varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveRecord '{"name":"localhost","type":28}' --json=short | jq -rc 'del(.rrs | .[] | .ifindex)')" == '{"rrs":[{"rr":{"key":{"class":1,"type":28,"name":"localhost"},"address":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]},"raw":"CWxvY2FsaG9zdAAAHAABAAAAAAAQAAAAAAAAAAAAAAAAAAAAAQ=="}],"flags":786945}'
+
+    # Ensure that reloading keeps the manually configured address
+    {
+        echo "[Resolve]"
+        echo "DNS=8.8.8.8"
+    } >/run/systemd/resolved.conf.d/reload.conf
+    resolvectl dns dns0 1.1.1.1
+    systemctl reload systemd-resolved.service
+    resolvectl status
+    resolvectl dns dns0 | grep -qF "1.1.1.1"
+    # For some reason piping this last command to grep fails with:
+    # 'resolvectl[1378]: Failed to print table: Broken pipe'
+    # so use an intermediate file in /tmp/
+    resolvectl >/tmp/output
+    grep -qF "DNS Servers: 8.8.8.8" /tmp/output
+
+    # Check if resolved exits cleanly.
+    restart_resolved
+}
+
+# PRE-SETUP
+systemctl unmask systemd-resolved.service
+systemctl enable --now systemd-resolved.service
+systemctl service-log-level systemd-resolved.service debug
+
+# Need to be run before SETUP, otherwise things will break
+manual_testcase_01_resolvectl
+manual_testcase_02_mdns_llmnr
+
+# Run setup
+setup
 
-### Test resolvectl show-server-state ###
-run resolvectl show-server-state
-grep -qF "10.0.0.1" "$RUN_OUT"
-grep -qF "Interface" "$RUN_OUT"
-
-run resolvectl show-server-state --json=short
-grep -qF "10.0.0.1" "$RUN_OUT"
-grep -qF "Interface" "$RUN_OUT"
-
-run resolvectl show-server-state --json=pretty
-grep -qF "10.0.0.1" "$RUN_OUT"
-grep -qF "Interface" "$RUN_OUT"
-
-### Test resolvectl statistics ###
-run resolvectl statistics
-grep -qF "Transactions" "$RUN_OUT"
-grep -qF "Cache" "$RUN_OUT"
-grep -qF "Failure Transactions" "$RUN_OUT"
-grep -qF "DNSSEC Verdicts" "$RUN_OUT"
-
-run resolvectl statistics --json=short
-grep -qF "transactions" "$RUN_OUT"
-grep -qF "cache" "$RUN_OUT"
-grep -qF "dnssec" "$RUN_OUT"
-
-run resolvectl statistics --json=pretty
-grep -qF "transactions" "$RUN_OUT"
-grep -qF "cache" "$RUN_OUT"
-grep -qF "dnssec" "$RUN_OUT"
-
-### Test resolvectl reset-statistics ###
-run resolvectl reset-statistics
-
-run resolvectl reset-statistics --json=pretty
-
-run resolvectl reset-statistics --json=short
-
-test "$(resolvectl --json=short query -t AAAA localhost)" == '{"key":{"class":1,"type":28,"name":"localhost"},"address":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]}'
-test "$(resolvectl --json=short query -t A localhost)" == '{"key":{"class":1,"type":1,"name":"localhost"},"address":[127,0,0,1]}'
-
-# Test ResolveRecord RR resolving via Varlink
-test "$(varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveRecord '{"name":"localhost","type":1}' --json=short | jq -rc 'del(.rrs | .[] | .ifindex)')" == '{"rrs":[{"rr":{"key":{"class":1,"type":1,"name":"localhost"},"address":[127,0,0,1]},"raw":"CWxvY2FsaG9zdAAAAQABAAAAAAAEfwAAAQ=="}],"flags":786945}'
-test "$(varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveRecord '{"name":"localhost","type":28}' --json=short | jq -rc 'del(.rrs | .[] | .ifindex)')" == '{"rrs":[{"rr":{"key":{"class":1,"type":28,"name":"localhost"},"address":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]},"raw":"CWxvY2FsaG9zdAAAHAABAAAAAAAQAAAAAAAAAAAAAAAAAAAAAQ=="}],"flags":786945}'
-
-# Ensure that reloading keeps the manually configured address
-{
-    echo "[Resolve]"
-    echo "DNS=8.8.8.8"
-} >/run/systemd/resolved.conf.d/reload.conf
-resolvectl dns dns0 1.1.1.1
-systemctl reload systemd-resolved.service
-resolvectl status
-resolvectl dns dns0 | grep -qF "1.1.1.1"
-# For some reason piping this last command to grep fails with:
-# 'resolvectl[1378]: Failed to print table: Broken pipe'
-# so use an intermediate file in /tmp/
-resolvectl >/tmp/output
-grep -qF "DNS Servers: 8.8.8.8" /tmp/output
-
-# Check if resolved exits cleanly.
-restart_resolved
+# Run tests
+run_testcases
 
 touch /testok