From fa6824c9cbe57d03fcec3aacece5a1daebef36df Mon Sep 17 00:00:00 2001 From: Song GUO Date: Fri, 31 Mar 2023 06:06:55 +0800 Subject: [PATCH] [test] add tests for DNS forwarding (#8847) This commit adds a new file `tests/scripts/thread-cert/border_router/nat64/test_upstream_dns.py` to test scripts for testing the DNS forwarding functions. The test adds a record `test.domain` to the bind9 interface since when adding to DNSSD tests, the OpenThread's DNSSD server will be conflict with the Docker's DNS forwarding functions. --- script/test | 7 +- src/posix/platform/resolver.cpp | 1 + .../border_router/nat64/test_upstream_dns.py | 135 ++++++++++++++++++ .../test_dnssd_instance_name_with_space.py | 1 + .../border_router/test_dnssd_server.py | 1 + .../test_dnssd_server_multi_border_routers.py | 2 + tests/scripts/thread-cert/node.py | 16 ++- 7 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 tests/scripts/thread-cert/border_router/nat64/test_upstream_dns.py diff --git a/script/test b/script/test index 9c5e6886a..d1e04fee0 100755 --- a/script/test +++ b/script/test @@ -321,8 +321,8 @@ do_build_otbr_docker() "-DOT_SRP_CLIENT=ON" "-DOT_FULL_LOGS=ON" "-DOT_UPTIME=ON" + "-DOTBR_DNS_UPSTREAM_QUERY=ON" "-DOTBR_DUA_ROUTING=ON" - "-DCMAKE_CXX_FLAGS='-DOPENTHREAD_CONFIG_DNSSD_SERVER_BIND_UNSPECIFIED_NETIF=1'" ) local args=( "BORDER_ROUTING=${BORDER_ROUTING}" @@ -338,6 +338,11 @@ do_build_otbr_docker() "MDNS=${OTBR_MDNS:-mDNSResponder}" ) + if [[ ${NAT64} != 1 ]]; then + # We are testing upstream DNS forwarding in the NAT64 tests, and OPENTHREAD_CONFIG_DNSSD_SERVER_BIND_UNSPECIFIED_NETIF will block OpenThread's DNSSD server since we already have bind9 running. + otbr_options+=("-DCMAKE_CXX_FLAGS='-DOPENTHREAD_CONFIG_DNSSD_SERVER_BIND_UNSPECIFIED_NETIF=1'") + fi + if [[ ${TREL} == 1 ]]; then otbr_options+=("-DOTBR_TREL=ON") else diff --git a/src/posix/platform/resolver.cpp b/src/posix/platform/resolver.cpp index 6528ea450..cc7b0b972 100644 --- a/src/posix/platform/resolver.cpp +++ b/src/posix/platform/resolver.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include diff --git a/tests/scripts/thread-cert/border_router/nat64/test_upstream_dns.py b/tests/scripts/thread-cert/border_router/nat64/test_upstream_dns.py new file mode 100644 index 000000000..617ea0f19 --- /dev/null +++ b/tests/scripts/thread-cert/border_router/nat64/test_upstream_dns.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +import unittest + +import config +import thread_cert + +import ipaddress +import shlex + +# Test description: +# This test verifies forwarding DNS queries sent by 'Router' by using +# a record resolved by BIND9 server. +# +# Topology: +# ----------------(eth)-------------------- +# | | +# BR (Leader) HOST +# | +# ROUTER +# + +BR = 1 +ROUTER = 2 +HOST = 3 + +TEST_DOMAIN = 'test.domain' +TEST_DOMAIN_IP6_ADDRESSES = {'2001:db8::1'} + +TEST_DOMAIN_BIND_CONF = f''' +zone "{TEST_DOMAIN}" {{ type master; file "/etc/bind/db.test.domain"; }}; +''' + +TEST_DOMAIN_BIND_ZONE = f''' +$TTL 24h +@ IN SOA {TEST_DOMAIN} test.{TEST_DOMAIN}. ( 20230330 86400 300 604800 3600 ) +@ IN NS {TEST_DOMAIN}. +''' + '\n'.join(f'@ IN AAAA {addr}' for addr in TEST_DOMAIN_IP6_ADDRESSES) + + +class UpstreamDns(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + TOPOLOGY = { + BR: { + 'name': 'BR', + 'allowlist': [ROUTER], + 'is_otbr': True, + 'version': '1.3', + }, + ROUTER: { + 'name': 'Router', + 'allowlist': [BR], + 'version': '1.3', + }, + HOST: { + 'name': 'Host', + 'is_host': True + }, + } + + def test(self): + br = self.nodes[BR] + router = self.nodes[ROUTER] + host = self.nodes[HOST] + + host.start(start_radvd=False) + self.simulator.go(5) + + br.start() + # When feature flag is enabled, NAT64 might be disabled by default. So + # ensure NAT64 is enabled here. + self.simulator.go(config.LEADER_STARTUP_DELAY) + self.assertEqual('leader', br.get_state()) + + br.nat64_set_enabled(True) + br.srp_server_set_enabled(True) + + br.bash('service bind9 stop') + + br.bash(shlex.join(['echo', TEST_DOMAIN_BIND_CONF]) + ' >> /etc/bind/named.conf.local') + br.bash(shlex.join(['echo', TEST_DOMAIN_BIND_ZONE]) + ' >> /etc/bind/db.test.domain') + + br.bash('service bind9 start') + + router.start() + self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.assertEqual('router', router.get_state()) + + self.simulator.go(10) + router.srp_client_enable_auto_start_mode() + + # verify the server can forward the DNS query to upstream server. + self._verify_upstream_dns(br, router) + + def _verify_upstream_dns(self, br, ed): + upstream_dns_enabled = br.dns_upstream_query_state + if not upstream_dns_enabled: + br.dns_upstream_query_state = True + self.assertTrue(br.dns_upstream_query_state) + + resolved_names = ed.dns_resolve(TEST_DOMAIN) + self.assertEqual(len(resolved_names), len(TEST_DOMAIN_IP6_ADDRESSES)) + for record in resolved_names: + self.assertIn(ipaddress.IPv6Address(record[0]).compressed, TEST_DOMAIN_IP6_ADDRESSES) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/border_router/test_dnssd_instance_name_with_space.py b/tests/scripts/thread-cert/border_router/test_dnssd_instance_name_with_space.py index 7c2cc9985..720b7bc36 100644 --- a/tests/scripts/thread-cert/border_router/test_dnssd_instance_name_with_space.py +++ b/tests/scripts/thread-cert/border_router/test_dnssd_instance_name_with_space.py @@ -93,6 +93,7 @@ class TestDnssdInstanceNameWithSpace(thread_cert.TestCase): self.simulator.go(config.LEADER_STARTUP_DELAY) self.assertEqual('leader', br1.get_state()) server.srp_server_set_enabled(True) + br1.dns_upstream_query_state = False br2.start() self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY) diff --git a/tests/scripts/thread-cert/border_router/test_dnssd_server.py b/tests/scripts/thread-cert/border_router/test_dnssd_server.py index 471c83ee2..d31bf0725 100644 --- a/tests/scripts/thread-cert/border_router/test_dnssd_server.py +++ b/tests/scripts/thread-cert/border_router/test_dnssd_server.py @@ -97,6 +97,7 @@ class TestDnssdServerOnBr(thread_cert.TestCase): self.simulator.go(config.LEADER_STARTUP_DELAY) self.assertEqual('leader', br1.get_state()) server.srp_server_set_enabled(True) + server.dns_upstream_query_state = False client1.start() diff --git a/tests/scripts/thread-cert/border_router/test_dnssd_server_multi_border_routers.py b/tests/scripts/thread-cert/border_router/test_dnssd_server_multi_border_routers.py index 3a09b726d..46178480b 100644 --- a/tests/scripts/thread-cert/border_router/test_dnssd_server_multi_border_routers.py +++ b/tests/scripts/thread-cert/border_router/test_dnssd_server_multi_border_routers.py @@ -114,6 +114,7 @@ class TestDnssdServerOnMultiBr(thread_cert.TestCase): self.simulator.go(config.LEADER_STARTUP_DELAY) self.assertEqual('leader', br1.get_state()) br1.srp_server_set_enabled(True) + br1.dns_upstream_query_state = False br2.stop_mdns_service() br2.stop_otbr_service() @@ -141,6 +142,7 @@ class TestDnssdServerOnMultiBr(thread_cert.TestCase): br2.start() self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY) + br2.dns_upstream_query_state = False br2_addr = br2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0] diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 96975cead..96a626eec 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -112,7 +112,7 @@ class OtbrDocker: logging.info(f'Docker image: {config.OTBR_DOCKER_IMAGE}') subprocess.check_call(f"docker rm -f {self._docker_name} || true", shell=True) CI_ENV = os.getenv('CI_ENV', '').split() - dns = ['--dns=127.0.0.1'] if INFRA_DNS64 == 1 else [] + dns = ['--dns=127.0.0.1'] if INFRA_DNS64 == 1 else ['--dns=8.8.8.8'] nat64_prefix = ['--nat64-prefix', '2001:db8:1:ffff::/96'] if INFRA_DNS64 == 1 else [] os.makedirs('/tmp/coverage/', exist_ok=True) @@ -376,6 +376,10 @@ class OtbrDocker: return self.call_dbus_method('org.freedesktop.DBus.Properties', 'Get', 'io.openthread.BorderRouter', property_name) + def set_dbus_property(self, property_name, property_value): + return self.call_dbus_method('org.freedesktop.DBus.Properties', 'Set', 'io.openthread.BorderRouter', + property_name, property_value) + def get_border_routing_counters(self): counters = self.get_dbus_property('BorderRoutingCounters') counters = { @@ -460,6 +464,16 @@ class OtbrDocker: 'TCP': self._process_traffic_counters(res[3]), } + @property + def dns_upstream_query_state(self): + return bool(self.get_dbus_property('DnsUpstreamQueryState')) + + @dns_upstream_query_state.setter + def dns_upstream_query_state(self, value): + if type(value) is not bool: + raise ValueError("dns_upstream_query_state must be a bool") + return self.set_dbus_property('DnsUpstreamQueryState', value) + def read_border_routing_counters_delta(self): old_counters = self._border_routing_counters new_counters = self.get_border_routing_counters()