[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.
This commit is contained in:
Yang Song
2025-03-24 08:29:15 -07:00
committed by GitHub
parent a8fef54bf6
commit a41490ea40
6 changed files with 268 additions and 88 deletions
+9
View File
@@ -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.
*
+148 -44
View File
@@ -27,9 +27,11 @@
*/
#include "resolver.hpp"
#include "ip6_utils.hpp"
#include "platform-posix.h"
#include <openthread/border_routing.h>
#include <openthread/logging.h>
#include <openthread/message.h>
#include <openthread/nat64.h>
@@ -39,6 +41,7 @@
#include <openthread/platform/time.h>
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include <arpa/inet.h>
#include <arpa/nameser.h>
@@ -48,11 +51,14 @@
#include <sys/socket.h>
#include <unistd.h>
#include <algorithm>
#include <fstream>
#include <string>
#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<Resolver *>(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<sockaddr *>(&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<sockaddr *>(&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<void *>(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<sockaddr *>(&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<sockaddr *>(&serverAddr6),
sizeof(serverAddr6)) > 0,
error = OT_ERROR_NO_ROUTE);
}
LogInfo("Forwarded DNS query %p to %s", static_cast<void *>(aTxn),
Ip6AddressString(&mUpstreamDnsServerList[i]).AsCString());
}
LogInfo("Forwarded DNS query %p to %d server(s).", static_cast<void *>(aTxn), mUpstreamDnsServerCount);
LogInfo("Forwarded DNS query %p to %d server(s).", static_cast<void *>(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<uint32_t>(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)
+33 -5
View File
@@ -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];
};
+4
View File
@@ -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
@@ -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')
+22
View File
@@ -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 <<EOF
interface eth0
{{
AdvSendAdvert on;
AdvReachableTime 20;
AdvRetransTimer 20;
AdvDefaultLifetime 180;
MinRtrAdvInterval 120;
MaxRtrAdvInterval 180;
AdvDefaultPreference low;
RDNSS {dns_server_address}
{{
AdvRDNSSLifetime 1800;
}};
}};
EOF
""")
self._start_radvd_and_verify()
def stop_radvd_service(self):
self.bash('service radvd stop')