mirror of
https://github.com/espressif/openthread.git
synced 2026-06-05 21:14:49 +00:00
[nexus] add 1.4 PIC-TC-1 test for DHCPv6-PD and DNS (#12859)
This commit adds a new Nexus test that implements the test specification in test-1-4-PIC-TC-1.md. The test verifies Border Router functionality including: - DHCPv6-PD client to obtain OMR prefix - Advertising route to OMR prefix on AIL (Stub Router) - DNS recursive resolver for public internet addresses - Connectivity (ICMPv6, UDP, TCP/HTTP) to internet and local servers New files: - tests/nexus/test_1_4_PIC_TC_1.cpp: C++ test execution - tests/nexus/verify_1_4_PIC_TC_1.py: Python pcap verification Nexus platform enhancements: - Enabled DHCPv6-PD client in openthread-core-nexus-config.h - Implemented DHCPv6-PD platform APIs in nexus_infra_if.cpp - Added RDNSS option to RA in nexus_infra_if.cpp - Improved packet delivery on infrastructure interface in nexus_core.cpp - Fixed upstream DNS query matching in nexus_dns.cpp
This commit is contained in:
@@ -296,6 +296,7 @@ ot_nexus_test(1_4_TREL_TC_6 "cert;nexus")
|
|||||||
ot_nexus_test(1_4_DNS_TC_1 "cert;nexus")
|
ot_nexus_test(1_4_DNS_TC_1 "cert;nexus")
|
||||||
ot_nexus_test(1_4_DNS_TC_3 "cert;nexus")
|
ot_nexus_test(1_4_DNS_TC_3 "cert;nexus")
|
||||||
ot_nexus_test(1_4_DNS_TC_5 "cert;nexus")
|
ot_nexus_test(1_4_DNS_TC_5 "cert;nexus")
|
||||||
|
ot_nexus_test(1_4_PIC_TC_1 "cert;nexus")
|
||||||
ot_nexus_test(1_4_CS_TC_3 "cert;nexus")
|
ot_nexus_test(1_4_CS_TC_3 "cert;nexus")
|
||||||
|
|
||||||
# Misc tests
|
# Misc tests
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
#define OPENTHREAD_CONFIG_BORDER_AGENT_TXT_DATA_PARSER_ENABLE 1
|
#define OPENTHREAD_CONFIG_BORDER_AGENT_TXT_DATA_PARSER_ENABLE 1
|
||||||
#define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 1
|
#define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 1
|
||||||
#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE 1
|
#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE 1
|
||||||
#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_CLIENT_ENABLE 0
|
#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_CLIENT_ENABLE 1
|
||||||
#define OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE 1
|
#define OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE 1
|
||||||
#define OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE 1
|
#define OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE 1
|
||||||
#define OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE 1
|
#define OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE 1
|
||||||
|
|||||||
@@ -648,7 +648,10 @@ void Core::ProcessInfraIf(Node &aNode)
|
|||||||
|
|
||||||
if (!header.GetDestination().IsMulticast())
|
if (!header.GetDestination().IsMulticast())
|
||||||
{
|
{
|
||||||
targetNode = FindNodeByInfraIfAddress(header.GetDestination());
|
if (!IsThreadAddress(header.GetDestination()))
|
||||||
|
{
|
||||||
|
targetNode = FindNodeByAddress(header.GetDestination());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Node &rxNode : mNodes)
|
for (Node &rxNode : mNodes)
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ void UpstreamDns::StartUpstreamQuery(UpstreamQueryTransaction &aTxn, const Messa
|
|||||||
ot::Dns::Header dnsHeader;
|
ot::Dns::Header dnsHeader;
|
||||||
PendingQuery *pendingQuery;
|
PendingQuery *pendingQuery;
|
||||||
Message *message = nullptr;
|
Message *message = nullptr;
|
||||||
|
|
||||||
// We use kDnsPort (53) as both source and destination port for upstream queries to simplify interception
|
// We use kDnsPort (53) as both source and destination port for upstream queries to simplify interception
|
||||||
// and response matching in this simulation environment.
|
// and response matching in this simulation environment.
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,8 @@
|
|||||||
|
|
||||||
#include "nexus_infra_if.hpp"
|
#include "nexus_infra_if.hpp"
|
||||||
|
|
||||||
#include "nexus_core.hpp"
|
#include <openthread/platform/infra_if.h>
|
||||||
|
|
||||||
#include "nexus_node.hpp"
|
#include "nexus_node.hpp"
|
||||||
|
|
||||||
namespace ot {
|
namespace ot {
|
||||||
@@ -39,6 +40,8 @@ InfraIf::InfraIf(Instance &aInstance)
|
|||||||
, mIfIndex(0)
|
, mIfIndex(0)
|
||||||
, mUdpHook(nullptr)
|
, mUdpHook(nullptr)
|
||||||
, mHasRioPrefix(false)
|
, mHasRioPrefix(false)
|
||||||
|
, mDhcp6PdListening(false)
|
||||||
|
, mIsDnsServer(false)
|
||||||
, mRaTimer(aInstance)
|
, mRaTimer(aInstance)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -196,6 +199,13 @@ void InfraIf::SendRouterAdvertisement(const Ip6::Address &aDestination,
|
|||||||
SuccessOrQuit(ra.AppendRouteInfoOption(*aRioPrefix, 1800, NetworkData::kRoutePreferenceMedium));
|
SuccessOrQuit(ra.AppendRouteInfoOption(*aRioPrefix, 1800, NetworkData::kRoutePreferenceMedium));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ETH_2 (Node 4) acts as DNS server
|
||||||
|
if (mIsDnsServer)
|
||||||
|
{
|
||||||
|
const Ip6::Address dnsServerAddr = GetLinkLocalAddress();
|
||||||
|
SuccessOrQuit(ra.AppendRecursiveDnsServerOption(&dnsServerAddr, 1, 1800));
|
||||||
|
}
|
||||||
|
|
||||||
ra.GetAsPacket(packet);
|
ra.GetAsPacket(packet);
|
||||||
|
|
||||||
SendIcmp6Nd(aDestination, packet.GetBytes(), packet.GetLength());
|
SendIcmp6Nd(aDestination, packet.GetBytes(), packet.GetLength());
|
||||||
@@ -406,6 +416,13 @@ void InfraIf::SendUdp(const Ip6::Address &aSrcAddress,
|
|||||||
mPendingTxQueue.Enqueue(aPayload);
|
mPendingTxQueue.Enqueue(aPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InfraIf::SetDhcp6ListeningEnabled(bool aEnable) { mDhcp6PdListening = aEnable; }
|
||||||
|
|
||||||
|
void InfraIf::SendDhcp6(Message &aMessage, const Ip6::Address &aDestAddress)
|
||||||
|
{
|
||||||
|
SendUdp(SelectSourceAddress(aDestAddress), aDestAddress, Dhcp6::kDhcpClientPort, Dhcp6::kDhcpServerPort, aMessage);
|
||||||
|
}
|
||||||
|
|
||||||
void InfraIf::Receive(Message &aMessage)
|
void InfraIf::Receive(Message &aMessage)
|
||||||
{
|
{
|
||||||
Ip6::Headers headers;
|
Ip6::Headers headers;
|
||||||
@@ -413,6 +430,16 @@ void InfraIf::Receive(Message &aMessage)
|
|||||||
aMessage.SetOffset(0);
|
aMessage.SetOffset(0);
|
||||||
SuccessOrExit(headers.ParseFrom(aMessage));
|
SuccessOrExit(headers.ParseFrom(aMessage));
|
||||||
|
|
||||||
|
if (mDhcp6PdListening && headers.IsUdp() && headers.GetDestinationPort() == Dhcp6::kDhcpClientPort)
|
||||||
|
{
|
||||||
|
Message *payload = aMessage.Clone<kNoReservedHeader>();
|
||||||
|
|
||||||
|
VerifyOrQuit(payload != nullptr);
|
||||||
|
payload->RemoveHeader(sizeof(Ip6::Header) + sizeof(Ip6::Udp::Header));
|
||||||
|
otPlatInfraIfDhcp6PdClientHandleReceived(&GetInstance(), payload, mIfIndex);
|
||||||
|
ExitNow();
|
||||||
|
}
|
||||||
|
|
||||||
if (headers.IsIcmp6() && (headers.GetDestinationAddress() == Ip6::Address::GetLinkLocalAllNodesMulticast() ||
|
if (headers.IsIcmp6() && (headers.GetDestinationAddress() == Ip6::Address::GetLinkLocalAllNodesMulticast() ||
|
||||||
headers.GetDestinationAddress() == Ip6::Address::GetLinkLocalAllRoutersMulticast() ||
|
headers.GetDestinationAddress() == Ip6::Address::GetLinkLocalAllRoutersMulticast() ||
|
||||||
HasAddress(headers.GetDestinationAddress())))
|
HasAddress(headers.GetDestinationAddress())))
|
||||||
@@ -648,6 +675,18 @@ otError otPlatGetInfraIfLinkLayerAddress(otInstance *aInstanc
|
|||||||
return OT_ERROR_NONE;
|
return OT_ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void otPlatInfraIfDhcp6PdClientSetListeningEnabled(otInstance *aInstance, bool aEnable, uint32_t aInfraIfIndex)
|
||||||
|
{
|
||||||
|
OT_UNUSED_VARIABLE(aInfraIfIndex);
|
||||||
|
AsNode(aInstance).mInfraIf.SetDhcp6ListeningEnabled(aEnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void otPlatInfraIfDhcp6PdClientSend(otInstance *aInstance, otMessage *aMessage, otIp6Address *aDest, uint32_t aIfIndex)
|
||||||
|
{
|
||||||
|
OT_UNUSED_VARIABLE(aIfIndex);
|
||||||
|
AsNode(aInstance).mInfraIf.SendDhcp6(AsCoreType(aMessage), AsCoreType(aDest));
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
||||||
} // namespace Nexus
|
} // namespace Nexus
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ public:
|
|||||||
|
|
||||||
void SetEchoReplyHandler(EchoReplyHandler aHandler, void *aContext) { mEchoReplyCallback.Set(aHandler, aContext); }
|
void SetEchoReplyHandler(EchoReplyHandler aHandler, void *aContext) { mEchoReplyCallback.Set(aHandler, aContext); }
|
||||||
|
|
||||||
|
void SetDhcp6ListeningEnabled(bool aEnable);
|
||||||
|
void SetIsDnsServer(bool aEnable) { mIsDnsServer = aEnable; }
|
||||||
|
void SendDhcp6(Message &aMessage, const Ip6::Address &aDestAddress);
|
||||||
|
|
||||||
typedef bool (*UdpHook)(Instance &aInstance, Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
|
typedef bool (*UdpHook)(Instance &aInstance, Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
|
||||||
void SetUdpHook(UdpHook aHook) { mUdpHook = aHook; }
|
void SetUdpHook(UdpHook aHook) { mUdpHook = aHook; }
|
||||||
|
|
||||||
@@ -108,6 +112,8 @@ private:
|
|||||||
Ip6::Prefix mPioPrefix;
|
Ip6::Prefix mPioPrefix;
|
||||||
Ip6::Prefix mRioPrefix;
|
Ip6::Prefix mRioPrefix;
|
||||||
bool mHasRioPrefix;
|
bool mHasRioPrefix;
|
||||||
|
bool mDhcp6PdListening;
|
||||||
|
bool mIsDnsServer;
|
||||||
|
|
||||||
using RaTimer = TimerMilliIn<InfraIf, &InfraIf::HandleRaTimer>;
|
using RaTimer = TimerMilliIn<InfraIf, &InfraIf::HandleRaTimer>;
|
||||||
|
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ DEFAULT_TESTS=(
|
|||||||
"1_4_DNS_TC_1"
|
"1_4_DNS_TC_1"
|
||||||
"1_4_DNS_TC_3"
|
"1_4_DNS_TC_3"
|
||||||
"1_4_DNS_TC_5"
|
"1_4_DNS_TC_5"
|
||||||
|
"1_4_PIC_TC_1"
|
||||||
"1_4_CS_TC_3"
|
"1_4_CS_TC_3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,581 @@
|
|||||||
|
/*
|
||||||
|
* 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 "common/offset_range.hpp"
|
||||||
|
#include "net/dhcp6_types.hpp"
|
||||||
|
#include "net/dns_types.hpp"
|
||||||
|
#include "net/tcp6.hpp"
|
||||||
|
#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 as a child and upgrade to a router, in milliseconds.
|
||||||
|
*/
|
||||||
|
static constexpr uint32_t kJoinNetworkTime = 200 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time to advance for the BR to perform automatic actions (DHCPv6-PD, RA), in milliseconds.
|
||||||
|
*/
|
||||||
|
static constexpr uint32_t kBrActionTime = 30 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time to advance for the DNS query and response, in milliseconds.
|
||||||
|
*/
|
||||||
|
static constexpr uint32_t kDnsTime = 5 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time to advance for the ping response, in milliseconds.
|
||||||
|
*/
|
||||||
|
static constexpr uint32_t kPingTime = 2 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time to advance for the TCP connection and data transfer, in milliseconds.
|
||||||
|
*/
|
||||||
|
static constexpr uint32_t kTcpTime = 10 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DHCPv6-PD Server Port.
|
||||||
|
*/
|
||||||
|
static constexpr uint16_t kDhcp6ServerPort = 547;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DHCPv6-PD Client Port.
|
||||||
|
*/
|
||||||
|
static constexpr uint16_t kDhcp6ClientPort = 546;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DNS Server Port.
|
||||||
|
*/
|
||||||
|
static constexpr uint16_t kDnsPort = 53;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP Server Port.
|
||||||
|
*/
|
||||||
|
static constexpr uint16_t kHttpPort = 80;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Echo Request identifier.
|
||||||
|
*/
|
||||||
|
static constexpr uint16_t kEchoIdentifier = 0x1234;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Echo Request payload size.
|
||||||
|
*/
|
||||||
|
static constexpr uint16_t kEchoPayloadSize = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internet Server Address.
|
||||||
|
*/
|
||||||
|
static const char kInternetServerAddr[] = "2002:1234::1234";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegated Prefix.
|
||||||
|
*/
|
||||||
|
static const char kDelegatedPrefix[] = "2005:1234:abcd:1::/64";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eth_1 GUA address prefix.
|
||||||
|
*/
|
||||||
|
static const char kEth1Prefix[] = "2001:db8:1::/64";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infrastructure interface index.
|
||||||
|
*/
|
||||||
|
static constexpr uint32_t kInfraIfIndex = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DHCPv6 Server Hook to simulate DHCPv6-PD server and DNS server.
|
||||||
|
*/
|
||||||
|
bool HandleEth2Udp(Instance &aInstance, Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
|
||||||
|
{
|
||||||
|
Node ð2 = Node::From(&aInstance);
|
||||||
|
bool handled = false;
|
||||||
|
|
||||||
|
if (aMessageInfo.GetSockPort() == kDhcp6ServerPort)
|
||||||
|
{
|
||||||
|
Dhcp6::Header header;
|
||||||
|
SuccessOrQuit(aMessage.Read(aMessage.GetOffset(), header));
|
||||||
|
|
||||||
|
if (header.GetMsgType() == Dhcp6::kMsgTypeSolicit || header.GetMsgType() == Dhcp6::kMsgTypeRequest)
|
||||||
|
{
|
||||||
|
Message *reply = eth2.Get<MessagePool>().Allocate(Message::kTypeIp6);
|
||||||
|
VerifyOrQuit(reply != nullptr);
|
||||||
|
|
||||||
|
Dhcp6::Header replyHeader;
|
||||||
|
replyHeader.Clear();
|
||||||
|
replyHeader.SetMsgType((header.GetMsgType() == Dhcp6::kMsgTypeSolicit) ? Dhcp6::kMsgTypeAdvertise
|
||||||
|
: Dhcp6::kMsgTypeReply);
|
||||||
|
replyHeader.SetTransactionId(header.GetTransactionId());
|
||||||
|
SuccessOrQuit(reply->Append(replyHeader));
|
||||||
|
|
||||||
|
OffsetRange offsetRange;
|
||||||
|
Dhcp6::Option option;
|
||||||
|
uint32_t iaid = 0;
|
||||||
|
|
||||||
|
offsetRange.InitFromRange(aMessage.GetOffset() + sizeof(Dhcp6::Header), aMessage.GetLength());
|
||||||
|
|
||||||
|
while (offsetRange.Contains(sizeof(Dhcp6::Option)))
|
||||||
|
{
|
||||||
|
SuccessOrQuit(aMessage.Read(offsetRange.GetOffset(), option));
|
||||||
|
VerifyOrQuit(offsetRange.Contains(option.GetSize()));
|
||||||
|
|
||||||
|
if (option.GetCode() == Dhcp6::Option::kClientId)
|
||||||
|
{
|
||||||
|
SuccessOrQuit(reply->AppendBytesFromMessage(aMessage, offsetRange.GetOffset(), option.GetSize()));
|
||||||
|
}
|
||||||
|
else if (option.GetCode() == Dhcp6::Option::kIaPd)
|
||||||
|
{
|
||||||
|
Dhcp6::IaPdOption iaPdSolicit;
|
||||||
|
|
||||||
|
VerifyOrQuit(option.GetSize() >= sizeof(iaPdSolicit));
|
||||||
|
SuccessOrQuit(aMessage.Read(offsetRange.GetOffset(), iaPdSolicit));
|
||||||
|
iaid = iaPdSolicit.GetIaid();
|
||||||
|
}
|
||||||
|
offsetRange.AdvanceOffset(option.GetSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append Server ID (ETH_2 address)
|
||||||
|
SuccessOrQuit(Dhcp6::ServerIdOption::AppendWithEui64Duid(*reply, eth2.Get<Mac::Mac>().GetExtAddress()));
|
||||||
|
|
||||||
|
if (header.GetMsgType() == Dhcp6::kMsgTypeSolicit)
|
||||||
|
{
|
||||||
|
// Append Preference
|
||||||
|
Dhcp6::PreferenceOption preference;
|
||||||
|
preference.Init();
|
||||||
|
preference.SetPreference(255);
|
||||||
|
SuccessOrQuit(reply->Append(preference));
|
||||||
|
}
|
||||||
|
|
||||||
|
Dhcp6::IaPdOption iaPd;
|
||||||
|
iaPd.Init();
|
||||||
|
iaPd.SetIaid(iaid);
|
||||||
|
iaPd.SetT1(1000);
|
||||||
|
iaPd.SetT2(2000);
|
||||||
|
SuccessOrQuit(reply->Append(iaPd));
|
||||||
|
|
||||||
|
Dhcp6::IaPrefixOption prefixOption;
|
||||||
|
prefixOption.Init();
|
||||||
|
Ip6::Prefix prefix;
|
||||||
|
SuccessOrQuit(prefix.FromString(kDelegatedPrefix));
|
||||||
|
prefixOption.SetPrefix(prefix);
|
||||||
|
prefixOption.SetPreferredLifetime(3600);
|
||||||
|
prefixOption.SetValidLifetime(7200);
|
||||||
|
SuccessOrQuit(reply->Append(prefixOption));
|
||||||
|
|
||||||
|
Dhcp6::Option::UpdateOptionLengthInMessage(*reply,
|
||||||
|
reply->GetLength() - sizeof(iaPd) - sizeof(prefixOption));
|
||||||
|
|
||||||
|
eth2.mInfraIf.SendUdp(eth2.mInfraIf.GetLinkLocalAddress(), aMessageInfo.GetPeerAddr(), kDhcp6ServerPort,
|
||||||
|
kDhcp6ClientPort, *reply);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (aMessageInfo.GetSockPort() == kDnsPort)
|
||||||
|
{
|
||||||
|
Dns::Header header;
|
||||||
|
SuccessOrQuit(aMessage.Read(aMessage.GetOffset(), header));
|
||||||
|
|
||||||
|
if (header.GetType() == Dns::Header::kTypeQuery)
|
||||||
|
{
|
||||||
|
Message *reply = eth2.Get<MessagePool>().Allocate(Message::kTypeIp6);
|
||||||
|
VerifyOrQuit(reply != nullptr);
|
||||||
|
|
||||||
|
Dns::Header replyHeader;
|
||||||
|
replyHeader.SetMessageId(header.GetMessageId());
|
||||||
|
replyHeader.SetType(Dns::Header::kTypeResponse);
|
||||||
|
replyHeader.SetQueryType(Dns::Header::kQueryTypeStandard);
|
||||||
|
replyHeader.SetRecursionDesiredFlag();
|
||||||
|
replyHeader.SetRecursionAvailableFlag();
|
||||||
|
replyHeader.SetQuestionCount(1);
|
||||||
|
replyHeader.SetAnswerCount(1);
|
||||||
|
SuccessOrQuit(reply->Append(replyHeader));
|
||||||
|
|
||||||
|
uint16_t offset = aMessage.GetOffset() + sizeof(Dns::Header);
|
||||||
|
uint16_t questionLen = aMessage.GetLength() - offset;
|
||||||
|
SuccessOrQuit(reply->AppendBytesFromMessage(aMessage, offset, questionLen));
|
||||||
|
|
||||||
|
SuccessOrQuit(Dns::Name::AppendName("threadgroup.org", *reply));
|
||||||
|
Dns::ResourceRecord rr;
|
||||||
|
rr.SetType(Dns::ResourceRecord::kTypeAaaa);
|
||||||
|
rr.SetClass(Dns::ResourceRecord::kClassInternet);
|
||||||
|
rr.SetTtl(3600);
|
||||||
|
rr.SetLength(sizeof(Ip6::Address));
|
||||||
|
SuccessOrQuit(reply->Append(rr));
|
||||||
|
Ip6::Address addr;
|
||||||
|
SuccessOrQuit(addr.FromString(kInternetServerAddr));
|
||||||
|
SuccessOrQuit(reply->Append(addr));
|
||||||
|
|
||||||
|
eth2.mInfraIf.SendUdp(eth2.mInfraIf.GetLinkLocalAddress(), aMessageInfo.GetPeerAddr(), kDnsPort,
|
||||||
|
aMessageInfo.GetPeerPort(), *reply);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Test_1_4_PIC_TC_1(void)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 10.1. IPv6 Connectivity using DHCPv6-PD delegated OMR prefix
|
||||||
|
*
|
||||||
|
* 10.1.1. Purpose
|
||||||
|
* - To verify that BR DUT:
|
||||||
|
* - uses DHCPv6-PD client functionality to obtain an OMR prefix for its Thread Network from the upstream CPE
|
||||||
|
* router
|
||||||
|
* - Offers IPv6 public internet connectivity to/from Thread Devices that configured an OMR address
|
||||||
|
* - Offers IPv6 local network connectivity to/from Thread Devices that configured an OMR address
|
||||||
|
* - Operates DNS recursive resolver to look up IPv6 server addresses on public internet
|
||||||
|
*
|
||||||
|
* 10.1.2. Topology
|
||||||
|
* - BR_1 (DUT) - Border Router
|
||||||
|
* - Router_1 - Thread Router Reference Device, attached to BR_1
|
||||||
|
* - ED_1 - Thread Reference Device, End Device (e.g. FED/REED) role, attached to Router_1
|
||||||
|
* - Eth_1 - Adjacent Infrastructure Link Linux Reference Device
|
||||||
|
* - Has HTTP web server with resource ‘/test2.html’.
|
||||||
|
* - Eth_2 - Adjacent Infrastructure Link SPIFF Reference Device
|
||||||
|
*/
|
||||||
|
|
||||||
|
Core nexus;
|
||||||
|
|
||||||
|
Node &br1 = nexus.CreateNode();
|
||||||
|
Node &r1 = nexus.CreateNode();
|
||||||
|
Node &ed1 = nexus.CreateNode();
|
||||||
|
Node ð1 = nexus.CreateNode();
|
||||||
|
Node ð2 = nexus.CreateNode();
|
||||||
|
|
||||||
|
br1.SetName("BR_1");
|
||||||
|
r1.SetName("ROUTER_1");
|
||||||
|
ed1.SetName("ED_1");
|
||||||
|
eth1.SetName("ETH_1");
|
||||||
|
eth2.SetName("ETH_2");
|
||||||
|
|
||||||
|
nexus.AdvanceTime(0);
|
||||||
|
|
||||||
|
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote));
|
||||||
|
|
||||||
|
Log("Step 1: Enable Eth_1 and Eth_2");
|
||||||
|
/**
|
||||||
|
* Step 1
|
||||||
|
* - Device: Eth_1, Eth_2
|
||||||
|
* - Description (PIC-10.1): Enable
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - N/A
|
||||||
|
*/
|
||||||
|
eth2.mInfraIf.SetIsDnsServer(true);
|
||||||
|
|
||||||
|
{
|
||||||
|
Ip6::Address internetServer;
|
||||||
|
SuccessOrQuit(internetServer.FromString(kInternetServerAddr));
|
||||||
|
eth2.mInfraIf.AddAddress(internetServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ip6::Prefix eth1Prefix;
|
||||||
|
SuccessOrQuit(eth1Prefix.FromString(kEth1Prefix));
|
||||||
|
eth2.mInfraIf.StartRouterAdvertisement(eth1Prefix);
|
||||||
|
eth2.mInfraIf.SetUdpHook(HandleEth2Udp);
|
||||||
|
|
||||||
|
Log("Step 2: Eth_2 default configuration");
|
||||||
|
/**
|
||||||
|
* Step 2
|
||||||
|
* - Device: Eth_2 (SPIFF)
|
||||||
|
* - Description (PIC-10.1): Harness does not configure the device, other than the default configuration. Note:
|
||||||
|
* in previous versions of the test plan, explicit configuration was provided in this step. Note: Eth_1 will
|
||||||
|
* automatically configure a GUA using SLAAC and the RA-advertised prefix.
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - N/A
|
||||||
|
*/
|
||||||
|
nexus.AdvanceTime(kPingTime);
|
||||||
|
|
||||||
|
Log("Step 3: Enable BR_1, Router_1, ED_1");
|
||||||
|
/**
|
||||||
|
* Step 3
|
||||||
|
* - Device: BR_1 (DUT), Router_1, ED_1
|
||||||
|
* - Description (PIC-10.1): Enable
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - N/A
|
||||||
|
*/
|
||||||
|
br1.AllowList(r1);
|
||||||
|
r1.AllowList(br1);
|
||||||
|
r1.AllowList(ed1);
|
||||||
|
ed1.AllowList(r1);
|
||||||
|
|
||||||
|
br1.Form();
|
||||||
|
nexus.AdvanceTime(kFormNetworkTime);
|
||||||
|
|
||||||
|
br1.Get<BorderRouter::InfraIf>().Init(kInfraIfIndex, true);
|
||||||
|
br1.Get<BorderRouter::RoutingManager>().Init();
|
||||||
|
br1.Get<BorderRouter::RoutingManager>().SetDhcp6PdEnabled(true);
|
||||||
|
SuccessOrQuit(br1.Get<BorderRouter::RoutingManager>().SetEnabled(true));
|
||||||
|
|
||||||
|
SuccessOrQuit(br1.Get<Dns::ServiceDiscovery::Server>().Start());
|
||||||
|
br1.Get<Dns::ServiceDiscovery::Server>().SetUpstreamQueryEnabled(true);
|
||||||
|
|
||||||
|
r1.Join(br1);
|
||||||
|
nexus.AdvanceTime(kJoinNetworkTime);
|
||||||
|
|
||||||
|
ed1.Join(r1);
|
||||||
|
nexus.AdvanceTime(kJoinNetworkTime);
|
||||||
|
|
||||||
|
Log("Step 4: BR_1 obtains OMR prefix via DHCPv6-PD");
|
||||||
|
/**
|
||||||
|
* Step 4
|
||||||
|
* - Device: BR_1 (DUT)
|
||||||
|
* - Description (PIC-10.1): Automatically uses DHCPv6-PD client function to obtain a delegated prefix from the
|
||||||
|
* DHCPv6 server. It configures this prefix as the OMR prefix.
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - An OMR prefix OMR_1 MUST appear in Thread Network Data in a Prefix TLV:
|
||||||
|
* - It MUST be subprefix of 2005:1234:abcd:0::/56.
|
||||||
|
* - It MUST be a /64 prefix
|
||||||
|
* - Border Router sub-TLV:
|
||||||
|
* - Prf bits = P_preference = '00' (medium)
|
||||||
|
* - R = P_default = '1'
|
||||||
|
*/
|
||||||
|
nexus.AdvanceTime(kBrActionTime);
|
||||||
|
|
||||||
|
Log("Step 4b: BR_1 advertises route to OMR prefix on AIL");
|
||||||
|
/**
|
||||||
|
* Step 4b
|
||||||
|
* - Device: BR_1 (DUT)
|
||||||
|
* - Description (PIC-10.1): Automatically advertises the route to its OMR prefix on the AIL, as a stub router
|
||||||
|
* (aka IETF SNAC router). Note: see 9.6.2 for Stub Router / SNAC Router flag detail.The flag is bit 6 of RA
|
||||||
|
* flags as specified by the TEMPORARY allocation made by IANA (link).
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - The DUT MUST advertise the route in multicast ND RA messages:
|
||||||
|
* - IPv6 destination MUST be ff02::1
|
||||||
|
* - SNAC Router flag set to '1'
|
||||||
|
* - Route Information Option (RIO)
|
||||||
|
* - Prefix: OMR_1
|
||||||
|
* - Prf bits: '00' or '11'
|
||||||
|
* - Route Lifetime > 0
|
||||||
|
*/
|
||||||
|
|
||||||
|
Log("Step 5: ED_1 performs DNS query for threadgroup.org");
|
||||||
|
/**
|
||||||
|
* Step 5
|
||||||
|
* - Device: ED_1
|
||||||
|
* - Description (PIC-10.1): Harness instructs device to perform DNS query QType=AAAA, name “threadgroup.org”.
|
||||||
|
* Automatically, the DNS query gets routed to BR_1.
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - N/A
|
||||||
|
*/
|
||||||
|
Ip6::Address dnsServerAddr;
|
||||||
|
dnsServerAddr = br1.Get<Mle::Mle>().GetMeshLocalEid();
|
||||||
|
|
||||||
|
Ip6::Udp::Socket dnsSocket(ed1, nullptr, nullptr);
|
||||||
|
SuccessOrQuit(dnsSocket.Open(Ip6::kNetifThreadInternal));
|
||||||
|
|
||||||
|
Message *dnsQuery = dnsSocket.NewMessage();
|
||||||
|
VerifyOrQuit(dnsQuery != nullptr);
|
||||||
|
|
||||||
|
Dns::Header dnsHeader;
|
||||||
|
dnsHeader.SetType(Dns::Header::kTypeQuery);
|
||||||
|
dnsHeader.SetQuestionCount(1);
|
||||||
|
dnsHeader.SetRecursionDesiredFlag();
|
||||||
|
SuccessOrQuit(dnsQuery->Append(dnsHeader));
|
||||||
|
SuccessOrQuit(Dns::Name::AppendName("threadgroup.org", *dnsQuery));
|
||||||
|
Dns::Question dnsQuestion(Dns::ResourceRecord::kTypeAaaa);
|
||||||
|
SuccessOrQuit(dnsQuery->Append(dnsQuestion));
|
||||||
|
|
||||||
|
Ip6::MessageInfo dnsMessageInfo;
|
||||||
|
dnsMessageInfo.SetPeerAddr(dnsServerAddr);
|
||||||
|
dnsMessageInfo.SetPeerPort(kDnsPort);
|
||||||
|
SuccessOrQuit(dnsSocket.SendTo(*dnsQuery, dnsMessageInfo));
|
||||||
|
SuccessOrQuit(dnsSocket.Close());
|
||||||
|
|
||||||
|
Log("Step 6: BR_1 processes DNS query upstream");
|
||||||
|
/**
|
||||||
|
* Step 6
|
||||||
|
* - Device: BR_1 (DUT)
|
||||||
|
* - Description (PIC-10.1): Automatically processes the DNS query by requesting upstream to the Eth_2 DNS
|
||||||
|
* server. Then, it responds back with the answer to ED_1.
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - N/A
|
||||||
|
*/
|
||||||
|
nexus.AdvanceTime(kDnsTime);
|
||||||
|
|
||||||
|
Log("Step 7: ED_1 receives DNS result");
|
||||||
|
/**
|
||||||
|
* Step 7
|
||||||
|
* - Device: ED_1
|
||||||
|
* - Description (PIC-10.1): Successfully receives DNS query result: threadgroup.org AAAA 2002:1234::1234
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - ED_1 MUST receive DNS query answer from DUT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Log("Step 8: ED_1 pings internet server");
|
||||||
|
/**
|
||||||
|
* Step 8
|
||||||
|
* - Device: ED_1
|
||||||
|
* - Description (PIC-10.1): Harness instructs device to send ICMpv6 ping request to internet server, using
|
||||||
|
* resolved address above. It is routed via the DUT to the Eth_2 simulated network.
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - ED_1 MUST receive ICMPv6 ping response from simulated server.
|
||||||
|
*/
|
||||||
|
Ip6::Address internetServer;
|
||||||
|
SuccessOrQuit(internetServer.FromString(kInternetServerAddr));
|
||||||
|
ed1.SendEchoRequest(internetServer, kEchoIdentifier, kEchoPayloadSize);
|
||||||
|
nexus.AdvanceTime(kPingTime);
|
||||||
|
|
||||||
|
Log("Step 8a: ED_1 sends UDP ping to internet server");
|
||||||
|
/**
|
||||||
|
* Step 8a
|
||||||
|
* - Device: ED_1
|
||||||
|
* - Description (PIC-10.1): Repeats same using a UDP ping.
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - ED_1 MUST receive UDP ping response from simulated server.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
Ip6::Udp::Socket udpSocket(ed1, nullptr, nullptr);
|
||||||
|
SuccessOrQuit(udpSocket.Open(Ip6::kNetifThreadInternal));
|
||||||
|
Message *udpMsg = udpSocket.NewMessage();
|
||||||
|
VerifyOrQuit(udpMsg != nullptr);
|
||||||
|
SuccessOrQuit(udpMsg->SetLength(kEchoPayloadSize));
|
||||||
|
Ip6::MessageInfo udpInfo;
|
||||||
|
udpInfo.SetPeerAddr(internetServer);
|
||||||
|
udpInfo.SetPeerPort(12345);
|
||||||
|
SuccessOrQuit(udpSocket.SendTo(*udpMsg, udpInfo));
|
||||||
|
SuccessOrQuit(udpSocket.Close());
|
||||||
|
}
|
||||||
|
nexus.AdvanceTime(kPingTime);
|
||||||
|
|
||||||
|
Log("Step 9: ED_1 sends HTTP GET to internet server");
|
||||||
|
/**
|
||||||
|
* Step 9
|
||||||
|
* - Device: ED_1
|
||||||
|
* - Description (PIC-10.1): Harness instructs device to sends HTTP GET request to server. If ED_1 is a Linux
|
||||||
|
* device, it may use 'curl' to save the file: curl -o test.html http://threadgroup.org/test.html. In case ED_1
|
||||||
|
* is a non-Linux Thread CLI device, it can use the OT CLI commands to send the HTTP GET request: tcp init, tcp
|
||||||
|
* connect 2002:1234::1234 80, tcp send -x
|
||||||
|
* 474554202F746573742E68746D6C20485454502F312E310D0A486F73743A2074687265616467726F75702E6F72670D0A0D0A, tcp
|
||||||
|
* sendend, tcp deinit
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - In case Linux 'curl' was used: File test.html MUST equal the test.html file as stored on the simulated
|
||||||
|
* server on Eth_2.
|
||||||
|
* - In case OT CLI was used: Output on the CLI MUST be the HTML file test.html as stored on the simulator
|
||||||
|
* server on Eth_2.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
Ip6::Tcp::Endpoint tcpEndpoint;
|
||||||
|
otTcpEndpointInitializeArgs args;
|
||||||
|
ClearAllBytes(args);
|
||||||
|
SuccessOrQuit(tcpEndpoint.Initialize(ed1, args));
|
||||||
|
Ip6::SockAddr internetSockAddr;
|
||||||
|
internetSockAddr.SetAddress(internetServer);
|
||||||
|
internetSockAddr.SetPort(kHttpPort);
|
||||||
|
SuccessOrQuit(tcpEndpoint.Connect(internetSockAddr, 0));
|
||||||
|
nexus.AdvanceTime(kTcpTime);
|
||||||
|
|
||||||
|
const char httpGet[] = "GET /test.html HTTP/1.1\r\nHost: threadgroup.org\r\n\r\n";
|
||||||
|
otLinkedBuffer linkedBuffer;
|
||||||
|
ClearAllBytes(linkedBuffer);
|
||||||
|
linkedBuffer.mData = reinterpret_cast<const uint8_t *>(httpGet);
|
||||||
|
linkedBuffer.mLength = sizeof(httpGet) - 1;
|
||||||
|
SuccessOrQuit(tcpEndpoint.SendByReference(linkedBuffer, 0));
|
||||||
|
nexus.AdvanceTime(kTcpTime);
|
||||||
|
SuccessOrQuit(tcpEndpoint.Deinitialize());
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("Step 10: ED_1 pings local server Eth_1");
|
||||||
|
/**
|
||||||
|
* Step 10
|
||||||
|
* - Device: ED_1
|
||||||
|
* - Description (PIC-10.1): Harness instructs device to send ping request to local server Eth_1. Automatically,
|
||||||
|
* it is routed via the DUT.
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - ED_1 MUST receive ping response from Eth_1.
|
||||||
|
*/
|
||||||
|
Ip6::Address eth1Addr = eth1.mInfraIf.FindMatchingAddress(kEth1Prefix);
|
||||||
|
ed1.SendEchoRequest(eth1Addr, kEchoIdentifier, kEchoPayloadSize);
|
||||||
|
nexus.AdvanceTime(kPingTime);
|
||||||
|
|
||||||
|
Log("Step 11: ED_1 sends HTTP GET to local server Eth_1");
|
||||||
|
/**
|
||||||
|
* Step 11
|
||||||
|
* - Device: ED_1
|
||||||
|
* - Description (PIC-10.1): Harness instructs device to send HTTP GET request to local server Eth_1. If ED_1 is a
|
||||||
|
* Linux device, it may use 'curl' to save the file: curl -o test2.html http:///test2.html. In case ED_1 is a
|
||||||
|
* non-Linux Thread CLI device, it can use the OT CLI commands to send the HTTP GET request: tcp init, tcp
|
||||||
|
* connect <Eth_1_GUA_IPv6_addr> 80, tcp send -x
|
||||||
|
* 474554202F74657374322E68746D6C20485454502F312E310D0A486F73743A206C6F63616C746573742E6F72670D0A0D0A, tcp
|
||||||
|
* sendend, tcp deinit
|
||||||
|
* - Pass Criteria:
|
||||||
|
* - In case Linux 'curl' was used: File test2.html MUST equal the test2.html file as stored on the Eth_1
|
||||||
|
* server.
|
||||||
|
* - In case OT CLI was used: Output on the CLI MUST be the HTML file test.html as stored on the simulator
|
||||||
|
* server on Eth_2.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
Ip6::Tcp::Endpoint tcpEndpoint;
|
||||||
|
otTcpEndpointInitializeArgs args;
|
||||||
|
ClearAllBytes(args);
|
||||||
|
SuccessOrQuit(tcpEndpoint.Initialize(ed1, args));
|
||||||
|
Ip6::SockAddr eth1SockAddr;
|
||||||
|
eth1SockAddr.SetAddress(eth1Addr);
|
||||||
|
eth1SockAddr.SetPort(kHttpPort);
|
||||||
|
SuccessOrQuit(tcpEndpoint.Connect(eth1SockAddr, 0));
|
||||||
|
nexus.AdvanceTime(kTcpTime);
|
||||||
|
|
||||||
|
const char httpGet[] = "GET /test2.html HTTP/1.1\r\nHost: localtest.org\r\n\r\n";
|
||||||
|
otLinkedBuffer linkedBuffer;
|
||||||
|
ClearAllBytes(linkedBuffer);
|
||||||
|
linkedBuffer.mData = reinterpret_cast<const uint8_t *>(httpGet);
|
||||||
|
linkedBuffer.mLength = sizeof(httpGet) - 1;
|
||||||
|
SuccessOrQuit(tcpEndpoint.SendByReference(linkedBuffer, 0));
|
||||||
|
nexus.AdvanceTime(kTcpTime);
|
||||||
|
SuccessOrQuit(tcpEndpoint.Deinitialize());
|
||||||
|
}
|
||||||
|
|
||||||
|
nexus.AddTestVar("ETH_1_ADDR", eth1Addr.ToString().AsCString());
|
||||||
|
nexus.AddTestVar("ETH_2_ADDR", eth2.mInfraIf.GetLinkLocalAddress().ToString().AsCString());
|
||||||
|
nexus.AddTestVar("INTERNET_SERVER_ADDR", kInternetServerAddr);
|
||||||
|
nexus.AddTestVar("DELEGATED_PREFIX", kDelegatedPrefix);
|
||||||
|
|
||||||
|
nexus.SaveTestInfo("test_1_4_PIC_TC_1.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nexus
|
||||||
|
} // namespace ot
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
ot::Nexus::Test_1_4_PIC_TC_1();
|
||||||
|
printf("All tests passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
#!/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):
|
||||||
|
# 10.1. IPv6 Connectivity using DHCPv6-PD delegated OMR prefix
|
||||||
|
#
|
||||||
|
# 10.1.1. Purpose
|
||||||
|
# - To verify that BR DUT:
|
||||||
|
# - uses DHCPv6-PD client functionality to obtain an OMR prefix for its Thread Network from the upstream CPE
|
||||||
|
# router
|
||||||
|
# - Offers IPv6 public internet connectivity to/from Thread Devices that configured an OMR address
|
||||||
|
# - Offers IPv6 local network connectivity to/from Thread Devices that configured an OMR address
|
||||||
|
# - Operates DNS recursive resolver to look up IPv6 server addresses on public internet
|
||||||
|
#
|
||||||
|
# 10.1.2. Topology
|
||||||
|
# - BR_1 (DUT) - Border Router
|
||||||
|
# - Router_1 - Thread Router Reference Device, attached to BR_1
|
||||||
|
# - ED_1 - Thread Reference Device, End Device (e.g. FED/REED) role, attached to Router_1
|
||||||
|
# - Eth_1 - Adjacent Infrastructure Link Linux Reference Device
|
||||||
|
# - Has HTTP web server with resource ‘/test2.html’.
|
||||||
|
# - Eth_2 - Adjacent Infrastructure Link SPIFF Reference Device
|
||||||
|
|
||||||
|
pkts = pv.pkts
|
||||||
|
pv.summary.show()
|
||||||
|
|
||||||
|
BR_1 = pv.vars['BR_1']
|
||||||
|
ROUTER_1 = pv.vars['ROUTER_1']
|
||||||
|
ED_1 = pv.vars['ED_1']
|
||||||
|
ETH_1_ADDR = pv.vars['ETH_1_ADDR']
|
||||||
|
ETH_2_ADDR = pv.vars['ETH_2_ADDR']
|
||||||
|
INTERNET_SERVER_ADDR = pv.vars['INTERNET_SERVER_ADDR']
|
||||||
|
DELEGATED_PREFIX = pv.vars['DELEGATED_PREFIX']
|
||||||
|
|
||||||
|
# Step 1
|
||||||
|
# - Device: Eth_1, Eth_2
|
||||||
|
# - Description (PIC-10.1): Enable
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - N/A
|
||||||
|
print("Step 1: Enable Eth_1 and Eth_2")
|
||||||
|
|
||||||
|
# Step 2
|
||||||
|
# - Device: Eth_2 (SPIFF)
|
||||||
|
# - Description (PIC-10.1): Harness does not configure the device, other than the default configuration. Note: in
|
||||||
|
# previous versions of the test plan, explicit configuration was provided in this step. Note: Eth_1 will
|
||||||
|
# automatically configure a GUA using SLAAC and the RA-advertised prefix.
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - N/A
|
||||||
|
print("Step 2: Eth_2 default configuration")
|
||||||
|
|
||||||
|
# Step 3
|
||||||
|
# - Device: BR_1 (DUT), Router_1, ED_1
|
||||||
|
# - Description (PIC-10.1): Enable
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - N/A
|
||||||
|
print("Step 3: Enable BR_1, Router_1, ED_1")
|
||||||
|
|
||||||
|
# Step 4
|
||||||
|
# - Device: BR_1 (DUT)
|
||||||
|
# - Description (PIC-10.1): Automatically uses DHCPv6-PD client function to obtain a delegated prefix from the
|
||||||
|
# DHCPv6 server. It configures this prefix as the OMR prefix.
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - An OMR prefix OMR_1 MUST appear in Thread Network Data in a Prefix TLV:
|
||||||
|
# - It MUST be subprefix of 2005:1234:abcd:0::/56.
|
||||||
|
# - It MUST be a /64 prefix
|
||||||
|
# - Border Router sub-TLV:
|
||||||
|
# - Prf bits = P_preference = '00' (medium)
|
||||||
|
# - R = P_default = '1'
|
||||||
|
print("Step 4: BR_1 obtains OMR prefix via DHCPv6-PD")
|
||||||
|
# Verify the prefix in Net Data is a subprefix of 2005:1234:abcd:0::/56
|
||||||
|
# 2005:1234:abcd:0001::/64 matches the first 8 bytes of the expanded address.
|
||||||
|
omr_prefix_pattern = Ipv6Addr("2005:1234:abcd:0001::")[:8]
|
||||||
|
omr_prefix_pkt = pkts.filter_wpan_src64(BR_1). \
|
||||||
|
filter_mle_cmd(consts.MLE_DATA_RESPONSE). \
|
||||||
|
filter(lambda p: any(pre.startswith(omr_prefix_pattern) for pre in p.thread_nwd.tlv.prefix)). \
|
||||||
|
must_next()
|
||||||
|
|
||||||
|
OMR_PREFIX = None
|
||||||
|
for pre in omr_prefix_pkt.thread_nwd.tlv.prefix:
|
||||||
|
if pre.startswith(omr_prefix_pattern):
|
||||||
|
OMR_PREFIX = pre
|
||||||
|
break
|
||||||
|
assert OMR_PREFIX is not None
|
||||||
|
|
||||||
|
# Step 4b
|
||||||
|
# - Device: BR_1 (DUT)
|
||||||
|
# - Description (PIC-10.1): Automatically advertises the route to its OMR prefix on the AIL, as a stub router (aka
|
||||||
|
# IETF SNAC router). Note: see 9.6.2 for Stub Router / SNAC Router flag detail.The flag is bit 6 of RA flags as
|
||||||
|
# specified by the TEMPORARY allocation made by IANA (link).
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - The DUT MUST advertise the route in multicast ND RA messages:
|
||||||
|
# - IPv6 destination MUST be ff02::1
|
||||||
|
# - SNAC Router flag set to '1'
|
||||||
|
# - Route Information Option (RIO)
|
||||||
|
# - Prefix: OMR_1
|
||||||
|
# - Prf bits: '00' or '11'
|
||||||
|
# - Route Lifetime > 0
|
||||||
|
print("Step 4b: BR_1 advertises route to OMR prefix on AIL")
|
||||||
|
pkts.filter_ipv6_dst("ff02::1"). \
|
||||||
|
filter_icmpv6_nd_ra(). \
|
||||||
|
filter(lambda p: verify_utils.check_ra_has_rio(p, OMR_PREFIX)). \
|
||||||
|
must_next()
|
||||||
|
|
||||||
|
# Step 5
|
||||||
|
# - Device: ED_1
|
||||||
|
# - Description (PIC-10.1): Harness instructs device to perform DNS query QType=AAAA, name “threadgroup.org”.
|
||||||
|
# Automatically, the DNS query gets routed to BR_1.
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - N/A
|
||||||
|
print("Step 5: ED_1 performs DNS query for threadgroup.org")
|
||||||
|
dns_query = pkts.filter(lambda p: 'dns' in p.layer_names). \
|
||||||
|
filter(lambda p: any(name.rstrip('.') == "threadgroup.org" for name in verify_utils.as_list(p.dns.qry.name))). \
|
||||||
|
must_next()
|
||||||
|
|
||||||
|
# Step 6
|
||||||
|
# - Device: BR_1 (DUT)
|
||||||
|
# - Description (PIC-10.1): Automatically processes the DNS query by requesting upstream to the Eth_2 DNS server.
|
||||||
|
# Then, it responds back with the answer to ED_1.
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - N/A
|
||||||
|
print("Step 6: BR_1 processes DNS query upstream")
|
||||||
|
pkts.filter_ipv6_dst(ETH_2_ADDR). \
|
||||||
|
filter(lambda p: 'dns' in p.layer_names). \
|
||||||
|
filter(lambda p: any(name.rstrip('.') == "threadgroup.org" for name in verify_utils.as_list(p.dns.qry.name))). \
|
||||||
|
must_next()
|
||||||
|
|
||||||
|
# Step 7
|
||||||
|
# - Device: ED_1
|
||||||
|
# - Description (PIC-10.1): Successfully receives DNS query result: threadgroup.org AAAA 2002:1234::1234
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - ED_1 MUST receive DNS query answer from DUT.
|
||||||
|
print("Step 7: ED_1 receives DNS result")
|
||||||
|
# Just look for the response anywhere in the pcap after the query
|
||||||
|
pkts.filter(lambda p: 'dns' in p.layer_names). \
|
||||||
|
filter(lambda p: any(name.rstrip('.') == "threadgroup.org" for name in verify_utils.as_list(p.dns.resp.name))). \
|
||||||
|
must_next()
|
||||||
|
|
||||||
|
# Step 8
|
||||||
|
# - Device: ED_1
|
||||||
|
# - Description (PIC-10.1): Harness instructs device to send ICMpv6 ping request to internet server, using resolved
|
||||||
|
# address above. It is routed via the DUT to the Eth_2 simulated network.
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - ED_1 MUST receive ICMPv6 ping response from simulated server.
|
||||||
|
print("Step 8: ED_1 pings internet server")
|
||||||
|
ping_req = pkts.filter_ipv6_dst(INTERNET_SERVER_ADDR). \
|
||||||
|
filter_ping_request(). \
|
||||||
|
must_next()
|
||||||
|
# Look for the reply from internet server
|
||||||
|
pkts.filter_ipv6_src(INTERNET_SERVER_ADDR). \
|
||||||
|
filter_ping_reply(identifier=ping_req.icmpv6.echo.identifier). \
|
||||||
|
must_next()
|
||||||
|
# ED_1 receives response
|
||||||
|
pkts.filter_ipv6_src(INTERNET_SERVER_ADDR). \
|
||||||
|
filter_ping_reply(identifier=ping_req.icmpv6.echo.identifier). \
|
||||||
|
must_next()
|
||||||
|
|
||||||
|
# Step 8a
|
||||||
|
# - Device: ED_1
|
||||||
|
# - Description (PIC-10.1): Repeats same using a UDP ping.
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - ED_1 MUST receive UDP ping response from simulated server.
|
||||||
|
print("Step 8a: ED_1 sends UDP ping to internet server")
|
||||||
|
pkts.filter_ipv6_dst(INTERNET_SERVER_ADDR). \
|
||||||
|
filter(lambda p: 'udp' in p.layer_names). \
|
||||||
|
must_next()
|
||||||
|
|
||||||
|
# Step 9
|
||||||
|
# - Device: ED_1
|
||||||
|
# - Description (PIC-10.1): Harness instructs device to sends HTTP GET request to server. If ED_1 is a Linux device,
|
||||||
|
# it may use 'curl' to save the file: curl -o test.html http://threadgroup.org/test.html. In case ED_1 is a
|
||||||
|
# non-Linux Thread CLI device, it can use the OT CLI commands to send the HTTP GET request: tcp init, tcp connect
|
||||||
|
# 2002:1234::1234 80, tcp send -x
|
||||||
|
# 474554202F746573742E68746D6C20485454502F312E310D0A486F73743A2074687265616467726F75702E6F72670D0A0D0A, tcp
|
||||||
|
# sendend, tcp deinit
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - In case Linux 'curl' was used: File test.html MUST equal the test.html file as stored on the simulated server
|
||||||
|
# on Eth_2.
|
||||||
|
# - In case OT CLI was used: Output on the CLI MUST be the HTML file test.html as stored on the simulator server
|
||||||
|
# on Eth_2.
|
||||||
|
print("Step 9: ED_1 sends HTTP GET to internet server")
|
||||||
|
pkts.filter_ipv6_dst(INTERNET_SERVER_ADDR). \
|
||||||
|
filter(lambda p: 'tcp' in p.layer_names). \
|
||||||
|
must_next()
|
||||||
|
|
||||||
|
# Step 10
|
||||||
|
# - Device: ED_1
|
||||||
|
# - Description (PIC-10.1): Harness instructs device to send ping request to local server Eth_1. Automatically, it
|
||||||
|
# is routed via the DUT.
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - ED_1 MUST receive ping response from Eth_1.
|
||||||
|
print("Step 10: ED_1 pings local server Eth_1")
|
||||||
|
ping_req = pkts.filter_ipv6_dst(ETH_1_ADDR). \
|
||||||
|
filter_ping_request(). \
|
||||||
|
must_next()
|
||||||
|
pkts.filter_ipv6_src(ETH_1_ADDR). \
|
||||||
|
filter_ping_reply(identifier=ping_req.icmpv6.echo.identifier). \
|
||||||
|
must_next()
|
||||||
|
|
||||||
|
# Step 11
|
||||||
|
# - Device: ED_1
|
||||||
|
# - Description (PIC-10.1): Harness instructs device to send HTTP GET request to local server Eth_1. If ED_1 is a
|
||||||
|
# Linux device, it may use 'curl' to save the file: curl -o test2.html http:///test2.html. In case ED_1 is a
|
||||||
|
# non-Linux Thread CLI device, it can use the OT CLI commands to send the HTTP GET request: tcp init, tcp connect
|
||||||
|
# <Eth_1_GUA_IPv6_addr> 80, tcp send -x
|
||||||
|
# 474554202F74657374322E68746D6C20485454502F312E310D0A486F73743A206C6F63616C746573742E6F72670D0A0D0A, tcp
|
||||||
|
# sendend, tcp deinit
|
||||||
|
# - Pass Criteria:
|
||||||
|
# - In case Linux 'curl' was used: File test2.html MUST equal the test2.html file as stored on the Eth_1 server.
|
||||||
|
# - In case OT CLI was used: Output on the CLI MUST be the HTML file test.html as stored on the simulator server
|
||||||
|
# on Eth_2.
|
||||||
|
print("Step 11: ED_1 sends HTTP GET to local server Eth_1")
|
||||||
|
pkts.filter_ipv6_dst(ETH_1_ADDR). \
|
||||||
|
filter(lambda p: 'tcp' in p.layer_names). \
|
||||||
|
must_next()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
verify_utils.run_main(verify)
|
||||||
Vendored
+20
-5
@@ -172,7 +172,9 @@ size_t cbuf_pop(struct cbufhead* chdr, size_t numbytes) {
|
|||||||
if (used_space < numbytes) {
|
if (used_space < numbytes) {
|
||||||
numbytes = used_space;
|
numbytes = used_space;
|
||||||
}
|
}
|
||||||
chdr->r_index = (chdr->r_index + numbytes) % chdr->size;
|
if (chdr->size > 0) {
|
||||||
|
chdr->r_index = (chdr->r_index + numbytes) % chdr->size;
|
||||||
|
}
|
||||||
chdr->used -= numbytes;
|
chdr->used -= numbytes;
|
||||||
return numbytes;
|
return numbytes;
|
||||||
}
|
}
|
||||||
@@ -288,7 +290,7 @@ size_t cbuf_reass_write(struct cbufhead* chdr, size_t offset, const void* data,
|
|||||||
size_t free_space = cbuf_free_space(chdr);
|
size_t free_space = cbuf_free_space(chdr);
|
||||||
size_t start_index;
|
size_t start_index;
|
||||||
size_t bytes_to_end;
|
size_t bytes_to_end;
|
||||||
if (offset > free_space) {
|
if (chdr->size == 0 || offset > free_space) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (offset + numbytes > free_space) {
|
} else if (offset + numbytes > free_space) {
|
||||||
numbytes = free_space - offset;
|
numbytes = free_space - offset;
|
||||||
@@ -335,8 +337,14 @@ size_t cbuf_reass_merge(struct cbufhead* chdr, size_t numbytes, uint8_t* bitmap)
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t cbuf_reass_count_set(struct cbufhead* chdr, size_t offset, uint8_t* bitmap, size_t limit) {
|
size_t cbuf_reass_count_set(struct cbufhead* chdr, size_t offset, uint8_t* bitmap, size_t limit) {
|
||||||
size_t bitmap_size = BITS_TO_BYTES(chdr->size);
|
size_t bitmap_size;
|
||||||
size_t until_end;
|
size_t until_end;
|
||||||
|
|
||||||
|
if (chdr->size == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitmap_size = BITS_TO_BYTES(chdr->size);
|
||||||
offset = (cbuf_get_w_index(chdr) + offset) % chdr->size;
|
offset = (cbuf_get_w_index(chdr) + offset) % chdr->size;
|
||||||
until_end = bmp_countset(bitmap, bitmap_size, offset, limit);
|
until_end = bmp_countset(bitmap, bitmap_size, offset, limit);
|
||||||
if (until_end >= limit || until_end < (chdr->size - offset)) {
|
if (until_end >= limit || until_end < (chdr->size - offset)) {
|
||||||
@@ -349,8 +357,15 @@ size_t cbuf_reass_count_set(struct cbufhead* chdr, size_t offset, uint8_t* bitma
|
|||||||
}
|
}
|
||||||
|
|
||||||
int cbuf_reass_within_offset(struct cbufhead* chdr, size_t offset, size_t index) {
|
int cbuf_reass_within_offset(struct cbufhead* chdr, size_t offset, size_t index) {
|
||||||
size_t range_start = cbuf_get_w_index(chdr);
|
size_t range_start;
|
||||||
size_t range_end = (range_start + offset) % chdr->size;
|
size_t range_end;
|
||||||
|
|
||||||
|
if (chdr->size == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
range_start = cbuf_get_w_index(chdr);
|
||||||
|
range_end = (range_start + offset) % chdr->size;
|
||||||
if (range_end >= range_start) {
|
if (range_end >= range_start) {
|
||||||
return index >= range_start && index < range_end;
|
return index >= range_start && index < range_end;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user