Files
openthread/tests/nexus/platform/nexus_dns.cpp
T
Jonathan Hui 05ad9803d8 [nexus] add 1.4 PIC-TC-1 test for DHCPv6-PD and DNS (#12859)
This commit adds a new Nexus test that implements the test
specification in test-1-4-PIC-TC-1.md. The test verifies Border
Router functionality including:
- DHCPv6-PD client to obtain OMR prefix
- Advertising route to OMR prefix on AIL (Stub Router)
- DNS recursive resolver for public internet addresses
- Connectivity (ICMPv6, UDP, TCP/HTTP) to internet and local servers

New files:
- tests/nexus/test_1_4_PIC_TC_1.cpp: C++ test execution
- tests/nexus/verify_1_4_PIC_TC_1.py: Python pcap verification

Nexus platform enhancements:
- Enabled DHCPv6-PD client in openthread-core-nexus-config.h
- Implemented DHCPv6-PD platform APIs in nexus_infra_if.cpp
- Added RDNSS option to RA in nexus_infra_if.cpp
- Improved packet delivery on infrastructure interface in nexus_core.cpp
- Fixed upstream DNS query matching in nexus_dns.cpp
2026-04-09 15:29:35 -05:00

215 lines
7.2 KiB
C++

/*
* Copyright (c) 2026, 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.
*/
/**
* @file
* This file implements the platform DNS APIs for Nexus.
*/
#include "nexus_dns.hpp"
#include <openthread/platform/dns.h>
#include "nexus_core.hpp"
#include "nexus_node.hpp"
#include "border_router/rx_ra_tracker.hpp"
#include "common/as_core_type.hpp"
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "net/dns_types.hpp"
namespace ot {
namespace Nexus {
bool UpstreamDns::IsGua(const Ip6::Prefix &aPrefix) const { return !aPrefix.IsLinkLocal() && !aPrefix.IsUniqueLocal(); }
uint16_t UpstreamDns::CountGuaPrefixes(const Ip6::Address &aRouterAddress) const
{
const BorderRouter::RxRaTracker &rxRaTracker = Get<BorderRouter::RxRaTracker>();
BorderRouter::PrefixTableIterator iter;
BorderRouter::PrefixTableEntry entry;
uint16_t count = 0;
rxRaTracker.InitIterator(iter);
while (rxRaTracker.GetNextPrefixTableEntry(iter, entry) == kErrorNone)
{
if (entry.mValidLifetime > 0 && IsGua(AsCoreType(&entry.mPrefix)) &&
AsCoreType(&entry.mRouter.mAddress) == aRouterAddress)
{
count++;
}
}
return count;
}
Error UpstreamDns::SelectUpstreamDnsServer(Ip6::Address &aSelectedAddress) const
{
const BorderRouter::RxRaTracker &rxRaTracker = Get<BorderRouter::RxRaTracker>();
BorderRouter::PrefixTableIterator iter;
BorderRouter::RdnssAddrEntry entry;
bool found = false;
bool bestHasGua = false;
rxRaTracker.InitIterator(iter);
while (rxRaTracker.GetNextRdnssAddrEntry(iter, entry) == kErrorNone)
{
if (!entry.mRouter.mIsReachable)
{
continue;
}
bool hasGua = (CountGuaPrefixes(AsCoreType(&entry.mRouter.mAddress)) > 0);
if (!found || (hasGua && !bestHasGua))
{
aSelectedAddress = AsCoreType(&entry.mAddress);
bestHasGua = hasGua;
found = true;
}
}
return found ? kErrorNone : kErrorNotFound;
}
UpstreamDns::UpstreamDns(Instance &aInstance)
: InstanceLocator(aInstance)
{
}
void UpstreamDns::StartUpstreamQuery(UpstreamQueryTransaction &aTxn, const Message &aQuery)
{
Error error = kErrorNone;
Node &node = AsNode(&GetInstance());
Ip6::Address serverAddress;
ot::Dns::Header dnsHeader;
PendingQuery *pendingQuery;
Message *message = nullptr;
// We use kDnsPort (53) as both source and destination port for upstream queries to simplify interception
// and response matching in this simulation environment.
SuccessOrExit(error = SelectUpstreamDnsServer(serverAddress));
SuccessOrExit(error = aQuery.Read(0, dnsHeader));
message = aQuery.Clone<kSameReservedHeader>();
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
pendingQuery = PendingQuery::Allocate();
VerifyOrExit(pendingQuery != nullptr, error = kErrorNoBufs);
pendingQuery->mTxn = &aTxn;
pendingQuery->mMessageId = dnsHeader.GetMessageId();
pendingQuery->mServerAddress = serverAddress;
mPendingQueries.Push(*pendingQuery);
node.mInfraIf.SendUdp(node.mInfraIf.SelectSourceAddress(serverAddress), serverAddress, kDnsPort, kDnsPort,
*message);
message = nullptr;
exit:
if (error != kErrorNone)
{
if (message != nullptr)
{
message->Free();
}
otPlatDnsUpstreamQueryDone(&GetInstance(), &aTxn, nullptr);
}
}
void UpstreamDns::CancelUpstreamQuery(UpstreamQueryTransaction &aTxn)
{
mPendingQueries.RemoveAndFreeAllMatching(&aTxn);
}
void UpstreamDns::Reset(void) { mPendingQueries.Clear(); }
bool UpstreamDns::IsUpstreamQueryAvailable(void) const
{
Ip6::Address address;
return SelectUpstreamDnsServer(address) == kErrorNone;
}
bool UpstreamDns::HandleUpstreamDnsResponse(const Ip6::Address &aSrcAddress, Message &aMessage)
{
Instance &instance = GetInstance();
Node &node = AsNode(&instance);
ot::Dns::Header dnsHeader;
OwnedPtr<PendingQuery> query;
bool handled = false;
SuccessOrExit(aMessage.Read(aMessage.GetOffset(), dnsHeader));
query = mPendingQueries.RemoveMatching(dnsHeader.GetMessageId(), aSrcAddress);
if (query != nullptr)
{
// Found matching query
// We use kTypeOther as it is appropriate for a payload-only message.
Message *response = node.Get<MessagePool>().Allocate(Message::kTypeOther);
if (response != nullptr)
{
if (response->AppendBytesFromMessage(aMessage, aMessage.GetOffset(),
aMessage.GetLength() - aMessage.GetOffset()) != kErrorNone)
{
response->Free();
response = nullptr;
}
}
otPlatDnsUpstreamQueryDone(&instance, query->mTxn, response);
handled = true;
}
exit:
return handled;
}
extern "C" bool otPlatDnsIsUpstreamQueryAvailable(otInstance *aInstance)
{
return AsNode(aInstance).mUpstreamDns.IsUpstreamQueryAvailable();
}
extern "C" void otPlatDnsStartUpstreamQuery(otInstance *aInstance,
otPlatDnsUpstreamQuery *aTxn,
const otMessage *aQuery)
{
AsNode(aInstance).mUpstreamDns.StartUpstreamQuery(AsCoreType(aTxn), AsCoreType(aQuery));
}
extern "C" void otPlatDnsCancelUpstreamQuery(otInstance *aInstance, otPlatDnsUpstreamQuery *aTxn)
{
AsNode(aInstance).mUpstreamDns.CancelUpstreamQuery(AsCoreType(aTxn));
}
} // namespace Nexus
} // namespace ot