[nexus] add SRPC-TC-5 for DNS-SD service discovery (#12787)

This commit adds the Nexus test case 1_3_SRPC_TC_5 which verifies
that a DNS-SD client can correctly discover multiple services
registered via SRP, as per the Thread 1.3 test specification.

The implementation includes:
- tests/nexus/test_1_3_SRPC_TC_5.cpp: Executes the test sequence
  by configuring a Border Router (BR_1), an End Device (ED_2)
  registering 5 services with various TXT records, and a DUT
  (TD_1). It instructs the DUT to browse for services, resolve
  them, and send UDP packets to each resolved service. It verifies
  the TXT record values and successful UDP transmissions.
- tests/nexus/verify_1_3_SRPC_TC_5.py: Performs automated
  verification of the captured traffic (PCAP). It validates the
  DNS query, the DNS response containing all 5 services, and the
  subsequent UDP packets sent to the resolved addresses and ports.
- Integrated the new test into tests/nexus/CMakeLists.txt and
  tests/nexus/run_nexus_tests.sh.

The test ensures the correctness of DNS-SD client discovery logic
and its ability to handle multiple service responses in a Thread
network.
This commit is contained in:
Jonathan Hui
2026-03-28 22:46:09 -05:00
committed by GitHub
parent dd855c3b3e
commit 1b20885508
4 changed files with 763 additions and 0 deletions
+1
View File
@@ -277,6 +277,7 @@ ot_nexus_test(1_3_SRP_TC_13 "cert;nexus")
ot_nexus_test(1_3_SRP_TC_15 "cert;nexus")
ot_nexus_test(1_3_SRPC_TC_1 "cert;nexus")
ot_nexus_test(1_3_SRPC_TC_4 "cert;nexus")
ot_nexus_test(1_3_SRPC_TC_5 "cert;nexus")
# Misc tests
ot_nexus_test(border_admitter "core;nexus")
+1
View File
@@ -212,6 +212,7 @@ DEFAULT_TESTS=(
"1_3_SRP_TC_15"
"1_3_SRPC_TC_1"
"1_3_SRPC_TC_4"
"1_3_SRPC_TC_5"
)
# Use provided arguments or the default test list
+573
View File
@@ -0,0 +1,573 @@
/*
* 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 <stdio.h>
#include <string.h>
#include "platform/nexus_core.hpp"
#include "platform/nexus_node.hpp"
namespace ot {
namespace Nexus {
/**
* Time to advance for a node to form a network and become leader, in milliseconds.
*/
static constexpr uint32_t kFormNetworkTime = 13 * 1000;
/**
* Time to advance for a node to join a network, in milliseconds.
*/
static constexpr uint32_t kJoinNetworkTime = 10 * 1000;
/**
* Time to advance for the network to stabilize, in milliseconds.
*/
static constexpr uint32_t kStabilizationTime = 15 * 1000;
/**
* Time to wait for SRP registration to complete.
*/
static constexpr uint32_t kSrpRegistrationTime = 10 * 1000;
/**
* Time to advance for DNS query processing.
*/
static constexpr uint32_t kDnsQueryTime = 5 * 1000;
/**
* Infrastructure interface index.
*/
static constexpr uint32_t kInfraIfIndex = 1;
/**
* SRP Lease time in seconds.
*/
static constexpr uint32_t kSrpLease5m = 300;
/**
* SRP Key Lease time in seconds.
*/
static constexpr uint32_t kSrpKeyLease10m = 600;
/**
* SRP service ports.
*/
static constexpr uint16_t kSrpServicePort1 = 55551;
static constexpr uint16_t kSrpServicePort2 = 55552;
static constexpr uint16_t kSrpServicePort3 = 55553;
static constexpr uint16_t kSrpServicePort4 = 55554;
static constexpr uint16_t kSrpServicePort5 = 55555;
/**
* SRP service and host names.
*/
static const char kSrpServiceType[] = "_thread-test._udp";
static const char kSrpFullServiceType[] = "_thread-test._udp.default.service.arpa.";
static const char kSrpHostName[] = "host-test-2";
/**
* Expected values for "thread-test" key in TXT records.
*/
static const char *const kExpectedTxtValues[] = {
"a49", "b50_test", "", "1", "C51",
};
/**
* Test context to track progress.
*/
struct TestContext
{
Node *mNode;
uint8_t mBrowseCount;
uint8_t mResolveCount;
uint8_t mUdpCount;
};
void HandleResolveResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext)
{
TestContext *context = static_cast<TestContext *>(aContext);
const Dns::Client::ServiceResponse &response = *static_cast<const Dns::Client::ServiceResponse *>(aResponse);
Dns::Client::ServiceInfo serviceInfo;
Ip6::Address address;
uint32_t ttl;
Dns::Name::Buffer hostName;
static constexpr uint16_t kMaxTxtDataSize = 254;
uint8_t txtData[kMaxTxtDataSize];
Ip6::MessageInfo messageInfo;
Message *message;
if (aError != OT_ERROR_NONE)
{
Log("HandleResolveResponse error: %s", ErrorToString(aError));
SuccessOrQuit(aError);
}
serviceInfo.mHostNameBuffer = hostName;
serviceInfo.mHostNameBufferSize = sizeof(hostName);
serviceInfo.mTxtData = txtData;
serviceInfo.mTxtDataSize = sizeof(txtData);
SuccessOrQuit(response.GetServiceInfo(serviceInfo));
context->mResolveCount++;
/**
* Step 7
* - Device: TD_1 (DUT)
* - Description (SRPC-3.5): Harness receives each of the N service responses via API from the DUT Component.
* For each service, it uses the API to extract the TXT record key "thread-test" and compares against the
* expected value.
* - Pass Criteria:
* - The DUT MUST return the correct values for the "thread-test" key as defined below for each of the
* respective N services:
* - 1. a49
* - 2. b50 test
* - 3. (empty string of 0 characters)
* - 4. 1
* - 5. C51
*/
Log("Step 7: Verify TXT record key 'thread-test' for resolved service on port %u", serviceInfo.mPort);
Dns::TxtEntry::Iterator iterator;
Dns::TxtEntry entry;
bool found = false;
iterator.Init(serviceInfo.mTxtData, serviceInfo.mTxtDataSize);
while (iterator.GetNextEntry(entry) == kErrorNone)
{
if (entry.MatchesKey("thread-test"))
{
uint8_t index = 0;
switch (serviceInfo.mPort)
{
case kSrpServicePort1:
index = 0;
break;
case kSrpServicePort2:
index = 1;
break;
case kSrpServicePort3:
index = 2;
break;
case kSrpServicePort4:
index = 3;
break;
case kSrpServicePort5:
index = 4;
break;
default:
VerifyOrQuit(false, "Unknown port");
break;
}
if (entry.mValueLength == strlen(kExpectedTxtValues[index]))
{
if (entry.mValueLength == 0 || memcmp(entry.mValue, kExpectedTxtValues[index], entry.mValueLength) == 0)
{
found = true;
break;
}
}
}
}
VerifyOrQuit(found, "thread-test key not found or value mismatch");
/**
* Step 8
* - Device: TD 1 (DUT)
* - Description (SRPC-3.5): Harness uses API to resolve for each of the N services the IPv6 address and port of
* the service. For each returned/resolved service, Harness uses the API to send a UDP packet to the service's
* IPv6 address at the respective returned port number.
* - Pass Criteria:
* - The DUT MUST transmit 5 UDP packets to ED_2, to the following port numbers, which may be in any order:
* - 1. 55551
* - 2. 55552
* - 3. 55553
* - 4. 55554
* - 5. 55555
* - The DUT MAY send to either the ML-EID or OMR address of ED 2.
*/
Log("Step 8: Resolve service address and send UDP packet to port %u", serviceInfo.mPort);
{
bool hasMeshLocal = false;
for (uint16_t i = 0; response.GetHostAddress(hostName, i, address, ttl) == kErrorNone; i++)
{
if (context->mNode->Get<Mle::Mle>().IsMeshLocalAddress(address))
{
hasMeshLocal = true;
break;
}
}
if (!hasMeshLocal)
{
// If no mesh-local address was found, get the first address.
// This will fail and quit if there are no addresses at all.
SuccessOrQuit(response.GetHostAddress(hostName, 0, address, ttl));
}
}
messageInfo.SetPeerAddr(address);
messageInfo.SetPeerPort(serviceInfo.mPort);
message = context->mNode->Get<Ip6::Udp>().NewMessage();
VerifyOrQuit(message != nullptr);
SuccessOrQuit(message->AppendBytes("hello", 5));
SuccessOrQuit(context->mNode->Get<Ip6::Udp>().SendDatagram(*message, messageInfo));
context->mUdpCount++;
}
void HandleBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext)
{
TestContext *context = static_cast<TestContext *>(aContext);
const Dns::Client::BrowseResponse &response = *static_cast<const Dns::Client::BrowseResponse *>(aResponse);
Dns::Name::LabelBuffer label;
if (aError != OT_ERROR_NONE)
{
Log("HandleBrowseResponse error: %s", ErrorToString(aError));
SuccessOrQuit(aError);
}
for (uint16_t index = 0; response.GetServiceInstance(index, label, sizeof(label)) == kErrorNone; index++)
{
context->mBrowseCount++;
Log("Step 5/6: Found service instance %s, starting resolve", label);
SuccessOrQuit(context->mNode->Get<Dns::Client>().ResolveService(label, kSrpFullServiceType,
HandleResolveResponse, context));
}
}
void Test_1_3_SRPC_TC_5(const char *aJsonFileName)
{
/**
* 3.5. [1.3] [CERT] [COMPONENT] DNS-SD client discovers services
*
* 3.5.1. Purpose
* To test the following:
* - DNS-SD client discovers services and sends out correctly formatted DNS query
* - Client handling multiple service responses
* - Client handling of both mesh-local discovered services and off-mesh services
* - Note: Thread Product DUTs must skip this test.
*
* 3.5.2. Topology
* - BR 1-Border Router Reference Device and Leader
* - TD_1 (DUT)-Thread End Device or Thread Router DUT (Component DUT)
* - ED 2-Thread Reference End Device
* - Eth 1-Host Reference Device on AIL
*
* Spec Reference | V1.1 Section | V1.3.0 Section
* -----------------|--------------|---------------
* SRP Client | N/A | 2.3.2
*/
Core nexus;
Ip6::Address addresses[3];
Node &br1 = nexus.CreateNode();
Node &td1 = nexus.CreateNode();
Node &ed2 = nexus.CreateNode();
Node &eth1 = nexus.CreateNode();
br1.SetName("BR_1");
td1.SetName("TD_1");
ed2.SetName("ED_2");
eth1.SetName("Eth_1");
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote));
Log("---------------------------------------------------------------------------------------");
Log("Step 1: Device: BR 1. ED 2, Eth 1 Description (SRPC-3.5): Enable");
/**
* Step 1
* - Device: BR 1. ED 2, Eth 1
* - Description (SRPC-3.5): Enable
* - Pass Criteria:
* - N/A
*/
br1.Form();
nexus.AdvanceTime(kFormNetworkTime);
ed2.Join(br1, Node::kAsFed);
nexus.AdvanceTime(kJoinNetworkTime);
eth1.mInfraIf.Init(eth1);
Log("---------------------------------------------------------------------------------------");
Log("Step 2: Device: BR 1 Description (SRPC-3.5): Automatically adds its SRP Server information");
/**
* Step 2
* - Device: BR 1
* - Description (SRPC-3.5): Automatically adds its SRP Server information in the DNS/SRP Unicast Dataset in the
* Thread Network Data. Automatically configures an OMR prefix in Thread Network Data.
* - Pass Criteria:
* - N/A
*/
br1.Get<BorderRouter::InfraIf>().Init(kInfraIfIndex, true);
br1.Get<BorderRouter::RoutingManager>().Init();
SuccessOrQuit(br1.Get<BorderRouter::RoutingManager>().SetEnabled(true));
SuccessOrQuit(br1.Get<Srp::Server>().SetAddressMode(Srp::Server::kAddressModeUnicast));
br1.Get<Srp::Server>().SetEnabled(true);
nexus.AdvanceTime(kStabilizationTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 3: Device: ED 2 Description (SRPC-3.5): Harness instructs device to register multiple N=5 services");
/**
* Step 3
* - Device: ED 2
* - Description (SRPC-3.5): Harness instructs device to register multiple N=5 services, each with TXT record per
* DNS-SD key/value formatting:
* - service-test-1. thread-test._udp ( SRV 8 8 55551 host-test-2 TXT dummy=abcd; thread-test=a49 )
* - service-test-2._thread-test._udp ( SRV 8 1 55552 host-test-2 TXT a=2; thread-test=b50_test )
* - service-test-3. thread-test._udp ( SRV 1 55553 host-test-2 TXT a_dummy=42; thread-test= )
* - service-test-4. thread-test._udp ( SRV 1 1 55554 host-test-2 TXT thread-test=1;ignorethis; thread-test= )
* - service-test-5. thread-test.udp ( SRV 2 2 55555 host-test-2 TXT a=88;thread- THREAD-TEST=C51; ignorethis;
* thread-test )
* - host-test-2 AAAA <non-routable address 2001::1234>
* - host-test-2 AAAA <OMR address of ED 2>
* - host-test-2 AAAA <ML-EID address of ED_2>
* - with the following options: Update Lease Option Lease: 5 minutes Key Lease: 10 minutes.
* - Pass Criteria:
* - N/A
*/
ed2.Get<Srp::Client>().EnableAutoStartMode(nullptr, nullptr);
addresses[0] = ed2.FindGlobalAddress();
addresses[1] = ed2.Get<Mle::Mle>().GetMeshLocalEid();
SuccessOrQuit(addresses[2].FromString("2001::1234"));
SuccessOrQuit(ed2.Get<Srp::Client>().SetHostAddresses(addresses, 3));
SuccessOrQuit(ed2.Get<Srp::Client>().SetHostName(kSrpHostName));
ed2.Get<Srp::Client>().SetLeaseInterval(kSrpLease5m);
ed2.Get<Srp::Client>().SetKeyLeaseInterval(kSrpKeyLease10m);
Srp::Client::Service services[5];
Dns::TxtEntry txtEntries[5][5];
ClearAllBytes(services);
ClearAllBytes(txtEntries);
// Service 1
services[0].mName = kSrpServiceType;
services[0].mInstanceName = "service-test-1";
services[0].mPort = kSrpServicePort1;
services[0].mPriority = 8;
services[0].mWeight = 8;
txtEntries[0][0].mKey = "dummy";
txtEntries[0][0].mValue = reinterpret_cast<const uint8_t *>("abcd");
txtEntries[0][0].mValueLength = 4;
txtEntries[0][1].mKey = "thread-test";
txtEntries[0][1].mValue = reinterpret_cast<const uint8_t *>("a49");
txtEntries[0][1].mValueLength = 3;
services[0].mTxtEntries = txtEntries[0];
services[0].mNumTxtEntries = 2;
SuccessOrQuit(services[0].Init());
SuccessOrQuit(ed2.Get<Srp::Client>().AddService(services[0]));
// Service 2
services[1].mName = kSrpServiceType;
services[1].mInstanceName = "service-test-2";
services[1].mPort = kSrpServicePort2;
services[1].mPriority = 8;
services[1].mWeight = 1;
txtEntries[1][0].mKey = "a";
txtEntries[1][0].mValue = reinterpret_cast<const uint8_t *>("2");
txtEntries[1][0].mValueLength = 1;
txtEntries[1][1].mKey = "thread-test";
txtEntries[1][1].mValue = reinterpret_cast<const uint8_t *>("b50_test");
txtEntries[1][1].mValueLength = 8;
services[1].mTxtEntries = txtEntries[1];
services[1].mNumTxtEntries = 2;
SuccessOrQuit(services[1].Init());
SuccessOrQuit(ed2.Get<Srp::Client>().AddService(services[1]));
// Service 3
services[2].mName = kSrpServiceType;
services[2].mInstanceName = "service-test-3";
services[2].mPort = kSrpServicePort3;
services[2].mPriority = 1;
services[2].mWeight = 0;
txtEntries[2][0].mKey = "a_dummy";
txtEntries[2][0].mValue = reinterpret_cast<const uint8_t *>("42");
txtEntries[2][0].mValueLength = 2;
txtEntries[2][1].mKey = "thread-test";
txtEntries[2][1].mValue = reinterpret_cast<const uint8_t *>("");
txtEntries[2][1].mValueLength = 0;
services[2].mTxtEntries = txtEntries[2];
services[2].mNumTxtEntries = 2;
SuccessOrQuit(services[2].Init());
SuccessOrQuit(ed2.Get<Srp::Client>().AddService(services[2]));
// Service 4
services[3].mName = kSrpServiceType;
services[3].mInstanceName = "service-test-4";
services[3].mPort = kSrpServicePort4;
services[3].mPriority = 1;
services[3].mWeight = 1;
txtEntries[3][0].mKey = "thread-test";
txtEntries[3][0].mValue = reinterpret_cast<const uint8_t *>("1");
txtEntries[3][0].mValueLength = 1;
txtEntries[3][1].mKey = "ignorethis";
txtEntries[3][1].mValue = nullptr;
txtEntries[3][1].mValueLength = 0;
txtEntries[3][2].mKey = "thread-test";
txtEntries[3][2].mValue = reinterpret_cast<const uint8_t *>("");
txtEntries[3][2].mValueLength = 0;
services[3].mTxtEntries = txtEntries[3];
services[3].mNumTxtEntries = 3;
SuccessOrQuit(services[3].Init());
SuccessOrQuit(ed2.Get<Srp::Client>().AddService(services[3]));
// Service 5
services[4].mName = kSrpServiceType;
services[4].mInstanceName = "service-test-5";
services[4].mPort = kSrpServicePort5;
services[4].mPriority = 2;
services[4].mWeight = 2;
txtEntries[4][0].mKey = "a";
txtEntries[4][0].mValue = reinterpret_cast<const uint8_t *>("88");
txtEntries[4][0].mValueLength = 2;
txtEntries[4][1].mKey = "thread-";
txtEntries[4][1].mValue = nullptr;
txtEntries[4][1].mValueLength = 0;
txtEntries[4][2].mKey = "THREAD-TEST";
txtEntries[4][2].mValue = reinterpret_cast<const uint8_t *>("C51");
txtEntries[4][2].mValueLength = 3;
txtEntries[4][3].mKey = "ignorethis";
txtEntries[4][3].mValue = nullptr;
txtEntries[4][3].mValueLength = 0;
txtEntries[4][4].mKey = "thread-test";
txtEntries[4][4].mValue = nullptr;
txtEntries[4][4].mValueLength = 0;
services[4].mTxtEntries = txtEntries[4];
services[4].mNumTxtEntries = 5;
SuccessOrQuit(services[4].Init());
SuccessOrQuit(ed2.Get<Srp::Client>().AddService(services[4]));
nexus.AdvanceTime(kSrpRegistrationTime);
{
Srp::Client::ItemState state = ed2.Get<Srp::Client>().GetHostInfo().GetState();
VerifyOrQuit(state == Srp::Client::kRegistered || state == Srp::Client::kToRefresh ||
state == Srp::Client::kRefreshing);
}
Log("---------------------------------------------------------------------------------------");
Log("Step 4: Device: TD 1 (DUT) Description (SRPC-3.5): Enable");
/**
* Step 4
* - Device: TD 1 (DUT)
* - Description (SRPC-3.5): Enable
* - Pass Criteria:
* - N/A
*/
td1.Join(br1, Node::kAsFed);
nexus.AdvanceTime(kJoinNetworkTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 5: Device: TD_1 (DUT) Description (SRPC-3.5): Harness instructs device to query for any services");
/**
* Step 5
* - Device: TD_1 (DUT)
* - Description (SRPC-3.5): Harness instructs device to query for any services of type
* "_thread-test._udp.default.service.arpa." using unicast DNS query QType=PTR
* - Pass Criteria:
* - The DUT MUST send a correctly formatted DNS query.
*/
TestContext context;
context.mNode = &td1;
context.mBrowseCount = 0;
context.mResolveCount = 0;
context.mUdpCount = 0;
Dns::Client::QueryConfig queryConfig = td1.Get<Dns::Client>().GetDefaultConfig();
AsCoreType(&queryConfig.mServerSockAddr).SetAddress(br1.Get<Mle::Mle>().GetMeshLocalEid());
queryConfig.mServerSockAddr.mPort = 53;
td1.Get<Dns::Client>().SetDefaultConfig(queryConfig);
SuccessOrQuit(td1.Get<Dns::Client>().Browse(kSrpFullServiceType, HandleBrowseResponse, &context));
nexus.AdvanceTime(kDnsQueryTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 6: Device: BR 1 Description (SRPC-3.5): Automatically responds with all N services");
/**
* Step 6
* - Device: BR 1
* - Description (SRPC-3.5): Automatically responds with all N services.
* - Pass Criteria:
* - Responds with DNS answer with all N services as indicated in step 3 in the Answer records section.
* - DNS response Rcode MUST be 'NoError' (0).
* - DNS response MUST contain N=5 answer records.
*/
nexus.AdvanceTime(kDnsQueryTime * 2);
// Verify all steps completed
VerifyOrQuit(context.mBrowseCount == 5);
VerifyOrQuit(context.mResolveCount == 5);
VerifyOrQuit(context.mUdpCount == 5);
nexus.AddTestVar("BR_1_MLEID_ADDR", br1.Get<Mle::Mle>().GetMeshLocalEid().ToString().AsCString());
nexus.AddTestVar("TD_1_MLEID_ADDR", td1.Get<Mle::Mle>().GetMeshLocalEid().ToString().AsCString());
nexus.AddTestVar("ED_2_MLEID_ADDR", ed2.Get<Mle::Mle>().GetMeshLocalEid().ToString().AsCString());
nexus.AddTestVar("ED_2_OMR_ADDR", ed2.FindGlobalAddress().ToString().AsCString());
nexus.SaveTestInfo(aJsonFileName);
}
} // namespace Nexus
} // namespace ot
int main(int argc, char *argv[])
{
ot::Nexus::Test_1_3_SRPC_TC_5((argc > 2) ? argv[2] : "test_1_3_SRPC_TC_5.json");
printf("All tests passed\n");
return 0;
}
+188
View File
@@ -0,0 +1,188 @@
#!/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
from pktverify.addrs import Ipv6Addr
def verify(pv):
# 3.5. [1.3] [CERT] [COMPONENT] DNS-SD client discovers services
#
# 3.5.1. Purpose
# To test the following:
# - DNS-SD client discovers services and sends out correctly formatted DNS query
# - Client handling multiple service responses
# - Client handling of both mesh-local discovered services and off-mesh services
# - Note: Thread Product DUTs must skip this test.
#
# 3.5.2. Topology
# - BR 1-Border Router Reference Device and Leader
# - TD_1 (DUT)-Thread End Device or Thread Router DUT (Component DUT)
# - ED 2-Thread Reference End Device
# - Eth 1-Host Reference Device on AIL
#
# Spec Reference | V1.1 Section | V1.3.0 Section
# -----------------|--------------|---------------
# SRP Client | N/A | 2.3.2
pkts = pv.pkts
pv.summary.show()
BR_1_MLEID = Ipv6Addr(pv.vars['BR_1_MLEID_ADDR'])
TD_1_MLEID = Ipv6Addr(pv.vars['TD_1_MLEID_ADDR'])
ED_2_MLEID = Ipv6Addr(pv.vars['ED_2_MLEID_ADDR'])
ED_2_OMR_ADDR = Ipv6Addr(pv.vars['ED_2_OMR_ADDR'])
SERVICE_TYPE = '_thread-test._udp.default.service.arpa'
DNS_PORT = 53
SRP_SERVICE_PORT1 = 55551
SRP_SERVICE_PORT2 = 55552
SRP_SERVICE_PORT3 = 55553
SRP_SERVICE_PORT4 = 55554
SRP_SERVICE_PORT5 = 55555
PORTS = [SRP_SERVICE_PORT1, SRP_SERVICE_PORT2, SRP_SERVICE_PORT3, SRP_SERVICE_PORT4, SRP_SERVICE_PORT5]
# Step 1
# - Device: BR 1. ED 2, Eth 1
# - Description (SRPC-3.5): Enable
# - Pass Criteria:
# - N/A
print("Step 1: BR 1, ED 2, Eth 1 enable.")
# Step 2
# - Device: BR 1
# - Description (SRPC-3.5): Automatically adds its SRP Server information in the DNS/SRP Unicast Dataset in the
# Thread Network Data. Automatically configures an OMR prefix in Thread Network Data.
# - Pass Criteria:
# - N/A
print("Step 2: BR 1 adds SRP Server information and OMR prefix.")
# Step 3
# - Device: ED 2
# - Description (SRPC-3.5): Harness instructs device to register multiple N=5 services, each with TXT record per
# DNS-SD key/value formatting:
# - service-test-1. thread-test._udp ( SRV 8 8 55551 host-test-2 TXT dummy=abcd; thread-test=a49 )
# - service-test-2._thread-test._udp ( SRV 8 1 55552 host-test-2 TXT a=2; thread-test=b50_test )
# - service-test-3. thread-test._udp ( SRV 1 55553 host-test-2 TXT a_dummy=42; thread-test= )
# - service-test-4. thread-test._udp ( SRV 1 1 55554 host-test-2 TXT thread-test=1;ignorethis; thread-test= )
# - service-test-5. thread-test.udp ( SRV 2 2 55555 host-test-2 TXT a=88;thread- THREAD-TEST=C51; ignorethis;
# thread-test )
# - host-test-2 AAAA <non-routable address 2001::1234>
# - host-test-2 AAAA <OMR address of ED 2>
# - host-test-2 AAAA <ML-EID address of ED_2>
# - with the following options: Update Lease Option Lease: 5 minutes Key Lease: 10 minutes.
# - Pass Criteria:
# - N/A
print("Step 3: ED 2 registers 5 services.")
# Step 4
# - Device: TD 1 (DUT)
# - Description (SRPC-3.5): Enable
# - Pass Criteria:
# - N/A
print("Step 4: TD 1 (DUT) enable.")
# Step 5
# - Device: TD_1 (DUT)
# - Description (SRPC-3.5): Harness instructs device to query for any services of type
# "_thread-test._udp.default.service.arpa." using unicast DNS query QType=PTR
# - Pass Criteria:
# - The DUT MUST send a correctly formatted DNS query.
print("Step 5: TD 1 (DUT) sends DNS query PTR.")
pkts.filter_ipv6_src(TD_1_MLEID).\
filter_ipv6_dst(BR_1_MLEID).\
filter(lambda p: p.udp.dstport == DNS_PORT).\
filter(lambda p: SERVICE_TYPE in verify_utils.as_list(p.dns.qry.name)).\
filter(lambda p: consts.DNS_TYPE_PTR in verify_utils.as_list(p.dns.qry.type)).\
must_next()
# Step 6
# - Device: BR 1
# - Description (SRPC-3.5): Automatically responds with all N services.
# - Pass Criteria:
# - Responds with DNS answer with all N services as indicated in step 3 in the Answer records section.
# - DNS response Rcode MUST be 'NoError' (0).
# - DNS response MUST contain N=5 answer records.
print("Step 6: BR 1 responds with all 5 services.")
pkts.filter_ipv6_src(BR_1_MLEID).\
filter_ipv6_dst(TD_1_MLEID).\
filter(lambda p: p.udp.srcport == DNS_PORT).\
filter(lambda p: p.dns.flags.response == 1).\
filter(lambda p: p.dns.flags.rcode == 0).\
filter(lambda p: len(verify_utils.as_list(p.dns.ptr.domain_name)) == 5).\
must_next()
# Step 7
# - Device: TD_1 (DUT)
# - Description (SRPC-3.5): Harness receives each of the N service responses via API from the DUT Component.
# For each service, it uses the API to extract the TXT record key "thread-test" and compares against the
# expected value.
# - Pass Criteria:
# - The DUT MUST return the correct values for the "thread-test" key as defined below for each of the
# respective N services:
# - 1. a49
# - 2. b50 test
# - 3. (empty string of 0 characters)
# - 4. 1
# - 5. C51
print("Step 7: TD 1 (DUT) extracts and compares TXT record values.")
# Step 8
# - Device: TD 1 (DUT)
# - Description (SRPC-3.5): Harness uses API to resolve for each of the N services the IPv6 address and port of
# the service. For each returned/resolved service, Harness uses the API to send a UDP packet to the service's
# IPv6 address at the respective returned port number.
# - Pass Criteria:
# - The DUT MUST transmit 5 UDP packets to ED_2, to the following port numbers, which may be in any order:
# - 1. 55551
# - 2. 55552
# - 3. 55553
# - 4. 55554
# - 5. 55555
# - The DUT MAY send to either the ML-EID or OMR address of ED 2.
print("Step 8: TD 1 (DUT) transmits 5 UDP packets to ED 2.")
for port in PORTS:
pkts.filter_ipv6_src(TD_1_MLEID).\
filter(lambda p: p.ipv6.dst == ED_2_MLEID or p.ipv6.dst == ED_2_OMR_ADDR).\
filter(lambda p: p.udp.dstport == port).\
must_next()
if __name__ == '__main__':
verify_utils.run_main(verify)