From a41490ea406cfb7b9ba9ecfc921df4870be7c02a Mon Sep 17 00:00:00 2001 From: Yang Song Date: Mon, 24 Mar 2025 23:29:15 +0800 Subject: [PATCH] [posix] update Posix::Resolver to support RDNSS-discovered recursive DNS servers (#11342) Key Changes: * Server Prioritization: RDNSS-discovered servers are prioritized based on their advertised lifetime. Servers with longer lifetimes are preferred. * Recursive DNS Server List: The resolver maintains a list of recursive DNS servers, which now includes servers learned via RDNSS. * DNS Query Integration: The resolver uses the RDNSS-learned servers when forwarding DNS queries upstream. * Border Routing Integration: The RDNSS callback is registered to the border routing module. Minor Changes: * The unused function `Transaction *GetTransaction(int aFd)` is removed. --- src/posix/platform/platform-posix.h | 9 + src/posix/platform/resolver.cpp | 192 ++++++++++++++---- src/posix/platform/resolver.hpp | 38 +++- src/posix/platform/system.cpp | 4 + .../internet/test_upstream_dns.py | 91 +++++---- tests/scripts/thread-cert/node.py | 22 ++ 6 files changed, 268 insertions(+), 88 deletions(-) diff --git a/src/posix/platform/platform-posix.h b/src/posix/platform/platform-posix.h index 606dc86f0..fa16e103a 100644 --- a/src/posix/platform/platform-posix.h +++ b/src/posix/platform/platform-posix.h @@ -421,6 +421,15 @@ void platformSpinelManagerUpdateFdSet(otSysMainloopContext *aContext); */ void platformResolverInit(void); +/** + * Sets up platform resolver. + * + * @note This function is called after OpenThread instance is created. + * + * @param[in] aInstance A pointer to the OpenThread instance. + */ +void platformResolverSetUp(void); + /** * Updates the file descriptor sets with file descriptors used by the resolver. * diff --git a/src/posix/platform/resolver.cpp b/src/posix/platform/resolver.cpp index 6f417756c..97b43ced9 100644 --- a/src/posix/platform/resolver.cpp +++ b/src/posix/platform/resolver.cpp @@ -27,9 +27,11 @@ */ #include "resolver.hpp" +#include "ip6_utils.hpp" #include "platform-posix.h" +#include #include #include #include @@ -39,6 +41,7 @@ #include #include "common/code_utils.hpp" +#include "common/debug.hpp" #include #include @@ -48,11 +51,14 @@ #include #include +#include #include #include #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE +using namespace ot::Posix::Ip6Utils; + namespace { constexpr char kResolvConfFullPath[] = "/etc/resolv.conf"; constexpr char kNameserverItem[] = "nameserver"; @@ -71,6 +77,15 @@ void Resolver::Init(void) LoadDnsServerListFromConf(); } +void Resolver::Setup(void) +{ + OT_ASSERT(gInstance != nullptr); + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + otBorderRoutingSetRdnssAddrCallback(gInstance, &Resolver::BorderRoutingRdnssCallback, this); +#endif +} + void Resolver::TryRefreshDnsServerList(void) { uint64_t now = otPlatTimeGet(); @@ -129,16 +144,120 @@ exit: return; } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +void Resolver::BorderRoutingRdnssCallback(void *aResolver) +{ + static_cast(aResolver)->BorderRoutingRdnssCallback(); +} + +void Resolver::BorderRoutingRdnssCallback(void) +{ + otBorderRoutingPrefixTableIterator iterator; + otBorderRoutingRdnssAddrEntry entry; + otBorderRoutingRdnssAddrEntry rdnssEntries[kMaxRecursiveServerCount + 1]; + otIp6Address rdnssServers[kMaxRecursiveServerCount]; + uint32_t numEntries = 0; + + otBorderRoutingPrefixTableInitIterator(gInstance, &iterator); + + while (otBorderRoutingGetNextRdnssAddrEntry(gInstance, &iterator, &entry) == OT_ERROR_NONE) + { + uint32_t i = 0; + + // Check if the entry address is already in the list. + for (; i < numEntries; ++i) + { + if (otIp6IsAddressEqual(&entry.mAddress, &rdnssEntries[i].mAddress)) + { + rdnssEntries[i].mLifetime = OT_MAX(rdnssEntries[i].mLifetime, entry.mLifetime); + + break; + } + } + + // If the address is not a duplicate, add the entry to the entry list. + if (i == numEntries) + { + rdnssEntries[numEntries++] = entry; + + std::sort(rdnssEntries, rdnssEntries + numEntries, + [](const otBorderRoutingRdnssAddrEntry &a, const otBorderRoutingRdnssAddrEntry &b) { + bool result = false; + + if (a.mLifetime != b.mLifetime) + { + result = a.mLifetime > b.mLifetime; + } + else + { + // If lifetimes are equal, prefer the one with the larger numeric values + for (uint8_t j = 0; j < sizeof(otIp6Address); j++) + { + if (a.mAddress.mFields.m8[j] != b.mAddress.mFields.m8[j]) + { + result = a.mAddress.mFields.m8[j] > b.mAddress.mFields.m8[j]; + break; + } + } + } + + return result; + }); + + numEntries = OT_MIN(numEntries, kMaxRecursiveServerCount); + } + } + + for (uint32_t i = 0; i < numEntries; i++) + { + rdnssServers[i] = rdnssEntries[i].mAddress; + } + + SetRecursiveDnsServerList(rdnssServers, numEntries); +} +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +otError Resolver::SendQueryToServer(Transaction *aTxn, + const otIp6Address &aServerAddress, + const char *aPacket, + uint16_t aLength) +{ + otError error = OT_ERROR_NONE; + otIp4Address ip4Addr; + sockaddr_in serverAddr4; + sockaddr_in6 serverAddr6; + + if (otIp4FromIp4MappedIp6Address(&aServerAddress, &ip4Addr) == OT_ERROR_NONE) + { + memcpy(&serverAddr4.sin_addr.s_addr, &ip4Addr, sizeof(otIp4Address)); + serverAddr4.sin_family = AF_INET; + serverAddr4.sin_port = htons(53); + + VerifyOrExit(sendto(aTxn->mUdpFd4, aPacket, aLength, MSG_DONTWAIT, reinterpret_cast(&serverAddr4), + sizeof(serverAddr4)) > 0, + error = OT_ERROR_NO_ROUTE); + } + else + { + memcpy(&serverAddr6.sin6_addr, &aServerAddress, sizeof(otIp6Address)); + serverAddr6.sin6_family = AF_INET6; + serverAddr6.sin6_port = htons(53); + + VerifyOrExit(sendto(aTxn->mUdpFd6, aPacket, aLength, MSG_DONTWAIT, reinterpret_cast(&serverAddr6), + sizeof(serverAddr6)) > 0, + error = OT_ERROR_NO_ROUTE); + } + +exit: + return error; +} + void Resolver::Query(otPlatDnsUpstreamQuery *aTxn, const otMessage *aQuery) { char packet[kMaxDnsMessageSize]; otError error = OT_ERROR_NONE; uint16_t length = otMessageGetLength(aQuery); - otIp4Address ip4Addr; - sockaddr_in serverAddr4; - sockaddr_in6 serverAddr6; - - Transaction *txn = nullptr; + Transaction *txn = nullptr; VerifyOrExit(length <= kMaxDnsMessageSize, error = OT_ERROR_NO_BUFS); VerifyOrExit(otMessageRead(aQuery, 0, &packet, sizeof(packet)) == length, error = OT_ERROR_NO_BUFS); @@ -147,32 +266,24 @@ void Resolver::Query(otPlatDnsUpstreamQuery *aTxn, const otMessage *aQuery) VerifyOrExit(txn != nullptr, error = OT_ERROR_NO_BUFS); TryRefreshDnsServerList(); + for (uint32_t i = 0; i < mRecursiveDnsServerCount; i++) + { + SuccessOrExit(error = SendQueryToServer(txn, mRecursiveDnsServerList[i], packet, length)); + + LogInfo("Forwarded DNS query %p to %s", static_cast(aTxn), + Ip6AddressString(&mRecursiveDnsServerList[i]).AsCString()); + } + for (uint32_t i = 0; i < mUpstreamDnsServerCount; i++) { - if (otIp4FromIp4MappedIp6Address(&mUpstreamDnsServerList[i], &ip4Addr) == OT_ERROR_NONE) - { - memcpy(&serverAddr4.sin_addr.s_addr, &ip4Addr, sizeof(otIp4Address)); + SuccessOrExit(error = SendQueryToServer(txn, mUpstreamDnsServerList[i], packet, length)); - serverAddr4.sin_family = AF_INET; - serverAddr4.sin_port = htons(53); - - VerifyOrExit(sendto(txn->mUdpFd4, packet, length, MSG_DONTWAIT, reinterpret_cast(&serverAddr4), - sizeof(serverAddr4)) > 0, - error = OT_ERROR_NO_ROUTE); - } - else - { - memcpy(&serverAddr6.sin6_addr, &mUpstreamDnsServerList[i], sizeof(otIp6Address)); - - serverAddr6.sin6_family = AF_INET6; - serverAddr6.sin6_port = htons(53); - - VerifyOrExit(sendto(txn->mUdpFd6, packet, length, MSG_DONTWAIT, reinterpret_cast(&serverAddr6), - sizeof(serverAddr6)) > 0, - error = OT_ERROR_NO_ROUTE); - } + LogInfo("Forwarded DNS query %p to %s", static_cast(aTxn), + Ip6AddressString(&mUpstreamDnsServerList[i]).AsCString()); } - LogInfo("Forwarded DNS query %p to %d server(s).", static_cast(aTxn), mUpstreamDnsServerCount); + + LogInfo("Forwarded DNS query %p to %d server(s).", static_cast(aTxn), + mRecursiveDnsServerCount + mUpstreamDnsServerCount); exit: if (error != OT_ERROR_NONE) @@ -184,7 +295,6 @@ exit: CloseTransaction(txn); } } - return; } void Resolver::Cancel(otPlatDnsUpstreamQuery *aTxn) @@ -265,22 +375,6 @@ exit: } } -Resolver::Transaction *Resolver::GetTransaction(int aFd) -{ - Transaction *ret = nullptr; - - for (Transaction &txn : mUpstreamTransaction) - { - if (txn.mThreadTxn != nullptr && (txn.mUdpFd4 == aFd || txn.mUdpFd6 == aFd)) - { - ret = &txn; - break; - } - } - - return ret; -} - Resolver::Transaction *Resolver::GetTransaction(otPlatDnsUpstreamQuery *aThreadTxn) { Transaction *ret = nullptr; @@ -364,6 +458,14 @@ void Resolver::SetUpstreamDnsServers(const otIp6Address *aUpstreamDnsServers, ui LogInfo("Set upstream DNS server list, count: %d", mUpstreamDnsServerCount); } +void Resolver::SetRecursiveDnsServerList(const otIp6Address *aRecursiveDnsServers, uint32_t aNumServers) +{ + mRecursiveDnsServerCount = OT_MIN(aNumServers, static_cast(kMaxRecursiveServerCount)); + memcpy(mRecursiveDnsServerList, aRecursiveDnsServers, mRecursiveDnsServerCount * sizeof(otIp6Address)); + + LogInfo("Set recursive DNS server list, count: %d", mRecursiveDnsServerCount); +} + int Resolver::CreateUdpSocket(sa_family_t aFamily) { int fd = -1; @@ -392,6 +494,8 @@ void platformResolverProcess(const otSysMainloopContext *aContext) { gResolver.P void platformResolverUpdateFdSet(otSysMainloopContext *aContext) { gResolver.UpdateFdSet(*aContext); } +void platformResolverSetUp(void) { gResolver.Setup(); } + void platformResolverInit(void) { gResolver.Init(); } void otPlatDnsStartUpstreamQuery(otInstance *aInstance, otPlatDnsUpstreamQuery *aTxn, const otMessage *aQuery) diff --git a/src/posix/platform/resolver.hpp b/src/posix/platform/resolver.hpp index db63ba1ac..a6517d63b 100644 --- a/src/posix/platform/resolver.hpp +++ b/src/posix/platform/resolver.hpp @@ -50,12 +50,20 @@ public: constexpr static ssize_t kMaxDnsMessageSize = 512; constexpr static ssize_t kMaxUpstreamTransactionCount = 16; constexpr static ssize_t kMaxUpstreamServerCount = 3; + constexpr static ssize_t kMaxRecursiveServerCount = 3; /** * Initialize the upstream DNS resolver. */ void Init(void); + /** + * Sets up the upstream DNS resolver. + * + * @note This method is called after OpenThread instance is created. + */ + void Setup(void); + /** * Sends the query to the upstream. * @@ -101,6 +109,15 @@ public: */ void SetUpstreamDnsServers(const otIp6Address *aUpstreamDnsServers, uint32_t aNumServers); + /** + * Sets the list of recursive DNS servers. + * + * @param[in] aRecursiveDnsServers A pointer to the list of IPv6 recursive DNS server addresses. + * @param[in] aNumServers The number of recursive DNS servers. + * + */ + void SetRecursiveDnsServerList(const otIp6Address *aRecursiveDnsServers, uint32_t aNumServers); + private: static constexpr uint64_t kDnsServerListNullCacheTimeoutMs = 1 * 60 * 1000; // 1 minute static constexpr uint64_t kDnsServerListCacheTimeoutMs = 10 * 60 * 1000; // 10 minutes @@ -114,20 +131,31 @@ private: static int CreateUdpSocket(sa_family_t aFamily); - Transaction *GetTransaction(int aFd); Transaction *GetTransaction(otPlatDnsUpstreamQuery *aThreadTxn); Transaction *AllocateTransaction(otPlatDnsUpstreamQuery *aThreadTxn); - void ForwardResponse(otPlatDnsUpstreamQuery *aThreadTxn, int aFd); - void CloseTransaction(Transaction *aTxn); - void TryRefreshDnsServerList(void); - void LoadDnsServerListFromConf(void); + otError SendQueryToServer(Transaction *aTxn, + const otIp6Address &aServerAddress, + const char *aPacket, + uint16_t aLength); + void ForwardResponse(otPlatDnsUpstreamQuery *aThreadTxn, int aFd); + void CloseTransaction(Transaction *aTxn); + void TryRefreshDnsServerList(void); + void LoadDnsServerListFromConf(void); + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + static void BorderRoutingRdnssCallback(void *aResolver); + void BorderRoutingRdnssCallback(void); +#endif bool mIsResolvConfEnabled = OPENTHREAD_POSIX_CONFIG_RESOLV_CONF_ENABLED_INIT; uint32_t mUpstreamDnsServerCount = 0; otIp6Address mUpstreamDnsServerList[kMaxUpstreamServerCount]; uint64_t mUpstreamDnsServerListFreshness = 0; + uint32_t mRecursiveDnsServerCount = 0; + otIp6Address mRecursiveDnsServerList[kMaxRecursiveServerCount]; + Transaction mUpstreamTransaction[kMaxUpstreamTransactionCount]; }; diff --git a/src/posix/platform/system.cpp b/src/posix/platform/system.cpp index 735c70c30..799bc4d79 100644 --- a/src/posix/platform/system.cpp +++ b/src/posix/platform/system.cpp @@ -237,6 +237,10 @@ void platformSetUp(otPlatformConfig *aPlatformConfig) platformNetifSetUp(); #endif +#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE + platformResolverSetUp(); +#endif + #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE ot::Posix::Udp::Get().SetUp(); #endif diff --git a/tests/scripts/thread-cert/border_router/internet/test_upstream_dns.py b/tests/scripts/thread-cert/border_router/internet/test_upstream_dns.py index 238f32b12..c93f35cb9 100755 --- a/tests/scripts/thread-cert/border_router/internet/test_upstream_dns.py +++ b/tests/scripts/thread-cert/border_router/internet/test_upstream_dns.py @@ -39,29 +39,23 @@ import shlex # a record resolved by BIND9 server. # # Topology: -# ----------------(eth)-------------------- -# | | -# BR (Leader) DNS SERVER +# ----------------(eth)---------------------------------- +# | | | +# BR (Leader) DNS SERVER 1 DNS SERVER 2 # | # ROUTER # BR = 1 ROUTER = 2 -DNS_SERVER = 3 +DNS_SERVER_1 = 3 +DNS_SERVER_2 = 4 -TEST_DOMAIN = 'test.domain' -TEST_DOMAIN_IP6_ADDRESSES = {'2001:db8::1'} +TEST_DOMAIN_1 = 'test.domain.resolv' +TEST_DOMAIN_IP6_ADDRESSES_1 = {'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) +TEST_DOMAIN_2 = 'test.domain.rdnss' +TEST_DOMAIN_IP6_ADDRESSES_2 = {'2001:db8::2'} class UpstreamDns(thread_cert.TestCase): @@ -77,8 +71,12 @@ class UpstreamDns(thread_cert.TestCase): 'name': 'Router', 'version': '1.4', }, - DNS_SERVER: { - 'name': 'DNS Server', + DNS_SERVER_1: { + 'name': 'DNS Server 1', + 'is_host': True + }, + DNS_SERVER_2: { + 'name': 'DNS Server 2', 'is_host': True }, } @@ -86,19 +84,15 @@ class UpstreamDns(thread_cert.TestCase): def test(self): br = self.nodes[BR] router = self.nodes[ROUTER] - dns_server = self.nodes[DNS_SERVER] + dns_server_1 = self.nodes[DNS_SERVER_1] + dns_server_2 = self.nodes[DNS_SERVER_2] - self._start_dns_server(dns_server) - dns_server_addr = dns_server.get_ether_addrs(ipv4=True, ipv6=False)[0] + self._start_dns_server(dns_server_1, TEST_DOMAIN_1, TEST_DOMAIN_IP6_ADDRESSES_1) + self._start_dns_server(dns_server_2, TEST_DOMAIN_2, TEST_DOMAIN_IP6_ADDRESSES_2) # Disable the bind9 service on the BR otherwise bind9 may respond to Thread devices' DNS queries br.bash('service bind9 stop') - # Update BR's /etc/resolv.conf and force BR to reload it - br.bash(shlex.join(['echo', 'nameserver ' + dns_server_addr]) + ' > /etc/resolv.conf') - br.stop_otbr_service() - br.start_otbr_service() - br.start() self.simulator.go(config.LEADER_STARTUP_DELAY) self.assertEqual('leader', br.get_state()) @@ -115,26 +109,45 @@ class UpstreamDns(thread_cert.TestCase): 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 + 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) + # Update BR's /etc/resolv.conf with the address of DNS server 1 and force BR to reload it + dns_server_addr_1 = dns_server_1.get_ether_addrs(ipv4=True, ipv6=False)[0] + br.bash(shlex.join(['echo', 'nameserver ' + dns_server_addr_1]) + ' > /etc/resolv.conf') + br.stop_otbr_service() + br.start_otbr_service() + + # Publish RDNSS with the address of DNS server 2 through RA + dns_server_addr_2 = dns_server_2.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0] + br.start_rdnss_radvd_service(dns_server_addr_2) + + self.simulator.go(10) + # verify the server can forward the DNS query to the two upstream servers. + resolved_addresses_1 = set( + ipaddress.IPv6Address(record[0]).compressed for record in router.dns_resolve(TEST_DOMAIN_1)) + self.assertEqual(resolved_addresses_1, TEST_DOMAIN_IP6_ADDRESSES_1) + + resolved_addresses_2 = set( + ipaddress.IPv6Address(record[0]).compressed for record in router.dns_resolve(TEST_DOMAIN_2)) + self.assertEqual(resolved_addresses_2, TEST_DOMAIN_IP6_ADDRESSES_2) + + def _start_dns_server(self, dns_server, test_domain, test_domain_ip6_addresses): + 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) - def _start_dns_server(self, dns_server): dns_server.start(start_radvd=False) dns_server.bash('service bind9 stop') - dns_server.bash(shlex.join(['echo', TEST_DOMAIN_BIND_CONF]) + ' >> /etc/bind/named.conf.local') - dns_server.bash(shlex.join(['echo', TEST_DOMAIN_BIND_ZONE]) + ' >> /etc/bind/db.test.domain') + dns_server.bash(shlex.join(['echo', test_domain_bind_conf]) + ' >> /etc/bind/named.conf.local') + dns_server.bash(shlex.join(['echo', test_domain_bind_zone]) + ' >> /etc/bind/db.test.domain') dns_server.bash('service bind9 start') diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index d4bf71b6c..7d45f9268 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -4145,6 +4145,28 @@ EOF """ % (prefix,)) self._start_radvd_and_verify() + def start_rdnss_radvd_service(self, dns_server_address): + self.bash(f"""cat >/etc/radvd.conf <