mirror of
https://github.com/espressif/openthread.git
synced 2026-11-10 16:31:11 +00:00
[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:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ð1 = 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;
|
||||
}
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user