From 42b653a18c8c41ffba119bdca4dcc0a9781fb033 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Wed, 1 Apr 2026 18:24:12 -0700 Subject: [PATCH] [nexus] add 1_4_DNS_TC_1 for multi-question DNS queries (#12817) This commit adds Nexus test case 1_4_DNS_TC_1, which verifies that the Thread Border Router DUT can successfully handle DNS queries with multiple questions (QDCOUNT > 1), per the Thread 1.4 specification. The implementation includes: - tests/nexus/test_1_4_dns_tc_1.cpp: C++ test logic that sets up a topology with Eth_1, BR_1 (DUT), Router_1, and ED_1. It registers services via mDNS on Eth_1 and SRP on Router_1, and then performs various DNS queries from ED_1, including multi-question queries. - tests/nexus/verify_1_4_dns_tc_1.py: Python script that verifies the DNS packet exchange in the pcap, ensuring that multi-question queries are correctly received by the DUT and that valid responses are returned. - Integration into tests/nexus/CMakeLists.txt and tests/nexus/run_nexus_tests.sh for automated building and execution. --- tests/nexus/CMakeLists.txt | 1 + tests/nexus/run_nexus_tests.sh | 1 + tests/nexus/test_1_4_DNS_TC_1.cpp | 362 +++++++++++++++++++++++++++++ tests/nexus/verify_1_4_DNS_TC_1.py | 267 +++++++++++++++++++++ tests/nexus/verify_utils.py | 2 + 5 files changed, 633 insertions(+) create mode 100644 tests/nexus/test_1_4_DNS_TC_1.cpp create mode 100644 tests/nexus/verify_1_4_DNS_TC_1.py diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 2c0256ce2..83ebc93df 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -290,6 +290,7 @@ ot_nexus_test(1_4_TREL_TC_3 "cert;nexus") ot_nexus_test(1_4_TREL_TC_4 "cert;nexus") ot_nexus_test(1_4_TREL_TC_5 "cert;nexus") ot_nexus_test(1_4_TREL_TC_6 "cert;nexus") +ot_nexus_test(1_4_DNS_TC_1 "cert;nexus") # Misc tests ot_nexus_test(border_admitter "core;nexus") diff --git a/tests/nexus/run_nexus_tests.sh b/tests/nexus/run_nexus_tests.sh index 1f4b81aab..c0e27749e 100755 --- a/tests/nexus/run_nexus_tests.sh +++ b/tests/nexus/run_nexus_tests.sh @@ -225,6 +225,7 @@ DEFAULT_TESTS=( "1_4_TREL_TC_4" "1_4_TREL_TC_5" "1_4_TREL_TC_6" + "1_4_DNS_TC_1" ) # Use provided arguments or the default test list diff --git a/tests/nexus/test_1_4_DNS_TC_1.cpp b/tests/nexus/test_1_4_DNS_TC_1.cpp new file mode 100644 index 000000000..554593303 --- /dev/null +++ b/tests/nexus/test_1_4_DNS_TC_1.cpp @@ -0,0 +1,362 @@ +/* + * 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 + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +namespace ot { +namespace Nexus { + +/** + * 11.1. [1.3] [CERT] Handling of multi-question DNS queries + * + * 11.1.1. Purpose + * - To verify that the Thread Border Router DUT can successfully handle DNS queries with + * multiple questions (QDCOUNT > 1) by either: + * - Responding with an error status (signaling to the querier that it needs to retry with + * single-question queries) + * - Providing the answers to the multiple questions (as defined by Thread) if the questions + * are for the same QNAME but different record types. + * + * 11.1.2. Topology + * - Eth_1 host registering a service + * - BR_1 Border Router DUT + * - Thread Router_1 registering service(s) (Connected to BR DUT) + * - Thread ED_1 sending QDCOUNT > 1 queries (Child of Router_1) + */ + +namespace { + +static constexpr uint32_t kFormNetworkTime = 13 * 1000; +static constexpr uint32_t kJoinNetworkTime = 20 * 1000; +static constexpr uint32_t kStabilizationTime = 10 * 1000; +static constexpr uint16_t kServicePort = 55556; + +void SendMultiQuestionQuery(Node &aNode, const Ip6::Address &aDest, const char *aName, uint16_t aType1, uint16_t aType2) +{ + Ip6::Udp::Socket socket(aNode, nullptr, nullptr); + SuccessOrQuit(socket.Open(Ip6::kNetifUnspecified)); + + Message *message = socket.NewMessage(); + VerifyOrQuit(message != nullptr); + + Dns::Header header; + header.SetType(Dns::Header::kTypeQuery); + header.SetQuestionCount(2); + SuccessOrQuit(message->Append(header)); + + SuccessOrQuit(Dns::Name::AppendName(aName, *message)); + Dns::Question question1(aType1); + SuccessOrQuit(message->Append(question1)); + + SuccessOrQuit(Dns::Name::AppendName(aName, *message)); + Dns::Question question2(aType2); + SuccessOrQuit(message->Append(question2)); + + Ip6::MessageInfo messageInfo; + messageInfo.SetPeerAddr(aDest); + messageInfo.SetPeerPort(53); + + SuccessOrQuit(socket.SendTo(*message, messageInfo)); + SuccessOrQuit(socket.Close()); +} + +void SendSingleQuestionQuery(Node &aNode, const Ip6::Address &aDest, const char *aName, uint16_t aType) +{ + Ip6::Udp::Socket socket(aNode, nullptr, nullptr); + SuccessOrQuit(socket.Open(Ip6::kNetifUnspecified)); + + Message *message = socket.NewMessage(); + VerifyOrQuit(message != nullptr); + + Dns::Header header; + header.SetType(Dns::Header::kTypeQuery); + header.SetQuestionCount(1); + SuccessOrQuit(message->Append(header)); + + SuccessOrQuit(Dns::Name::AppendName(aName, *message)); + Dns::Question question(aType); + SuccessOrQuit(message->Append(question)); + + Ip6::MessageInfo messageInfo; + messageInfo.SetPeerAddr(aDest); + messageInfo.SetPeerPort(53); + + SuccessOrQuit(socket.SendTo(*message, messageInfo)); + SuccessOrQuit(socket.Close()); +} + +} // namespace + +void Test_1_4_DNS_TC_1(const char *aJsonFileName) +{ + Core nexus; + + Node ð1 = nexus.CreateNode(); + Node &br1 = nexus.CreateNode(); + Node &r1 = nexus.CreateNode(); + Node &ed1 = nexus.CreateNode(); + + Dns::Multicast::Core::Host host; + Ip6::Address eth1Addr; + Dns::Multicast::Core::Service mdnsService; + Srp::Client::Service srpService; + static const Dns::TxtEntry kTxtEntries[] = {{"key1", reinterpret_cast("value1"), 6}}; + + eth1.SetName("Eth", 1); + br1.SetName("BR", 1); + r1.SetName("Router", 1); + ed1.SetName("ED", 1); + + IgnoreError(Instance::SetGlobalLogLevel(kLogLevelNote)); + + /** + * Step 1 + * Device: All + * Description (DNS-11.1): Start topology + * Pass Criteria: + * - N/A + */ + Log("Step 1: Topology: Start Eth_1, BR_1, Router_1, and ED_1."); + + // BR_1 and Router_1 + br1.AllowList(r1); + r1.AllowList(br1); + + // Router_1 and ED_1 + r1.AllowList(ed1); + ed1.AllowList(r1); + + br1.Form(); + nexus.AdvanceTime(kFormNetworkTime); + + r1.Join(br1); + nexus.AdvanceTime(kJoinNetworkTime); + + ed1.Join(r1); + nexus.AdvanceTime(kJoinNetworkTime); + + // Setup BR_1 as Border Router + br1.Get().Init(Mdns::kInfraIfIndex, true); + SuccessOrQuit(br1.Get().SetEnabled(true)); + br1.Get().SetEnabled(true); + SuccessOrQuit(br1.Get().Start()); + + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 2 + * Device: Eth_1 + * Description (DNS-11.1): Harness instructs device to advertise a test service using + * mDNS: $ORIGIN local. Service-test-2._thread-test._udp ( SRV 0 0 55556 host-eth-1 + * TXT key2=value2 ) host-eth-1 AAAA + * Pass Criteria: + * - mDNS service MUST be registered without name conflict or service name change. + */ + Log("Step 2: Eth_1 host registers a service using mDNS."); + + SuccessOrQuit(eth1.Get().SetEnabled(true, Mdns::kInfraIfIndex)); + { + SuccessOrQuit(eth1Addr.FromString("fd00:1234:5678:abcd::1")); + eth1.mInfraIf.AddAddress(eth1Addr); + + ClearAllBytes(host); + host.mHostName = "host-eth-1"; + host.mAddresses = ð1Addr; + host.mAddressesLength = 1; + SuccessOrQuit(eth1.Get().RegisterHost(host, 0, nullptr)); + + ClearAllBytes(mdnsService); + mdnsService.mServiceInstance = "service-test-2"; + mdnsService.mServiceType = "_thread-test._udp"; + mdnsService.mHostName = "host-eth-1"; + mdnsService.mTxtData = reinterpret_cast("\x0bkey2=value2"); + mdnsService.mTxtDataLength = 12; + mdnsService.mPort = kServicePort; + SuccessOrQuit(eth1.Get().RegisterService(mdnsService, 1, nullptr)); + } + nexus.AdvanceTime(kStabilizationTime); + + Ip6::Address br1Addr = br1.Get().GetMeshLocalEid(); + + /** + * Step 3 + * Device: Router_1 + * Description (DNS-11.1): Harness instructs device to register a test service using + * SRP: $ORIGIN default.service.arpa. service-test-1._thread-test._udp ( SRV 0 0 + * 55556 host-router-1 TXT key1=value1 ) host-router-1 AAAA + * Pass Criteria: + * - The DUT MUST respond with success status (RCODE=0) to the SRP registration. + */ + Log("Step 3: Router_1 host registers a service using SRP."); + + { + Ip6::SockAddr serverSockAddr; + serverSockAddr.SetAddress(br1Addr); + serverSockAddr.SetPort(br1.Get().GetPort()); + SuccessOrQuit(r1.Get().Start(serverSockAddr)); + } + + SuccessOrQuit(r1.Get().SetHostName("host-router-1")); + SuccessOrQuit(r1.Get().EnableAutoHostAddress()); + + { + ClearAllBytes(srpService); + srpService.mName = "_thread-test._udp"; + srpService.mInstanceName = "service-test-1"; + srpService.mPort = kServicePort; + srpService.mTxtEntries = kTxtEntries; + srpService.mNumTxtEntries = 1; + SuccessOrQuit(srpService.Init()); + SuccessOrQuit(r1.Get().AddService(srpService)); + } + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 4 + * Device: ED_1 + * Description (DNS-11.1): Harness instructs device to perform DNS query with QDCOUNT=2: + * First question QNAME=service-test-1._thread-test._udp. Default.service.arpa QTYPE + * =SRV Second question QNAME=service-test-1._thread-test._udp. Default.service.arpa + * QTYPE =TXT + * Pass Criteria: + * - The DUT MUST either: 3. Respond with error RCODE=1 (FormErr) and 0 answers; or 3. + * Respond with success RCODE=0 (NoError) and two answer records in the Answers + * section as follows: $ORIGIN default.service.arpa. + * service-test-1._thread-test._udp ( SRV 0 0 55556 host-router-1 TXT key1=value1 ) + * - Furthermore the Additional records section MAY contain : host-router-1 AAAA + * - If this record is present, address MUST be same as in step 9. + */ + Log("Step 4: ED_1 performs a DNS query with two questions (QDCOUNT=2)."); + SendMultiQuestionQuery(ed1, br1Addr, "service-test-1._thread-test._udp.default.service.arpa", + Dns::ResourceRecord::kTypeSrv, Dns::ResourceRecord::kTypeTxt); + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 5 + * Device: ED_1 + * Description (DNS-11.1): Harness instructs devie to perform DNS query with QDCOUNT=2: + * First question QNAME=service-test-2._thread-test._udp. Default.service.arpa QTYPE + * =SRV Second question QNAME=service-test-2._thread-test._udp. Default.service.arpa + * QTYPE =TXT + * Pass Criteria: + * - The DUT MUST either: 3. 3. Respond with error RCODE=1 (FormErr) and 0 answers; + * or Respond with success RCODE=0 (NoError) and two answer records in the + * Answers section as follows: $ORIGIN default.service.arpa. + * service-test-2._thread-test._udp ( SRV 0 0 55556 host-eth-1 TXT key2=value2 ) + * - Furthermore the Additional records section MAY contain : host-eth-1 AAAA + * - If this record is present, address MUST be same as in step 8. + */ + Log("Step 5: ED_1 performs a DNS query with two questions (QDCOUNT=2)."); + SendMultiQuestionQuery(ed1, br1Addr, "service-test-2._thread-test._udp.default.service.arpa", + Dns::ResourceRecord::kTypeSrv, Dns::ResourceRecord::kTypeTxt); + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 6 + * Device: ED_1 + * Description (DNS-11.1): Harness instructs device to perform DNS query with + * QDCOUNT=1: First question QNAME=service-test-1_thread-test. + * _udp.default.service.arpa QTYPE=SRV + * Pass Criteria: + * - The DUT MUST respond with success RCODE=0 (NoError) and one answer record in + * the Answers section as follows: $ORIGIN default.service.arpa. + * service-test-1._thread-test._udp ( SRV 0 0 55556 host-router-1 ) + * - Furthermore the Additional records section MAY contain : host-router-1 AAAA + * + */ + Log("Step 6: ED_1 performs a DNS query for service-test-1 (SRV)."); + SendSingleQuestionQuery(ed1, br1Addr, "service-test-1._thread-test._udp.default.service.arpa", + Dns::ResourceRecord::kTypeSrv); + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 7 + * Device: ED_1 + * Description (DNS-11.1): Harness instructs device to perform DNS query with + * QDCOUNT=1: First question QNAME=service-test-2_thread-test. + * _udp.default.service.arpa QTYPE=TXT + * Pass Criteria: + * - The DUT MUST respond with success RCODE=0 (NoError) and one answer record in + * the Answers section as follows: $ORIGIN default.service.arpa. + * service-test-2._thread-test._udp ( TXT key2=value2 ) + */ + Log("Step 7: ED_1 performs a DNS query for service-test-2 (TXT)."); + SendSingleQuestionQuery(ed1, br1Addr, "service-test-2._thread-test._udp.default.service.arpa", + Dns::ResourceRecord::kTypeTxt); + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 8 + * Device: ED_1 + * Description (DNS-11.1): Harness instructs device to perform DNS query with + * QDCOUNT=1: First question QNAME=host-eth-1.default.service.arpa QTYPE=AAAA + * Pass Criteria: + * - The DUT MUST respond with success RCODE=0 (NoError) and one answer record in + * the Answers section as follows: $ORIGIN default.service.arpa. host-eth-1 AAAA + * + * - The DUT MUST NOT include a link-local address in an AAAA record in the + * answer(s). + */ + Log("Step 8: ED_1 performs a DNS query for host-eth-1 (AAAA)."); + SendSingleQuestionQuery(ed1, br1Addr, "host-eth-1.default.service.arpa", Dns::ResourceRecord::kTypeAaaa); + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 9 + * Device: ED_1 + * Description (DNS-11.1): Harness instructs device to perform DNS query with + * QDCOUNT=1: First question QNAME=host-router-1.default.service.arpa QTYPE=AAAA + * Pass Criteria: + * - The DUT MUST respond with success RCODE=0 (NoError) and one answer record in + * the Answers section as follows: $ORIGIN default.service.arpa. host-router-1 + * AAAA + * - The DUT MUST NOT include a link-local address in an AAAA record in the + * answer(s). + */ + Log("Step 9: ED_1 performs a DNS query for host-router-1 (AAAA)."); + SendSingleQuestionQuery(ed1, br1Addr, "host-router-1.default.service.arpa", Dns::ResourceRecord::kTypeAaaa); + nexus.AdvanceTime(kStabilizationTime); + + nexus.SaveTestInfo(aJsonFileName); +} + +} // namespace Nexus +} // namespace ot + +int main(int argc, char *argv[]) +{ + ot::Nexus::Test_1_4_DNS_TC_1((argc > 2) ? argv[2] : "test_1_4_dns_tc_1.json"); + ot::Nexus::Log("All tests passed"); + return 0; +} diff --git a/tests/nexus/verify_1_4_DNS_TC_1.py b/tests/nexus/verify_1_4_DNS_TC_1.py new file mode 100644 index 000000000..981b80068 --- /dev/null +++ b/tests/nexus/verify_1_4_DNS_TC_1.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +# +# 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. +# + +import sys +import os + +# Add the current directory to sys.path to find verify_utils +CUR_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(CUR_DIR) + +import verify_utils +from pktverify import consts + + +def verify(pv): + # 11.1. [1.3] [CERT] Handling of multi-question DNS queries + # + # 11.1.1. Purpose + # - To verify that the Thread Border Router DUT can successfully handle DNS queries with + # multiple questions (QDCOUNT > 1) by either: + # - Responding with an error status (signaling to the querier that it needs to retry with + # single-question queries) + # - Providing the answers to the multiple questions (as defined by Thread) if the questions + # are for the same QNAME but different record types. + # + # 11.1.2. Topology + # - Eth_1 host registering a service + # - BR_1 Border Router DUT + # - Thread Router_1 registering service(s) (Connected to BR DUT) + # - Thread ED_1 sending QDCOUNT > 1 queries (Child of Router_1) + + pkts = pv.pkts + pv.summary.show() + + BR_1 = pv.vars['BR_1'] + ED_1 = pv.vars['ED_1'] + BR_1_MLEID = pv.vars['BR_1_MLEID'] + ED_1_MLEID = pv.vars['ED_1_MLEID'] + + # Step 1 + # Device: All + # Description (DNS-11.1): Start topology + # Pass Criteria: + # - N/A + print("Step 1: Topology: Start Eth_1, BR_1, Router_1, and ED_1.") + + # Step 2 + # Device: Eth_1 + # Description (DNS-11.1): Harness instructs device to advertise a test service using + # mDNS: $ORIGIN local. Service-test-2._thread-test._udp ( SRV 0 0 55556 host-eth-1 + # TXT key2=value2 ) host-eth-1 AAAA + # Pass Criteria: + # - mDNS service MUST be registered without name conflict or service name change. + print("Step 2: Eth_1 host registers a service using mDNS.") + + # Step 3 + # Device: Router_1 + # Description (DNS-11.1): Harness instructs device to register a test service using + # SRP: $ORIGIN default.service.arpa. service-test-1._thread-test._udp ( SRV 0 0 + # 55556 host-router-1 TXT key1=value1 ) host-router-1 AAAA + # Pass Criteria: + # - The DUT MUST respond with success status (RCODE=0) to the SRP registration. + print("Step 3: Router_1 host registers a service using SRP.") + + # Step 4 + # Device: ED_1 + # Description (DNS-11.1): Harness instructs device to perform DNS query with QDCOUNT=2: + # First question QNAME=service-test-1._thread-test._udp. Default.service.arpa QTYPE + # =SRV Second question QNAME=service-test-1._thread-test._udp. Default.service.arpa + # QTYPE =TXT + # Pass Criteria: + # - The DUT MUST either: 3. Respond with error RCODE=1 (FormErr) and 0 answers; or 3. + # Respond with success RCODE=0 (NoError) and two answer records in the Answers + # section as follows: $ORIGIN default.service.arpa. + # service-test-1._thread-test._udp ( SRV 0 0 55556 host-router-1 TXT key1=value1 ) + # - Furthermore the Additional records section MAY contain : host-router-1 AAAA + # - If this record is present, address MUST be same as in step 9. + print("Step 4: ED_1 performs a DNS query with two questions (QDCOUNT=2).") + pkts.filter_wpan_src64(ED_1).\ + filter_ipv6_dst(BR_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 0).\ + filter(lambda p: int(p.dns.count.queries) == 2).\ + must_next() + + pkts.filter_ipv6_src(BR_1_MLEID).\ + filter_ipv6_dst(ED_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 1).\ + filter(lambda p: (p.dns.flags.rcode == consts.DNS_RCODE_NOERROR and + int(p.dns.count.answers) == 2 and + 55556 in verify_utils.as_list(p.dns.srv.port) and + "host-router-1.default.service.arpa" in verify_utils.as_list(p.dns.srv.target) and + any(b"key1=value1" in t for t in verify_utils.as_list(p.dns.txt))) or + (p.dns.flags.rcode == 1)).\ + must_next() + + # Step 5 + # Device: ED_1 + # Description (DNS-11.1): Harness instructs devie to perform DNS query with QDCOUNT=2: + # First question QNAME=service-test-2._thread-test._udp. Default.service.arpa QTYPE + # =SRV Second question QNAME=service-test-2._thread-test._udp. Default.service.arpa + # QTYPE =TXT + # Pass Criteria: + # - The DUT MUST either: 3. 3. Respond with error RCODE=1 (FormErr) and 0 answers; + # or Respond with success RCODE=0 (NoError) and two answer records in the + # Answers section as follows: $ORIGIN default.service.arpa. + # service-test-2._thread-test._udp ( SRV 0 0 55556 host-eth-1 TXT key2=value2 ) + # - Furthermore the Additional records section MAY contain : host-eth-1 AAAA + # - If this record is present, address MUST be same as in step 8. + print("Step 5: ED_1 performs a DNS query with two questions (QDCOUNT=2).") + pkts.filter_wpan_src64(ED_1).\ + filter_ipv6_dst(BR_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 0).\ + filter(lambda p: int(p.dns.count.queries) == 2).\ + must_next() + + pkts.filter_ipv6_src(BR_1_MLEID).\ + filter_ipv6_dst(ED_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 1).\ + filter(lambda p: (p.dns.flags.rcode == consts.DNS_RCODE_NOERROR and + int(p.dns.count.answers) == 2 and + 55556 in verify_utils.as_list(p.dns.srv.port) and + "host-eth-1.default.service.arpa" in verify_utils.as_list(p.dns.srv.target) and + any(b"key2=value2" in t for t in verify_utils.as_list(p.dns.txt))) or + (p.dns.flags.rcode == 1)).\ + must_next() + + # Step 6 + # Device: ED_1 + # Description (DNS-11.1): Harness instructs device to perform DNS query with + # QDCOUNT=1: First question QNAME=service-test-1_thread-test. + # _udp.default.service.arpa QTYPE=SRV + # Pass Criteria: + # - The DUT MUST respond with success RCODE=0 (NoError) and one answer record in + # the Answers section as follows: $ORIGIN default.service.arpa. + # service-test-1._thread-test._udp ( SRV 0 0 55556 host-router-1 ) + # - Furthermore the Additional records section MAY contain : host-router-1 AAAA + # + print("Step 6: ED_1 performs a DNS query for service-test-1 (SRV).") + pkts.filter_wpan_src64(ED_1).\ + filter_ipv6_dst(BR_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 0).\ + filter(lambda p: "service-test-1._thread-test._udp.default.service.arpa" in + verify_utils.as_list(p.dns.qry.name) and + consts.DNS_TYPE_SRV in verify_utils.as_list(p.dns.qry.type)).\ + must_next() + + pkts.filter_ipv6_src(BR_1_MLEID).\ + filter_ipv6_dst(ED_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 1).\ + filter(lambda p: p.dns.flags.rcode == consts.DNS_RCODE_NOERROR and + int(p.dns.count.answers) >= 1 and + 55556 in verify_utils.as_list(p.dns.srv.port) and + "host-router-1.default.service.arpa" in verify_utils.as_list(p.dns.srv.target)).\ + must_next() + + # Step 7 + # Device: ED_1 + # Description (DNS-11.1): Harness instructs device to perform DNS query with + # QDCOUNT=1: First question QNAME=service-test-2_thread-test. + # _udp.default.service.arpa QTYPE=TXT + # Pass Criteria: + # - The DUT MUST respond with success RCODE=0 (NoError) and one answer record in + # the Answers section as follows: $ORIGIN default.service.arpa. + # service-test-2._thread-test._udp ( TXT key2=value2 ) + print("Step 7: ED_1 performs a DNS query for service-test-2 (TXT).") + pkts.filter_wpan_src64(ED_1).\ + filter_ipv6_dst(BR_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 0).\ + filter(lambda p: "service-test-2._thread-test._udp.default.service.arpa" in + verify_utils.as_list(p.dns.qry.name) and + consts.DNS_TYPE_TXT in verify_utils.as_list(p.dns.qry.type)).\ + must_next() + + pkts.filter_ipv6_src(BR_1_MLEID).\ + filter_ipv6_dst(ED_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 1).\ + filter(lambda p: p.dns.flags.rcode == consts.DNS_RCODE_NOERROR and + int(p.dns.count.answers) >= 1 and + any(b"key2=value2" in t for t in verify_utils.as_list(p.dns.txt))).\ + must_next() + + # Step 8 + # Device: ED_1 + # Description (DNS-11.1): Harness instructs device to perform DNS query with + # QDCOUNT=1: First question QNAME=host-eth-1.default.service.arpa QTYPE=AAAA + # Pass Criteria: + # - The DUT MUST respond with success RCODE=0 (NoError) and one answer record in + # the Answers section as follows: $ORIGIN default.service.arpa. host-eth-1 AAAA + # + # - The DUT MUST NOT include a link-local address in an AAAA record in the + # answer(s). + print("Step 8: ED_1 performs a DNS query for host-eth-1 (AAAA).") + pkts.filter_wpan_src64(ED_1).\ + filter_ipv6_dst(BR_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 0).\ + filter(lambda p: "host-eth-1.default.service.arpa" in + verify_utils.as_list(p.dns.qry.name) and + consts.DNS_TYPE_AAAA in verify_utils.as_list(p.dns.qry.type)).\ + must_next() + + pkts.filter_ipv6_src(BR_1_MLEID).\ + filter_ipv6_dst(ED_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 1).\ + filter(lambda p: p.dns.flags.rcode == consts.DNS_RCODE_NOERROR).\ + filter(lambda p: all(not verify_utils.Ipv6Addr(addr).is_link_local + for addr in verify_utils.as_list(p.dns.aaaa))).\ + must_next() + + # Step 9 + # Device: ED_1 + # Description (DNS-11.1): Harness instructs device to perform DNS query with + # QDCOUNT=1: First question QNAME=host-router-1.default.service.arpa QTYPE=AAAA + # Pass Criteria: + # - The DUT MUST respond with success RCODE=0 (NoError) and one answer record in + # the Answers section as follows: $ORIGIN default.service.arpa. host-router-1 + # AAAA + # - The DUT MUST NOT include a link-local address in an AAAA record in the + # answer(s). + print("Step 9: ED_1 performs a DNS query for host-router-1 (AAAA).") + pkts.filter_wpan_src64(ED_1).\ + filter_ipv6_dst(BR_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 0).\ + filter(lambda p: "host-router-1.default.service.arpa" in + verify_utils.as_list(p.dns.qry.name) and + consts.DNS_TYPE_AAAA in verify_utils.as_list(p.dns.qry.type)).\ + must_next() + + pkts.filter_ipv6_src(BR_1_MLEID).\ + filter_ipv6_dst(ED_1_MLEID).\ + filter(lambda p: p.dns.flags.response == 1).\ + filter(lambda p: p.dns.flags.rcode == consts.DNS_RCODE_NOERROR).\ + filter(lambda p: all(not verify_utils.Ipv6Addr(addr).is_link_local + for addr in verify_utils.as_list(p.dns.aaaa))).\ + must_next() + + +if __name__ == '__main__': + verify_utils.run_main(verify) diff --git a/tests/nexus/verify_utils.py b/tests/nexus/verify_utils.py index 09d4273fa..65c39ef90 100644 --- a/tests/nexus/verify_utils.py +++ b/tests/nexus/verify_utils.py @@ -636,7 +636,9 @@ def apply_patches(): layer_fields._LAYER_FIELDS['thread_meshcop.tlv.delay_timer'] = layer_fields._auto layer_fields._LAYER_FIELDS['mle.tlv.link_query_options'] = layer_fields._bytes layer_fields._LAYER_FIELDS['dns.opt.data'] = layer_fields._list(layer_fields._bytes) + layer_fields._LAYER_FIELDS['dns.count.queries'] = layer_fields._auto layer_fields._layer_containers.add('dns.opt') + layer_fields._layer_containers.add('dns.count') layer_fields._LAYER_FIELDS['mdns.nsec'] = layer_fields._list(layer_fields._bytes) def which_tshark_patch():