Files
openthread/tests/nexus/test_1_4_DNS_TC_5.cpp
Abtin Keshavarzian 2a2d4be953 [ip6] rename methods fully initializing an Ip6::Address/Prefix (#13169)
This commit renames several methods in `Ip6::Address`,
`Ip6::InterfaceIdentifier`, `Ip6::Prefix`, and `Ip4::Address` that
fully initialize the object from `Set...()` to `Init...()`.

This creates a clear semantic distinction in the API:
- `Init...()`: Fully (re-)initializing the object.
- `Set...()`: Modifies a specific property or a sub-component of
   the object (e.g., `SetPrefix()`,  `SetLocator()`,
   `SetSubnetId()`).

Some examples of renames include:
- `SetFromExtAddress()` -> `InitFromExtAddress()`
- `SetToLocator()` -> `InitAsLocator()`
- `SetToLinkLocalAddress()` -> `InitAsLinkLocalAddress()`
- `SetToRoutingLocator()` -> `InitAsRoutingLocator()`
- `SetToAnycastLocator()` -> `InitAsAnycastLocator()`
- `SetToIp4Mapped()` -> `InitAsIp4Mapped()`

All calls to these methods across the codebase have been updated
to reflect the new names.
2026-05-28 20:52:05 -07:00

525 lines
20 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.
*/
#include "nexus_core.hpp"
#include "nexus_node.hpp"
namespace ot {
namespace Nexus {
namespace {
static constexpr uint32_t kFormNetworkTime = 10 * 1000;
static constexpr uint32_t kJoinNetworkTime = 20 * 1000;
static constexpr uint32_t kStabilizationTime = 10 * 1000;
static constexpr char kGuaPrefix[] = "2005:1234:abcd:100::/64";
static constexpr char kEth1Addr[] = "2005:1234:abcd:100::E1";
static constexpr char kBr1AilAddr[] = "2005:1234:abcd:100::B1";
static constexpr char kThreadGroup1[] = "threadgroup1.org";
static constexpr char kThreadGroup2[] = "threadgroup2.org";
static constexpr char kThreadGroup1Aaaa[] = "2002:1234::E1:1";
static constexpr char kThreadGroup1A[] = "192.168.217.1";
static constexpr char kThreadGroup2A[] = "192.168.217.2";
static constexpr uint16_t kTypePrivateUse = 65285; // 0xFF05
static constexpr uint32_t kPrivateData = 0x12345678;
static Node *sEth1Node = nullptr;
bool HandleUdpHook(Instance &aInstance, Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Node &node = AsNode(&aInstance);
Dns::Header dnsHeader;
char name[Dns::Name::kMaxNameSize];
uint16_t readPos;
uint16_t qType;
uint16_t qClass;
Message *response;
bool handled = false;
VerifyOrExit(&node == sEth1Node);
VerifyOrExit(aMessageInfo.GetSockPort() == UpstreamDns::kDnsPort);
SuccessOrExit(aMessage.Read(aMessage.GetOffset(), dnsHeader));
VerifyOrExit(dnsHeader.GetType() == Dns::Header::kTypeQuery);
VerifyOrExit(dnsHeader.GetQuestionCount() == 1);
readPos = aMessage.GetOffset() + sizeof(dnsHeader);
SuccessOrExit(Dns::Name::ReadName(aMessage, readPos, name));
SuccessOrExit(aMessage.Read(readPos, qType));
qType = BigEndian::HostSwap16(qType);
readPos += sizeof(uint16_t);
SuccessOrExit(aMessage.Read(readPos, qClass));
qClass = BigEndian::HostSwap16(qClass);
readPos += sizeof(uint16_t);
Log("Eth_1 received DNS query for '%s' (type %u)", name, qType);
response = node.Get<MessagePool>().Allocate(Message::kTypeIp6);
VerifyOrExit(response != nullptr);
dnsHeader.SetType(Dns::Header::kTypeResponse);
dnsHeader.SetAnswerCount(0);
dnsHeader.SetRecursionDesiredFlag();
dnsHeader.SetRecursionAvailableFlag();
if (Dns::Name::IsSameDomain(name, kThreadGroup1))
{
if (qType == Dns::ResourceRecord::kTypeAaaa)
{
Ip6::Address address;
Dns::ResourceRecord rr;
SuccessOrQuit(address.FromString(kThreadGroup1Aaaa));
dnsHeader.SetAnswerCount(1);
SuccessOrQuit(response->Append(dnsHeader));
SuccessOrQuit(Dns::Name::AppendName(name, *response));
SuccessOrQuit(response->Append(BigEndian::HostSwap16(qType)));
SuccessOrQuit(response->Append(BigEndian::HostSwap16(qClass)));
SuccessOrQuit(Dns::Name::AppendName(name, *response));
rr.Init(Dns::ResourceRecord::kTypeAaaa);
rr.SetTtl(1800);
rr.SetLength(sizeof(Ip6::Address));
SuccessOrQuit(response->Append(rr));
SuccessOrQuit(response->Append(address));
Log("Eth_1 sending AAAA response for %s: %s", name, kThreadGroup1Aaaa);
}
else if (qType == Dns::ResourceRecord::kTypeA)
{
Ip4::Address address;
Dns::ResourceRecord rr;
SuccessOrQuit(address.FromString(kThreadGroup1A));
dnsHeader.SetAnswerCount(1);
SuccessOrQuit(response->Append(dnsHeader));
SuccessOrQuit(Dns::Name::AppendName(name, *response));
SuccessOrQuit(response->Append(BigEndian::HostSwap16(qType)));
SuccessOrQuit(response->Append(BigEndian::HostSwap16(qClass)));
SuccessOrQuit(Dns::Name::AppendName(name, *response));
rr.Init(Dns::ResourceRecord::kTypeA);
rr.SetTtl(1800);
rr.SetLength(sizeof(Ip4::Address));
SuccessOrQuit(response->Append(rr));
SuccessOrQuit(response->Append(address));
Log("Eth_1 sending A response for %s: %s", name, kThreadGroup1A);
}
}
else if (Dns::Name::IsSameDomain(name, kThreadGroup2))
{
if (qType == Dns::ResourceRecord::kTypeA)
{
Ip4::Address address;
Dns::ResourceRecord rr;
SuccessOrQuit(address.FromString(kThreadGroup2A));
dnsHeader.SetAnswerCount(1);
SuccessOrQuit(response->Append(dnsHeader));
SuccessOrQuit(Dns::Name::AppendName(name, *response));
SuccessOrQuit(response->Append(BigEndian::HostSwap16(qType)));
SuccessOrQuit(response->Append(BigEndian::HostSwap16(qClass)));
SuccessOrQuit(Dns::Name::AppendName(name, *response));
rr.Init(Dns::ResourceRecord::kTypeA);
rr.SetTtl(1800);
rr.SetLength(sizeof(Ip4::Address));
SuccessOrQuit(response->Append(rr));
SuccessOrQuit(response->Append(address));
Log("Eth_1 sending A response for %s: %s", name, kThreadGroup2A);
}
else if (qType == kTypePrivateUse)
{
Dns::ResourceRecord rr;
uint32_t data = BigEndian::HostSwap32(kPrivateData);
dnsHeader.SetAnswerCount(1);
SuccessOrQuit(response->Append(dnsHeader));
SuccessOrQuit(Dns::Name::AppendName(name, *response));
SuccessOrQuit(response->Append(BigEndian::HostSwap16(qType)));
SuccessOrQuit(response->Append(BigEndian::HostSwap16(qClass)));
SuccessOrQuit(Dns::Name::AppendName(name, *response));
rr.Init(kTypePrivateUse);
rr.SetTtl(1800);
rr.SetLength(sizeof(uint32_t));
SuccessOrQuit(response->Append(rr));
SuccessOrQuit(response->Append(data));
Log("Eth_1 sending PrivateUse response for %s", name);
}
}
if (dnsHeader.GetAnswerCount() == 0)
{
Log("Eth_1 sending empty response for %s", name);
SuccessOrQuit(response->Append(dnsHeader));
SuccessOrQuit(Dns::Name::AppendName(name, *response));
SuccessOrQuit(response->Append(BigEndian::HostSwap16(qType)));
SuccessOrQuit(response->Append(BigEndian::HostSwap16(qClass)));
}
node.mInfraIf.SendUdp(aMessageInfo.GetSockAddr(), aMessageInfo.GetPeerAddr(), UpstreamDns::kDnsPort,
aMessageInfo.GetPeerPort(), *response);
handled = true;
exit:
return handled;
}
void HandleDnsResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext)
{
OT_UNUSED_VARIABLE(aError);
OT_UNUSED_VARIABLE(aResponse);
OT_UNUSED_VARIABLE(aContext);
}
void HandleDnsRecordResponse(otError aError, const otDnsRecordResponse *aResponse, void *aContext)
{
OT_UNUSED_VARIABLE(aError);
OT_UNUSED_VARIABLE(aResponse);
OT_UNUSED_VARIABLE(aContext);
}
void SendQuery(Node &aNode, const char *aName, uint16_t aType)
{
if (aType == Dns::ResourceRecord::kTypeAaaa)
{
SuccessOrQuit(aNode.Get<Dns::Client>().ResolveAddress(aName, HandleDnsResponse, nullptr));
}
else
{
SuccessOrQuit(aNode.Get<Dns::Client>().QueryRecord(aType, nullptr, aName, HandleDnsRecordResponse, nullptr));
}
}
void SendRa(Node &aNode, const Ip6::Address &aDnsAddr, const Ip6::Prefix &aPrefix)
{
Ip6::Nd::RouterAdvert::TxMessage ra;
Ip6::Nd::RouterAdvert::Header header;
Ip6::Nd::Icmp6Packet packet;
header.SetToDefault();
header.SetRouterLifetime(1800);
SuccessOrQuit(ra.Append(header));
SuccessOrQuit(ra.AppendPrefixInfoOption(
aPrefix, 1800, 1800, Ip6::Nd::PrefixInfoOption::kOnLinkFlag | Ip6::Nd::PrefixInfoOption::kAutoConfigFlag));
SuccessOrQuit(ra.AppendRecursiveDnsServerOption(&aDnsAddr, 1, 1800));
ra.GetAsPacket(packet);
aNode.mInfraIf.SendIcmp6Nd(Ip6::Address::GetLinkLocalAllNodesMulticast(), packet.GetBytes(), packet.GetLength());
}
} // namespace
/**
* 11.5. [1.4] [CERT] DNS record types and special cases
*
* 11.5.1. Purpose
* To verify that the BR DUT:
* - Can resolve A records
* - Can resolve A records without doing IPv6 AAAA synthesis
* - Can resolve AAAA records
* - Can resolve records contributed by different sources (on-mesh, on-AIL, and upstream DNS server)
* - Supports non-typical record types (RRTypes) for queries
* - Supports queries using RR type 0xFF00-0xFFFE (65280-65534) “Private Use”
* - Blocks the “ipv4only.arpa” query
*/
void Test_1_4_DNS_TC_5(const char *aJsonFileName)
{
Core nexus;
Node &br1 = nexus.CreateNode();
Node &ed1 = nexus.CreateNode();
Node &eth1 = nexus.CreateNode();
Ip6::Address eth1Addr;
Ip6::Address br1AilAddr;
Ip6::Prefix guaPrefix;
br1.SetName("BR", 1);
ed1.SetName("ED", 1);
eth1.SetName("Eth", 1);
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote));
sEth1Node = &eth1;
eth1.mInfraIf.SetUdpHook(HandleUdpHook);
AllowLinkBetween(br1, ed1);
SuccessOrQuit(eth1Addr.FromString(kEth1Addr));
SuccessOrQuit(br1AilAddr.FromString(kBr1AilAddr));
SuccessOrQuit(guaPrefix.FromString(kGuaPrefix));
/**
* Step 1
* Device: Eth_1, BR_1, ED_1
* Description (DNS-11.5): Form topology. Thread network is formed.
* Pass Criteria:
* - N/A
*/
Log("Step 1: Form topology. Thread network is formed.");
eth1.Get<ThreadNetif>().Up();
eth1.mInfraIf.AddAddress(eth1Addr);
br1.Form();
nexus.AdvanceTime(kFormNetworkTime);
ed1.Join(br1, Node::kAsFed);
nexus.AdvanceTime(kJoinNetworkTime);
br1.Get<BorderRouter::InfraIf>().Init(1, true);
br1.mInfraIf.AddAddress(br1AilAddr);
br1.Get<BorderRouter::RoutingManager>().Init();
SuccessOrQuit(br1.Get<BorderRouter::RoutingManager>().SetEnabled(true));
SuccessOrQuit(br1.Get<Dns::ServiceDiscovery::Server>().Start());
br1.Get<Dns::ServiceDiscovery::Server>().SetUpstreamQueryEnabled(true);
{
Dns::Client::QueryConfig dnsConfig;
dnsConfig.Clear();
AsCoreType(&dnsConfig.mServerSockAddr.mAddress) = br1.Get<Mle::Mle>().GetMeshLocalEid();
dnsConfig.mServerSockAddr.mPort = 53;
ed1.Get<Dns::Client>().SetDefaultConfig(dnsConfig);
}
// Send RA for BR to adopt the DNS server address and OMR prefix
SendRa(eth1, eth1Addr, guaPrefix);
nexus.AdvanceTime(kStabilizationTime);
/**
* Step 2
* Device: Eth_1
* Description (DNS-11.5): Harness instructs device to configure DNS server with
* records: threadgroup1.org AAAA 2002:1234::E1:1, threadgroup1.org A 192.168.217.1,
* threadgroup2.org A 192.168.217.2, threadgroup2.org TYPE65285 \# 4 12345678.
* Note: the record type 0xFF05 is a private range record type, hence it has the
* symbolic name TYPE65285 associated.
* Pass Criteria:
* - N/A
*/
Log("Step 2: Eth_1 configures DNS server with records.");
/**
* Step 3
* Device: Eth_1
* Description (DNS-11.5): Harness instructs device to start advertising mDNS
* services on the AIL: Note: the PTR records for services are not shown below;
* however these are present as usual. $ORIGIN local. Service-test-1.
* _infra-test._udp ( SRV 0 0 55551 host-test-eth TXT dummy=abcd;thread-test=a49 )
* service-test-2._infra-test._udp ( SRV 0 0 55552 host-test-eth TXT a=2;thread-test
* =b50_test ) ) host-test-eth A <IPv4 address of Eth_1>.
* Pass Criteria:
* - N/A
*/
Log("Step 3: Eth_1 starts advertising mDNS services on the AIL.");
{
Dns::Multicast::Core::Service mdnsService;
Ip6::Address eth1Ip4Addr;
Ip4::Address ip4Addr;
SuccessOrQuit(eth1.Get<Dns::Multicast::Core>().SetLocalHostName("host-test-eth"));
SuccessOrQuit(eth1.Get<Dns::Multicast::Core>().SetEnabled(true, 1));
SuccessOrQuit(ip4Addr.FromString("192.168.217.100"));
eth1Ip4Addr.InitAsIp4Mapped(ip4Addr);
eth1.mInfraIf.AddAddress(eth1Ip4Addr);
eth1.mMdns.SignalIfAddresses(eth1.GetInstance());
ClearAllBytes(mdnsService);
mdnsService.mServiceInstance = "service-test-1";
mdnsService.mServiceType = "_infra-test._udp";
mdnsService.mHostName = "host-test-eth";
static const uint8_t kTxtData1[] = "\x0adummy=abcd\x0fthread-test=a49";
mdnsService.mTxtData = kTxtData1;
mdnsService.mTxtDataLength = sizeof(kTxtData1) - 1;
mdnsService.mPort = 55551;
SuccessOrQuit(eth1.Get<Dns::Multicast::Core>().RegisterService(mdnsService, 1, nullptr));
mdnsService.mServiceInstance = "service-test-2";
mdnsService.mPort = 55552;
static const uint8_t kTxtData2[] = "\x03a=2\x14thread-test=b50_test";
mdnsService.mTxtData = kTxtData2;
mdnsService.mTxtDataLength = sizeof(kTxtData2) - 1;
SuccessOrQuit(eth1.Get<Dns::Multicast::Core>().RegisterService(mdnsService, 2, nullptr));
}
nexus.AdvanceTime(kStabilizationTime);
/**
* Step 4
* Device: ED_1
* Description (DNS-11.5): Harness instructs device to send a DNS query to the DUT
* with Qtype=AAAA for name “threadgroup1.org”.
* Pass Criteria:
* - N/A
*/
Log("Step 4: ED_1 sends a DNS query to the DUT with Qtype=AAAA for name threadgroup1.org.");
SendRa(eth1, eth1Addr, guaPrefix);
SendQuery(ed1, kThreadGroup1, Dns::ResourceRecord::kTypeAaaa);
nexus.AdvanceTime(kStabilizationTime);
/**
* Step 5
* Device: BR_1 (DUT)
* Description (DNS-11.5): Automatically performs the DNS query via the Eth_1 DNS
* server, and provides the answer back to ED_1.
* Pass Criteria:
* - The DUT MUST respond Rcode=0 (NoError) with answer record: threadgroup1.org
* AAAA 2002:1234::E1:1
*/
Log("Step 5: BR_1 performs the DNS query via Eth_1 and provides the answer to ED_1.");
/**
* Step 6
* Device: ED_1
* Description (DNS-11.5): Harness instructs device to send a DNS query to the DUT,
* with Qtype=A for name “threadgroup1.org”.
* Pass Criteria:
* - N/A
*/
Log("Step 6: ED_1 sends a DNS query to the DUT with Qtype=A for name threadgroup1.org.");
SendRa(eth1, eth1Addr, guaPrefix);
SendQuery(ed1, kThreadGroup1, Dns::ResourceRecord::kTypeA);
nexus.AdvanceTime(kStabilizationTime);
/**
* Step 7
* Device: BR_1 (DUT)
* Description (DNS-11.5): Automatically performs the DNS query via the Eth_1 DNS
* server, and provides the answer back to ED_1.
* Pass Criteria:
* - The DUT MUST respond Rcode=0 (NoError) with answer record: threadgroup1.org A
* 192.168.217.1
*/
Log("Step 7: BR_1 performs the DNS query via Eth_1 and provides the answer to ED_1.");
/**
* Step 8
* Device: ED_1
* Description (DNS-11.5): Harness instructs device to send a DNS query to the DUT,
* with Qtype=AAAA for name “threadgroup2.org”.
* Pass Criteria:
* - N/A
*/
Log("Step 8: ED_1 sends a DNS query to the DUT with Qtype=AAAA for name threadgroup2.org.");
SendRa(eth1, eth1Addr, guaPrefix);
SendQuery(ed1, kThreadGroup2, Dns::ResourceRecord::kTypeAaaa);
nexus.AdvanceTime(kStabilizationTime);
/**
* Step 9
* Device: BR_1 (DUT)
* Description (DNS-11.5): Automatically performs the DNS query via the Eth_1 DNS
* server, and provides the error answer back to ED_1. Note: this checks that the
* DUT does not perform AAAA synthesis from the A record.
* Pass Criteria:
* - The DUT MUST respond Rcode=0 (NoError) with zero answer records (ANCOUNT=0).
*/
Log("Step 9: BR_1 performs the DNS query via Eth_1 and provides the error answer to ED_1.");
/**
* Step 10
* Device: ED_1
* Description (DNS-11.5): Harness instructs device to send a DNS query, with
* Qtype=A for name “host-test-eth.default.service.arpa”.
* Pass Criteria:
* - N/A
*/
Log("Step 10: ED_1 sends a DNS query with Qtype=A for host-test-eth.default.service.arpa.");
SendQuery(ed1, "host-test-eth.default.service.arpa", Dns::ResourceRecord::kTypeA);
nexus.AdvanceTime(kStabilizationTime);
/**
* Step 11
* Device: BR_1 (DUT)
* Description (DNS-11.5): Automatically performs the mDNS query on AIL
* (optionally, if still needed), and provides the answer back to ED_1.
* Pass Criteria:
* - The DUT MUST respond Rcode=0 (NoError) with answer record: host-test-eth.
* default.service.arpa A <IPv4 address of Eth_1>
*/
Log("Step 11: BR_1 performs mDNS query on AIL and provides the answer to ED_1.");
/**
* Step 12
* Device: ED_1
* Description (DNS-11.5): Harness instructs device to send DNS query Qtype=A for
* name “ipv4only.arpa”
* Pass Criteria:
* - N/A
*/
Log("Step 12: ED_1 sends DNS query Qtype=A for name ipv4only.arpa.");
SendQuery(ed1, "ipv4only.arpa", Dns::ResourceRecord::kTypeA);
nexus.AdvanceTime(kStabilizationTime);
/**
* Step 13
* Device: BR_1 (DUT)
* Description (DNS-11.5): Automatically blocks the ipv4only.arpa query and
* responds NXDomain error.
* Pass Criteria:
* - The DUT MUST respond Rcode=3 (NXDomain) with zero answer records (ANCOUNT=0).
* - The DUT MUST NOT send a DNS query to Eth_1.
*/
Log("Step 13: BR_1 blocks the ipv4only.arpa query and responds NXDomain error.");
/**
* Step 14
* Device: ED_1
* Description (DNS-11.5): Harness instructs device to send DNS query with RR type
* in the private use range Qtype=0xFF05 for name “threadgroup2.org”. To do this
* with OT CLI, use the following: udp send <BR_1 ML-EID address> 53 -x
* e13c010000010000000000000c74687265616467726f757032036f726700ff050001 “”
* Pass Criteria:
* - N/A
*/
Log("Step 14: ED_1 sends DNS query with RR type in private use range Qtype=0xFF05.");
SendRa(eth1, eth1Addr, guaPrefix);
SendQuery(ed1, kThreadGroup2, kTypePrivateUse);
nexus.AdvanceTime(kStabilizationTime);
/**
* Step 15
* Device: BR_1 (DUT)
* Description (DNS-11.5): Automatically responds with the requested record.
* Pass Criteria:
* - The DUT MUST respond Rcode=0 (NoError) with answer record with binary data
* (4 bytes): threadgroup2.org 0xFF05 0x12345678
*/
Log("Step 15: BR_1 responds with the requested record.");
nexus.SaveTestInfo(aJsonFileName);
}
} // namespace Nexus
} // namespace ot
int main(int argc, char *argv[])
{
ot::Nexus::Test_1_4_DNS_TC_5((argc > 2) ? argv[2] : "test_1_4_dns_tc_5.json");
ot::Nexus::Log("All tests passed");
return 0;
}