diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 3a981aea3..59fd2032d 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -400,6 +400,7 @@ ot_nexus_test(log_override "core;nexus") ot_nexus_test(mac_scan "core;nexus") ot_nexus_test(mle_blocking_downgrade "core;nexus") ot_nexus_test(nat64_translator "core;nexus") +ot_nexus_test(netdata_publisher "core;nexus") ot_nexus_test(reed_address_solicit_rejected "core;nexus") ot_nexus_test(router_downgrade_on_sec_policy_change "core;nexus") ot_nexus_test(srp_lease "core;nexus") diff --git a/tests/nexus/test_netdata_publisher.cpp b/tests/nexus/test_netdata_publisher.cpp new file mode 100644 index 000000000..536dcc25e --- /dev/null +++ b/tests/nexus/test_netdata_publisher.cpp @@ -0,0 +1,926 @@ +/* + * Copyright (c) 2026, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" +#include "thread/network_data_leader.hpp" +#include "thread/network_data_local.hpp" +#include "thread/network_data_publisher.hpp" +#include "thread/network_data_service.hpp" + +namespace ot { +namespace Nexus { + +static constexpr uint32_t kFormNetworkTime = 20 * 1000; +static constexpr uint32_t kJoinNetworkTime = 10 * 1000; +static constexpr uint32_t kNetDataUpdateTime = 55 * 1000; + +static constexpr char kOnMeshPrefix[] = "fd00:1234::/64"; +static constexpr char kExternalRoute[] = "fd00:abce::/64"; + +static constexpr uint8_t kAnycastSeqNum = 4; + +static const char *const kDnsSrpAddrStr = "fd00::cdef"; +static constexpr uint16_t kDnsSrpPort = 49152; + +static constexpr uint8_t kDesiredNumAnycast = 8; +static constexpr uint8_t kDesiredNumUnicast = 2; +static constexpr uint8_t kDesiredNumOnMeshPrefix = 3; +static constexpr uint8_t kDesiredNumExternalRoute = 10; + +static constexpr uint32_t kThreadEnterpriseNumber = 44970; +static constexpr uint8_t kAnycastServiceNum = 0x5c; +static constexpr uint8_t kUnicastServiceNum = 0x5d; + +static uint8_t GetNumServices(Node &aNode) +{ + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::ServiceConfig serviceConfig; + uint8_t num = 0; + const NetworkData::Leader &leader = aNode.Get(); + + while (leader.GetNext(iterator, serviceConfig) == kErrorNone) + { + num++; + } + + return num; +} + +static void VerifyAnycastService(const NetworkData::ServiceConfig &aService) +{ + VerifyOrQuit(aService.mEnterpriseNumber == kThreadEnterpriseNumber); + VerifyOrQuit(aService.mServiceDataLength >= 2); + VerifyOrQuit(aService.mServiceData[0] == kAnycastServiceNum); + VerifyOrQuit(aService.mServiceData[1] == kAnycastSeqNum); + VerifyOrQuit(aService.mServerConfig.mStable); +} + +static void VerifyUnicastService(const NetworkData::ServiceConfig &aService) +{ + VerifyOrQuit(aService.mEnterpriseNumber == kThreadEnterpriseNumber); + VerifyOrQuit(aService.mServiceDataLength >= 1); + VerifyOrQuit(aService.mServiceData[0] == kUnicastServiceNum); + VerifyOrQuit(aService.mServerConfig.mStable); +} + +enum ServiceType : uint8_t +{ + kAsAnycastService, + kAsUnicastService, +}; + +static void VerifyServices(Node &aNode, ServiceType aType) +{ + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::ServiceConfig serviceConfig; + const NetworkData::Leader &leader = aNode.Get(); + + while (leader.GetNext(iterator, serviceConfig) == kErrorNone) + { + if (aType == kAsAnycastService) + { + VerifyAnycastService(serviceConfig); + } + else + { + VerifyUnicastService(serviceConfig); + } + } +} + +static void CheckNumOfPrefixes(Node &aNode, uint8_t aNumLow, uint8_t aNumMed, uint8_t aNumHigh) +{ + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::OnMeshPrefixConfig prefixConfig; + const NetworkData::Leader &leader = aNode.Get(); + uint8_t numTotal = 0; + uint8_t numLow = 0; + uint8_t numMed = 0; + uint8_t numHigh = 0; + + while (leader.GetNext(iterator, prefixConfig) == kErrorNone) + { + numTotal++; + if (prefixConfig.mPreference == NetworkData::kRoutePreferenceLow) + { + numLow++; + } + else if (prefixConfig.mPreference == NetworkData::kRoutePreferenceMedium) + { + numMed++; + } + else if (prefixConfig.mPreference == NetworkData::kRoutePreferenceHigh) + { + numHigh++; + } + } + + VerifyOrQuit(numTotal == + Min(static_cast(aNumHigh) + aNumMed + aNumLow, kDesiredNumOnMeshPrefix)); + VerifyOrQuit(numHigh == Min(aNumHigh, kDesiredNumOnMeshPrefix)); + VerifyOrQuit(numMed == + Min(aNumMed, static_cast(Max(0, (int)kDesiredNumOnMeshPrefix - (int)aNumHigh)))); + VerifyOrQuit(numLow == Min(aNumLow, static_cast( + Max(0, (int)kDesiredNumOnMeshPrefix - (int)aNumHigh - (int)numMed)))); +} + +static void CheckNumOfRoutes(Node &aNode, uint8_t aNumLow, uint8_t aNumMed, uint8_t aNumHigh) +{ + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::ExternalRouteConfig routeConfig; + const NetworkData::Leader &leader = aNode.Get(); + uint8_t numTotal = 0; + uint8_t numLow = 0; + uint8_t numMed = 0; + uint8_t numHigh = 0; + + while (leader.GetNext(iterator, routeConfig) == kErrorNone) + { + numTotal++; + if (routeConfig.mPreference == NetworkData::kRoutePreferenceLow) + { + numLow++; + } + else if (routeConfig.mPreference == NetworkData::kRoutePreferenceMedium) + { + numMed++; + } + else if (routeConfig.mPreference == NetworkData::kRoutePreferenceHigh) + { + numHigh++; + } + } + + VerifyOrQuit(numTotal == + Min(static_cast(aNumHigh) + aNumMed + aNumLow, kDesiredNumExternalRoute)); + VerifyOrQuit(numHigh == Min(aNumHigh, kDesiredNumExternalRoute)); + VerifyOrQuit(numMed == + Min(aNumMed, static_cast(Max(0, (int)kDesiredNumExternalRoute - (int)aNumHigh)))); + VerifyOrQuit(numLow == Min(aNumLow, static_cast( + Max(0, (int)kDesiredNumExternalRoute - (int)aNumHigh - (int)numMed)))); +} + +void Test_NetDataPublisher(void) +{ + Core nexus; + + Node &leader = nexus.CreateNode(); + Node &router1 = nexus.CreateNode(); + Node &router2 = nexus.CreateNode(); + Node &router3 = nexus.CreateNode(); + Node &router4 = nexus.CreateNode(); + Node &router5 = nexus.CreateNode(); + Node &ed1 = nexus.CreateNode(); + Node &ed2 = nexus.CreateNode(); + Node &ed3 = nexus.CreateNode(); + Node &ed4 = nexus.CreateNode(); + Node &ed5 = nexus.CreateNode(); + + leader.SetName("LEADER"); + router1.SetName("ROUTER1"); + router2.SetName("ROUTER2"); + router3.SetName("ROUTER3"); + router4.SetName("ROUTER4"); + router5.SetName("ROUTER5"); + ed1.SetName("END_DEV1"); + ed2.SetName("END_DEV2"); + ed3.SetName("END_DEV3"); + ed4.SetName("END_DEV4"); + ed5.SetName("END_DEV5"); + + Node *routers[] = {&router1, &router2, &router3, &router4, &router5}; + Node *eds[] = {&ed1, &ed2, &ed3, &ed4, &ed5}; + Node *nodes[] = {&leader, &router1, &router2, &router3, &router4, &router5, &ed1, &ed2, &ed3, &ed4, &ed5}; + + nexus.AdvanceTime(0); + + leader.Form(); + nexus.AdvanceTime(kFormNetworkTime); + + for (Node *router : routers) + { + router->Join(leader); + nexus.AdvanceTime(kJoinNetworkTime); + } + + for (Node *ed : eds) + { + ed->Join(leader, Node::kAsFed); + nexus.AdvanceTime(kJoinNetworkTime); + } + + Log("---------------------------------------------------------------------------------------"); + Log("DNS/SRP anycast entries - equal version number"); + + leader.Get().PublishDnsSrpServiceAnycast(kAnycastSeqNum, 0); + for (Node *router : routers) + { + router->Get().PublishDnsSrpServiceAnycast(kAnycastSeqNum, 0); + } + nexus.AdvanceTime(kNetDataUpdateTime); + + VerifyOrQuit(GetNumServices(leader) == Min(1 + GetArrayLength(routers), kDesiredNumAnycast)); + VerifyServices(leader, kAsAnycastService); + + for (Node *ed : eds) + { + ed->Get().PublishDnsSrpServiceAnycast(kAnycastSeqNum, 0); + } + nexus.AdvanceTime(kNetDataUpdateTime); + + VerifyOrQuit(GetNumServices(leader) == Min(GetArrayLength(nodes), kDesiredNumAnycast)); + VerifyServices(leader, kAsAnycastService); + + uint8_t num = GetArrayLength(nodes); + for (Node *node : nodes) + { + node->Get().UnpublishDnsSrpService(); + nexus.AdvanceTime(kNetDataUpdateTime); + num--; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumAnycast)); + VerifyServices(leader, kAsAnycastService); + } + + Log("---------------------------------------------------------------------------------------"); + Log("DNS/SRP anycast entries - different version numbers"); + + uint8_t version = 0; + leader.Get().PublishDnsSrpServiceAnycast(kAnycastSeqNum, version); + num = 1; + + for (Node *router : routers) + { + version++; + router->Get().PublishDnsSrpServiceAnycast(kAnycastSeqNum, version); + num++; + } + nexus.AdvanceTime(kNetDataUpdateTime); + + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumAnycast)); + VerifyServices(leader, kAsAnycastService); + + for (Node *ed : eds) + { + ed->Get().PublishDnsSrpServiceAnycast(kAnycastSeqNum, version); + num++; + nexus.AdvanceTime(kNetDataUpdateTime); + + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumAnycast)); + VerifyServices(leader, kAsAnycastService); + + // Check that the entry from this ED is present by checking its RLOC16 + bool found = false; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::ServiceConfig serviceConfig; + while (leader.Get().GetNext(iterator, serviceConfig) == kErrorNone) + { + if (serviceConfig.mServerConfig.mRloc16 == ed->Get().GetRloc16()) + { + found = true; + break; + } + } + VerifyOrQuit(found); + } + + for (Node *node : nodes) + { + node->Get().UnpublishDnsSrpService(); + nexus.AdvanceTime(kNetDataUpdateTime); + num--; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumAnycast)); + VerifyServices(leader, kAsAnycastService); + } + + Log("---------------------------------------------------------------------------------------"); + Log("DNS/SRP service data unicast entries - equal version number"); + + Ip6::Address dnsSrpAddr; + SuccessOrQuit(dnsSrpAddr.FromString(kDnsSrpAddrStr)); + + num = 0; + for (Node *router : routers) + { + router->Get().PublishDnsSrpServiceUnicast(dnsSrpAddr, kDnsSrpPort, 0); + nexus.AdvanceTime(kNetDataUpdateTime); + num++; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + } + + for (Node *router : routers) + { + router->Get().SetEnabled(true); + nexus.AdvanceTime(kNetDataUpdateTime); + } + + uint8_t runningCount = 0; + uint8_t stoppedCount = 0; + for (Node *router : routers) + { + if (router->Get().GetState() == Srp::Server::kStateRunning) + { + runningCount++; + } + else if (router->Get().GetState() == Srp::Server::kStateStopped) + { + stoppedCount++; + } + } + VerifyOrQuit(runningCount == Min(GetArrayLength(routers), kDesiredNumUnicast)); + VerifyOrQuit(stoppedCount == + static_cast(Max(0, (int)GetArrayLength(routers) - (int)kDesiredNumUnicast))); + + for (Node *router : routers) + { + router->Get().UnpublishDnsSrpService(); + nexus.AdvanceTime(kNetDataUpdateTime); + num--; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + } + + for (Node *router : routers) + { + router->Get().SetEnabled(false); + VerifyOrQuit(router->Get().GetState() == Srp::Server::kStateDisabled); + } + + Log("---------------------------------------------------------------------------------------"); + Log("DNS/SRP service data unicast entries - different version numbers"); + + num = 0; + for (uint8_t i = 0; i < GetArrayLength(routers); i++) + { + routers[i]->Get().PublishDnsSrpServiceUnicast(dnsSrpAddr, kDnsSrpPort, num); + nexus.AdvanceTime(kNetDataUpdateTime); + num++; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + + // The most recent service should win as it uses a higher version number. + bool found = false; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::ServiceConfig serviceConfig; + while (leader.Get().GetNext(iterator, serviceConfig) == kErrorNone) + { + if (serviceConfig.mServerConfig.mRloc16 == routers[i]->Get().GetRloc16()) + { + found = true; + break; + } + } + VerifyOrQuit(found); + } + + for (int i = static_cast(GetArrayLength(routers)) - 1; i >= 0; i--) + { + routers[i]->Get().UnpublishDnsSrpService(); + nexus.AdvanceTime(kNetDataUpdateTime); + num--; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + } + + Log("---------------------------------------------------------------------------------------"); + Log("DNS/SRP server data unicast entries - equal version number"); + + num = 0; + for (Node *router : routers) + { + router->Get().PublishDnsSrpServiceUnicast(kDnsSrpPort, 0); + nexus.AdvanceTime(kNetDataUpdateTime); + num++; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + } + + for (Node *router : routers) + { + router->Get().SetEnabled(true); + nexus.AdvanceTime(kNetDataUpdateTime); + } + runningCount = 0; + stoppedCount = 0; + for (Node *router : routers) + { + if (router->Get().GetState() == Srp::Server::kStateRunning) + { + runningCount++; + } + else if (router->Get().GetState() == Srp::Server::kStateStopped) + { + stoppedCount++; + } + } + VerifyOrQuit(runningCount == Min(GetArrayLength(routers), kDesiredNumUnicast)); + VerifyOrQuit(stoppedCount == + static_cast(Max(0, (int)GetArrayLength(routers) - (int)kDesiredNumUnicast))); + + for (Node *router : routers) + { + router->Get().UnpublishDnsSrpService(); + nexus.AdvanceTime(kNetDataUpdateTime); + num--; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + } + + for (Node *router : routers) + { + router->Get().SetEnabled(false); + VerifyOrQuit(router->Get().GetState() == Srp::Server::kStateDisabled); + } + + Log("---------------------------------------------------------------------------------------"); + Log("DNS/SRP server data unicast entries - different version numbers"); + + num = 0; + for (uint8_t i = 0; i < GetArrayLength(routers); i++) + { + routers[i]->Get().PublishDnsSrpServiceUnicast(kDnsSrpPort, num); + nexus.AdvanceTime(kNetDataUpdateTime); + num++; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + + bool found = false; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::ServiceConfig serviceConfig; + while (leader.Get().GetNext(iterator, serviceConfig) == kErrorNone) + { + if (serviceConfig.mServerConfig.mRloc16 == routers[i]->Get().GetRloc16()) + { + found = true; + break; + } + } + VerifyOrQuit(found); + } + + for (int i = static_cast(GetArrayLength(routers)) - 1; i >= 0; i--) + { + routers[i]->Get().UnpublishDnsSrpService(); + nexus.AdvanceTime(kNetDataUpdateTime); + num--; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + } + + num = 0; + for (uint8_t i = 0; i < GetArrayLength(routers); i++) + { + routers[i]->Get().PublishDnsSrpServiceUnicast(kDnsSrpPort, 20 - num); + nexus.AdvanceTime(kNetDataUpdateTime); + num++; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + + // The service from first router should win as it uses the highest version number. + bool found = false; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::ServiceConfig serviceConfig; + while (leader.Get().GetNext(iterator, serviceConfig) == kErrorNone) + { + if (serviceConfig.mServerConfig.mRloc16 == routers[0]->Get().GetRloc16()) + { + found = true; + break; + } + } + VerifyOrQuit(found); + } + + for (Node *router : routers) + { + router->Get().UnpublishDnsSrpService(); + nexus.AdvanceTime(kNetDataUpdateTime); + num--; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + } + + Log("---------------------------------------------------------------------------------------"); + Log("DNS/SRP server data unicast vs anycast"); + + num = 0; + for (Node *router : routers) + { + router->Get().PublishDnsSrpServiceUnicast(kDnsSrpPort, 0); + nexus.AdvanceTime(kNetDataUpdateTime); + num++; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + } + + leader.Get().PublishDnsSrpServiceAnycast(kAnycastSeqNum, 0); + + // Verify that publishing an anycast entry will update the + // limit for the server data unicast address entry and all are + // removed quickly. We validate this by waiting for a short + // time (5000 msec) after publishing anycast. + nexus.AdvanceTime(5000); + VerifyOrQuit(GetNumServices(leader) == 1); + VerifyServices(leader, kAsAnycastService); + + // Removing the anycast entry will cause the lower priority + // server data unicast entries to be added again. + leader.Get().UnpublishDnsSrpService(); + nexus.AdvanceTime(kNetDataUpdateTime); + + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + + Log("---------------------------------------------------------------------------------------"); + Log("DNS/SRP server data unicast vs service data unicast"); + + leader.Get().PublishDnsSrpServiceUnicast(dnsSrpAddr, kDnsSrpPort, 0); + nexus.AdvanceTime(kNetDataUpdateTime); + VerifyOrQuit(GetNumServices(leader) == 1); + VerifyServices(leader, kAsUnicastService); + + leader.Get().UnpublishDnsSrpService(); + nexus.AdvanceTime(kNetDataUpdateTime); + + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumUnicast)); + VerifyServices(leader, kAsUnicastService); + + for (Node *router : routers) + { + router->Get().UnpublishDnsSrpService(); + } + nexus.AdvanceTime(kNetDataUpdateTime); + + Log("---------------------------------------------------------------------------------------"); + Log("DNS/SRP entries: Verify publisher preference when removing entries"); + + num = 0; + Node *testRoutersNodes[] = {&leader, &router1, &router2}; + for (Node *node : testRoutersNodes) + { + node->Get().PublishDnsSrpServiceAnycast(kAnycastSeqNum, 0); + nexus.AdvanceTime(kNetDataUpdateTime); + num++; + VerifyOrQuit(GetNumServices(leader) == num); + VerifyServices(leader, kAsAnycastService); + } + for (Node *node : eds) + { + node->Get().PublishDnsSrpServiceAnycast(kAnycastSeqNum, 0); + nexus.AdvanceTime(kNetDataUpdateTime); + num++; + VerifyOrQuit(GetNumServices(leader) == Min(num, kDesiredNumAnycast)); + VerifyServices(leader, kAsAnycastService); + } + + VerifyOrQuit(GetNumServices(leader) == kDesiredNumAnycast); + + for (Node *node : {&router3, &router4, &router5}) + { + // Manually add service to router + NetworkData::ServiceData serviceData; + NetworkData::ServerData serverData; + uint8_t sData[] = {kAnycastServiceNum, kAnycastSeqNum}; + + serviceData.Init(sData, sizeof(sData)); + serverData.Init(nullptr, 0); + + SuccessOrQuit( + node->Get().AddService(kThreadEnterpriseNumber, serviceData, true, serverData)); + node->Get().HandleServerDataUpdated(); + nexus.AdvanceTime(kNetDataUpdateTime); + + VerifyOrQuit(GetNumServices(leader) == kDesiredNumAnycast); + VerifyServices(leader, kAsAnycastService); + } + + // Final check for all routers being present. + { + Node *allRouters[] = {&leader, &router1, &router2, &router3, &router4, &router5}; + for (Node *router : allRouters) + { + bool found = false; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::ServiceConfig serviceConfig; + while (leader.Get().GetNext(iterator, serviceConfig) == kErrorNone) + { + if (serviceConfig.mServerConfig.mRloc16 == router->Get().GetRloc16()) + { + found = true; + break; + } + } + VerifyOrQuit(found); + } + } + + nexus.AdvanceTime(kNetDataUpdateTime); + + Log("---------------------------------------------------------------------------------------"); + Log("On-mesh prefix"); + + Ip6::Prefix onMeshPrefix; + SuccessOrQuit(onMeshPrefix.FromString(kOnMeshPrefix)); + + uint8_t numLow = 0; + uint8_t numMed = 0; + uint8_t numHigh = 0; + + for (Node *ed : eds) + { + NetworkData::OnMeshPrefixConfig config; + config.Clear(); + config.GetPrefix() = onMeshPrefix; + config.mPreference = NetworkData::kRoutePreferenceLow; + config.mSlaac = true; + config.mOnMesh = true; + config.mStable = true; + config.mPreferred = true; + SuccessOrQuit(ed->Get().PublishOnMeshPrefix(config, NetworkData::Publisher::kFromUser)); + nexus.AdvanceTime(kNetDataUpdateTime); + numLow++; + CheckNumOfPrefixes(leader, numLow, numMed, numHigh); + } + + for (Node *router : routers) + { + NetworkData::OnMeshPrefixConfig config; + config.Clear(); + config.GetPrefix() = onMeshPrefix; + config.mPreference = NetworkData::kRoutePreferenceMedium; + config.mSlaac = true; + config.mOnMesh = true; + config.mStable = true; + config.mPreferred = true; + SuccessOrQuit( + router->Get().PublishOnMeshPrefix(config, NetworkData::Publisher::kFromUser)); + nexus.AdvanceTime(kNetDataUpdateTime); + numMed++; + CheckNumOfPrefixes(leader, numLow, numMed, numHigh); + } + + { + NetworkData::OnMeshPrefixConfig config; + config.Clear(); + config.GetPrefix() = onMeshPrefix; + config.mPreference = NetworkData::kRoutePreferenceHigh; + config.mSlaac = true; + config.mOnMesh = true; + config.mStable = true; + config.mPreferred = true; + SuccessOrQuit( + leader.Get().PublishOnMeshPrefix(config, NetworkData::Publisher::kFromUser)); + } + nexus.AdvanceTime(kNetDataUpdateTime); + numHigh++; + CheckNumOfPrefixes(leader, numLow, numMed, numHigh); + + for (Node *router : routers) + { + SuccessOrQuit(router->Get().UnpublishPrefix(onMeshPrefix)); + nexus.AdvanceTime(kNetDataUpdateTime); + numMed--; + CheckNumOfPrefixes(leader, numLow, numMed, numHigh); + } + + SuccessOrQuit(leader.Get().UnpublishPrefix(onMeshPrefix)); + nexus.AdvanceTime(kNetDataUpdateTime); + numHigh--; + CheckNumOfPrefixes(leader, numLow, numMed, numHigh); + + for (Node *ed : eds) + { + SuccessOrQuit(ed->Get().UnpublishPrefix(onMeshPrefix)); + nexus.AdvanceTime(kNetDataUpdateTime); + numLow--; + CheckNumOfPrefixes(leader, numLow, numMed, numHigh); + } + + Log("---------------------------------------------------------------------------------------"); + Log("Verify prefix removal preference"); + + { + NetworkData::OnMeshPrefixConfig config; + config.Clear(); + config.GetPrefix() = onMeshPrefix; + config.mPreference = NetworkData::kRoutePreferenceMedium; + config.mSlaac = true; + config.mOnMesh = true; + config.mStable = true; + config.mPreferred = true; + SuccessOrQuit(ed1.Get().PublishOnMeshPrefix(config, NetworkData::Publisher::kFromUser)); + } + nexus.AdvanceTime(kNetDataUpdateTime); + CheckNumOfPrefixes(leader, 0, 1, 0); + + for (Node *router : routers) + { + NetworkData::OnMeshPrefixConfig config; + config.Clear(); + config.GetPrefix() = onMeshPrefix; + config.mPreference = NetworkData::kRoutePreferenceMedium; + config.mSlaac = true; + config.mOnMesh = true; + config.mStable = true; + config.mPreferred = true; + SuccessOrQuit( + router->Get().PublishOnMeshPrefix(config, NetworkData::Publisher::kFromUser)); + nexus.AdvanceTime(kNetDataUpdateTime); + } + CheckNumOfPrefixes(leader, 0, 1 + GetArrayLength(routers), 0); + + // Check ed1 is in the list + { + bool found = false; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::OnMeshPrefixConfig prefixConfig; + while (leader.Get().GetNext(iterator, prefixConfig) == kErrorNone) + { + if (prefixConfig.mRloc16 == ed1.Get().GetRloc16()) + { + found = true; + break; + } + } + VerifyOrQuit(found); + } + + { + NetworkData::OnMeshPrefixConfig config; + config.Clear(); + config.GetPrefix() = onMeshPrefix; + config.mPreference = NetworkData::kRoutePreferenceHigh; + config.mSlaac = true; + config.mOnMesh = true; + config.mStable = true; + config.mPreferred = true; + SuccessOrQuit( + leader.Get().PublishOnMeshPrefix(config, NetworkData::Publisher::kFromUser)); + } + nexus.AdvanceTime(kNetDataUpdateTime); + CheckNumOfPrefixes(leader, 0, 1 + GetArrayLength(routers), 1); + + // Publish same prefix now with `high` preference on leader. + // Since it is `high` preference, it is added to network data + // which leads to total number of entries to go above the desired + // number temporarily and trigger other nodes to try to remove + // their entry. The entries from routers should be preferred over + // the one from `end_dev1` so that is the one we expect to be + // removed. We check that this is the case (i.e., the entry from + // `end_dev1` is no longer present in network data). + // + // Check ed1 is NO LONGER in the list + { + bool found = false; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::OnMeshPrefixConfig prefixConfig; + while (leader.Get().GetNext(iterator, prefixConfig) == kErrorNone) + { + if (prefixConfig.mRloc16 == ed1.Get().GetRloc16()) + { + found = true; + break; + } + } + VerifyOrQuit(!found); + } + + Log("---------------------------------------------------------------------------------------"); + Log("External route"); + + Ip6::Prefix externalRoute; + SuccessOrQuit(externalRoute.FromString(kExternalRoute)); + + num = 0; + for (Node *node : nodes) + { + NetworkData::ExternalRouteConfig config; + config.Clear(); + config.GetPrefix() = externalRoute; + config.mPreference = NetworkData::kRoutePreferenceLow; + config.mStable = true; + SuccessOrQuit( + node->Get().PublishExternalRoute(config, NetworkData::Publisher::kFromUser)); + nexus.AdvanceTime(kNetDataUpdateTime); + num++; + CheckNumOfRoutes(leader, num, 0, 0); + } + + { + NetworkData::ExternalRouteConfig config; + config.Clear(); + config.GetPrefix() = externalRoute; + config.mPreference = NetworkData::kRoutePreferenceHigh; + config.mStable = true; + SuccessOrQuit( + leader.Get().PublishExternalRoute(config, NetworkData::Publisher::kFromUser)); + } + nexus.AdvanceTime(kNetDataUpdateTime); + CheckNumOfRoutes(leader, num - 1, 0, 1); + + { + Ip6::Prefix defaultRoute; + SuccessOrQuit(defaultRoute.FromString("::/0")); + NetworkData::ExternalRouteConfig config; + config.Clear(); + config.GetPrefix() = defaultRoute; + config.mPreference = NetworkData::kRoutePreferenceMedium; + config.mStable = true; + SuccessOrQuit(leader.Get().ReplacePublishedExternalRoute( + externalRoute, config, NetworkData::Publisher::kFromUser)); + nexus.AdvanceTime(kNetDataUpdateTime); + + bool found = false; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::ExternalRouteConfig routeConfig; + while (leader.Get().GetNext(iterator, routeConfig) == kErrorNone) + { + if (AsCoreType(&routeConfig.mPrefix) == defaultRoute) + { + found = true; + break; + } + } + VerifyOrQuit(found); + + // Replace it back + config.GetPrefix() = externalRoute; + config.mPreference = NetworkData::kRoutePreferenceHigh; + SuccessOrQuit(leader.Get().ReplacePublishedExternalRoute( + defaultRoute, config, NetworkData::Publisher::kFromUser)); + } + nexus.AdvanceTime(kNetDataUpdateTime); + CheckNumOfRoutes(leader, num - 1, 0, 1); + + { + NetworkData::OnMeshPrefixConfig config; + config.Clear(); + config.GetPrefix() = externalRoute; + config.mPreference = NetworkData::kRoutePreferenceLow; + config.mSlaac = true; + config.mOnMesh = true; + config.mStable = true; + config.mPreferred = true; + SuccessOrQuit( + leader.Get().PublishOnMeshPrefix(config, NetworkData::Publisher::kFromUser)); + } + nexus.AdvanceTime(kNetDataUpdateTime); + CheckNumOfRoutes(leader, num - 1, 0, 0); + + { + bool found = false; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::OnMeshPrefixConfig prefixConfig; + while (leader.Get().GetNext(iterator, prefixConfig) == kErrorNone) + { + if (AsCoreType(&prefixConfig.mPrefix) == externalRoute) + { + found = true; + break; + } + } + VerifyOrQuit(found); + } + + Log("All steps completed."); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::Test_NetDataPublisher(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/scripts/thread-cert/test_netdata_publisher.py b/tests/scripts/thread-cert/test_netdata_publisher.py deleted file mode 100755 index 4d49de033..000000000 --- a/tests/scripts/thread-cert/test_netdata_publisher.py +++ /dev/null @@ -1,693 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2021, 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 ipaddress -import unittest - -import command -import config -import thread_cert - -# Test description: -# This test verifies network data publisher behavior with DNS/SRP service entries and on-mesh prefix and external -# route entries. -# -# Topology: -# -# 1 leader, 5 routers and 5 end-devices all connected. -# - -LEADER = 1 -ROUTER1 = 2 -ROUTER2 = 3 -ROUTER3 = 4 -ROUTER4 = 5 -ROUTER5 = 6 -END_DEV1 = 7 -END_DEV2 = 8 -END_DEV3 = 9 -END_DEV4 = 10 -END_DEV5 = 11 - -WAIT_TIME = 55 - -ON_MESH_PREFIX = 'fd00:1234:0:0::/64' -ON_MESH_FLAGS = 'paso' - -EXTERNAL_ROUTE = 'fd00:abce:0:0::/64' -EXTERNAL_FLAGS = 's' - -ANYCAST_SEQ_NUM = 4 - -DNSSRP_ADDRESS = 'fd00::cdef' -DNSSRP_PORT = 49152 - -# The desired number of entries (based on related config). -DESIRED_NUM_DNSSRP_ANYCAST = 8 -DESIRED_NUM_DNSSRP_UNICAST = 2 -DESIRED_NUM_ON_MESH_PREFIX = 3 -DESIRED_NUM_EXTERNAL_ROUTE = 10 - -THREAD_ENTERPRISE_NUMBER = 44970 -ANYCAST_SERVICE_NUM = 0x5c -UNICAST_SERVICE_NUM = 0x5d - - -class NetDataPublisher(thread_cert.TestCase): - USE_MESSAGE_FACTORY = False - SUPPORT_NCP = False - - TOPOLOGY = { - LEADER: { - 'name': 'LEADER', - 'mode': 'rdn', - }, - ROUTER1: { - 'name': 'ROUTER1', - 'mode': 'rdn', - }, - ROUTER2: { - 'name': 'ROUTER2', - 'mode': 'rdn', - }, - ROUTER3: { - 'name': 'ROUTER3', - 'mode': 'rdn', - }, - ROUTER4: { - 'name': 'ROUTER4', - 'mode': 'rdn', - }, - ROUTER5: { - 'name': 'ROUTER5', - 'mode': 'rdn', - }, - END_DEV1: { - 'name': 'END_DEV1', - 'mode': 'rn', - }, - END_DEV2: { - 'name': 'END_DEV2', - 'mode': 'rn', - }, - END_DEV3: { - 'name': 'END_DEV3', - 'mode': 'rn', - }, - END_DEV4: { - 'name': 'END_DEV4', - 'mode': 'rn', - }, - END_DEV5: { - 'name': 'END_DEV5', - 'mode': 'rn', - }, - } - - def verify_anycast_service(self, service): - # Verify the data in a single anycast `service` from `get_services()` - # Example of `service`: ['44970', '5c04', '', 's', 'bc00'] - self.assertEqual(int(service[0]), THREAD_ENTERPRISE_NUMBER) - # Check service data - service_data = bytes.fromhex(service[1]) - self.assertTrue(len(service_data) >= 2) - self.assertEqual(service_data[0], ANYCAST_SERVICE_NUM) - self.assertEqual(service_data[1], int(ANYCAST_SEQ_NUM)) - # Verify that it stable - self.assertEqual(service[3], 's') - - def verify_anycast_services(self, services): - # Verify a list of anycast `services` from `get_services()` - for service in services: - self.verify_anycast_service(service) - - def verify_unicast_service(self, service): - # Verify the data in a single unicast `service` from `get_services()` - # Example of `service`: ['44970', '5d', 'fd000db800000000c6b0e5ee81f940e8223d', 's', '7000'] - self.assertEqual(int(service[0]), THREAD_ENTERPRISE_NUMBER) - # Check service data - service_data = bytes.fromhex(service[1]) - self.assertTrue(len(service_data) >= 1) - self.assertEqual(service_data[0], UNICAST_SERVICE_NUM) - # Verify that it stable - self.assertEqual(service[3], 's') - - def verify_unicast_services(self, services): - # Verify a list of unicast `services` from `get_services()` - for service in services: - self.verify_unicast_service(service) - - def check_num_of_prefixes(self, prefixes, num_low, num_med, num_high): - # Check and validate the prefix entries in network data (from - # `prefixes`) based on number of published prefix entries at - # different preference levels given by `num_low`, `num_med`, - # `num_high`. Prefixes is a list of the format - # 'fd00:1234:0:0::/64 paos low a802'. - self.assertEqual(len(prefixes), min(num_high + num_med + num_low, DESIRED_NUM_ON_MESH_PREFIX)) - prfs = [prefix.split(' ')[2] for prefix in prefixes] - self.assertEqual(prfs.count('high'), min(num_high, DESIRED_NUM_ON_MESH_PREFIX)) - self.assertEqual(prfs.count('med'), min(num_med, max(0, DESIRED_NUM_ON_MESH_PREFIX - num_high))) - self.assertEqual(prfs.count('low'), min(num_low, max(0, DESIRED_NUM_ON_MESH_PREFIX - num_high - num_med))) - - def check_num_of_routes(self, routes, num_low, num_med, num_high): - # Check and validate the prefix entries in network data (from - # `routes`) based on number of published prefix entries at - # different preference levels given by `num_low`, `num_med`, - # `num_high`. Prefixes is a list of the format - # 'fd00:abce:0:0::/64 s med 6c01'. - self.assertEqual(len(routes), min(num_high + num_med + num_low, DESIRED_NUM_EXTERNAL_ROUTE)) - prfs = [route.split(' ')[2] for route in routes] - self.assertEqual(prfs.count('high'), min(num_high, DESIRED_NUM_EXTERNAL_ROUTE)) - self.assertEqual(prfs.count('med'), min(num_med, max(0, DESIRED_NUM_EXTERNAL_ROUTE - num_high))) - self.assertEqual(prfs.count('low'), min(num_low, max(0, DESIRED_NUM_EXTERNAL_ROUTE - num_high - num_med))) - - def test(self): - leader = self.nodes[LEADER] - router1 = self.nodes[ROUTER1] - router2 = self.nodes[ROUTER2] - router3 = self.nodes[ROUTER3] - router4 = self.nodes[ROUTER4] - router5 = self.nodes[ROUTER5] - end_dev1 = self.nodes[END_DEV1] - end_dev2 = self.nodes[END_DEV2] - end_dev3 = self.nodes[END_DEV3] - end_dev4 = self.nodes[END_DEV4] - end_dev5 = self.nodes[END_DEV5] - - nodes = self.nodes.values() - routers = [router1, router2, router3, router4, router5] - end_devs = [end_dev1, end_dev2, end_dev3, end_dev4, end_dev5] - - # Start the nodes - - leader.start() - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(leader.get_state(), 'leader') - - for router in routers: - router.start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) - self.assertEqual(router.get_state(), 'router') - - for end_dev in end_devs: - end_dev.start() - self.simulator.go(5) - self.assertEqual(end_dev.get_state(), 'child') - - #--------------------------------------------------------------------------------- - # DNS/SRP anycast entries - equal version number - - # Publish DNS/SRP anycast on leader and all routers (6 nodes). - - leader.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) - for node in routers: - node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) - self.simulator.go(WAIT_TIME) - - # Check all entries are present in the network data - - services = leader.get_services() - self.assertEqual(len(services), min(1 + len(routers), DESIRED_NUM_DNSSRP_ANYCAST)) - self.verify_anycast_services(services) - - # Publish same entry on all end-devices (5 nodes). - - for node in end_devs: - node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) - print(node.name) - self.simulator.go(WAIT_TIME) - - # Check number of entries in the network data is limited to - # the desired number (8 entries). - - services = leader.get_services() - self.assertEqual(len(leader.get_services()), min(len(nodes), DESIRED_NUM_DNSSRP_ANYCAST)) - self.verify_anycast_services(services) - - # Unpublish the entry from nodes one by one starting from leader - # and check that number of entries is correct in each step. - - num = len(nodes) - for node in nodes: - node.netdata_unpublish_dnssrp() - self.simulator.go(WAIT_TIME) - num -= 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_ANYCAST)) - self.verify_anycast_services(services) - - #--------------------------------------------------------------------------------- - # DNS/SRP anycast entries - different version numbers - - # Publish DNS/SRP anycast on leader and all routers (6 nodes). - - version = 0 - leader.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM, version) - num = 1 - - for node in routers: - version += 1 - node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM, version) - num += 1 - - self.simulator.go(WAIT_TIME) - - # Check all entries are present in the network data - - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_ANYCAST)) - self.verify_anycast_services(services) - - # Publish same entry with same version on all end-devices (5 nodes). - - for node in end_devs: - node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM, version) - num += 1 - print(node.name) - self.simulator.go(WAIT_TIME) - # Check number of entries in the network data is limited - # to the desired number (8 entries). All new entries use - # higher version and should be preferred. Validate that - # the 'services' list contains the new services. - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_ANYCAST)) - self.verify_anycast_services(services) - node_rloc16 = node.get_addr16() - self.assertTrue(any(int(service[4], 16) == node_rloc16 for service in services)) - - # Unpublish the entry from nodes one by one starting from leader - # and check that number of entries is correct in each step. - - for node in nodes: - node.netdata_unpublish_dnssrp() - self.simulator.go(WAIT_TIME) - num -= 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_ANYCAST)) - self.verify_anycast_services(services) - - #--------------------------------------------------------------------------------- - # DNS/SRP service data unicast entries - equal version number - - num = 0 - for node in routers: - node.netdata_publish_dnssrp_unicast(DNSSRP_ADDRESS, DNSSRP_PORT) - self.simulator.go(WAIT_TIME) - num += 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - - for node in routers: - node.srp_server_set_enabled(True) - self.simulator.go(WAIT_TIME) - - self.assertEqual(sum(node.srp_server_get_state() == 'running' for node in routers), - min(len(routers), DESIRED_NUM_DNSSRP_UNICAST)) - self.assertEqual(sum(node.srp_server_get_state() == 'stopped' for node in routers), - max(len(routers) - DESIRED_NUM_DNSSRP_UNICAST, 0)) - - for node in routers: - node.netdata_unpublish_dnssrp() - self.simulator.go(WAIT_TIME) - num -= 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - for node in routers: - node.srp_server_set_enabled(False) - self.assertEqual(node.srp_server_get_state(), 'disabled') - - #--------------------------------------------------------------------------------- - # DNS/SRP service data unicast entries - different version numbers - - num = 0 - for node in routers: - # Use `num` as version. - node.netdata_publish_dnssrp_unicast(DNSSRP_ADDRESS, DNSSRP_PORT, num) - self.simulator.go(WAIT_TIME) - num += 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - # The most recent service should win as it uses a higher version - # number. Validate that the 'services' list contains the service - # from this node by checking the service RLOC16. - node_rloc16 = node.get_addr16() - self.assertTrue(any(int(service[4], 16) == node_rloc16 for service in services)) - - for node in reversed(routers): - node.netdata_unpublish_dnssrp() - self.simulator.go(WAIT_TIME) - num -= 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - - #--------------------------------------------------------------------------------- - # DNS/SRP server data unicast entries - equal version number - - num = 0 - for node in routers: - node.netdata_publish_dnssrp_unicast_mleid(DNSSRP_PORT) - self.simulator.go(WAIT_TIME) - num += 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - - for node in routers: - node.srp_server_set_enabled(True) - self.simulator.go(WAIT_TIME) - self.assertEqual(sum(node.srp_server_get_state() == 'running' for node in routers), - min(len(routers), DESIRED_NUM_DNSSRP_UNICAST)) - self.assertEqual(sum(node.srp_server_get_state() == 'stopped' for node in routers), - max(len(routers) - DESIRED_NUM_DNSSRP_UNICAST, 0)) - - for node in routers: - node.netdata_unpublish_dnssrp() - self.simulator.go(WAIT_TIME) - num -= 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - for node in routers: - node.srp_server_set_enabled(False) - self.assertEqual(node.srp_server_get_state(), 'disabled') - - #--------------------------------------------------------------------------------- - # DNS/SRP server data unicast entries - different version numbers - - num = 0 - for node in routers: - node.netdata_publish_dnssrp_unicast_mleid(DNSSRP_PORT, num) - self.simulator.go(WAIT_TIME) - num += 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - # The most recent service should win as it uses a higher version - # number. Validate that the 'services' list contains the service - # from this node by checking the service RLOC16. - node_rloc16 = node.get_addr16() - self.assertTrue(any(int(service[4], 16) == node_rloc16 for service in services)) - - for node in reversed(routers): - node.netdata_unpublish_dnssrp() - self.simulator.go(WAIT_TIME) - num -= 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - - # Repeat the same test steps, but start with larger version - # numbers first. - - num = 0 - for node in routers: - node.netdata_publish_dnssrp_unicast_mleid(DNSSRP_PORT, 20 - num) - self.simulator.go(WAIT_TIME) - num += 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - # The service from first router should win as it uses the highest - # version number. - first_router_rloc16 = routers[0].get_addr16() - self.assertTrue(any(int(service[4], 16) == first_router_rloc16 for service in services)) - - for node in routers: - node.netdata_unpublish_dnssrp() - self.simulator.go(WAIT_TIME) - num -= 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - - #--------------------------------------------------------------------------------- - # DNS/SRP server data unicast vs anycast - - num = 0 - for node in routers: - node.netdata_publish_dnssrp_unicast_mleid(DNSSRP_PORT) - self.simulator.go(WAIT_TIME) - num += 1 - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - - # Verify that publishing an anycast entry will update the - # limit for the server data unicast address entry and all are - # removed quickly. We validate this by waiting for a short - # time (15 msec) after publishing anycast. - - leader.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) - self.simulator.go(15) - services = leader.get_services() - self.assertEqual(len(services), 1) - self.verify_anycast_services(services) - - # Removing the anycast entry will cause the lower priority - # server data unicast entries to be added again. - - leader.netdata_unpublish_dnssrp() - self.simulator.go(WAIT_TIME) - - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - - #--------------------------------------------------------------------------------- - # DNS/SRP server data unicast vs service data unicast - - leader.netdata_publish_dnssrp_unicast(DNSSRP_ADDRESS, DNSSRP_PORT) - self.simulator.go(WAIT_TIME) - services = leader.get_services() - self.assertEqual(len(services), 1) - self.verify_unicast_services(services) - - # Removing the service data unicast entry will cause the lower - # priority server data unicast entries to be added again. - - leader.netdata_unpublish_dnssrp() - self.simulator.go(WAIT_TIME) - - services = leader.get_services() - self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) - self.verify_unicast_services(services) - - for node in routers: - node.netdata_unpublish_dnssrp() - - #--------------------------------------------------------------------------------- - # DNS/SRP entries: Verify publisher preference when removing - # entries. - # - # Publish DNS/SRP anycast on 8 nodes: leader, router1, - # router2, and all 5 end-devices. Afterwards, manually add - # the same service entry in Network Data on router3, router4, - # and router5 and at each step check that entry from one of - # the end-devices is removed (publisher prefers - # entries from routers over the ones from end-devices). - - num = 0 - test_routers = [leader, router1, router2] - for node in test_routers + end_devs: - node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) - self.simulator.go(WAIT_TIME) - num += 1 - services = leader.get_services() - self.assertEqual(len(services), num) - self.verify_anycast_services(services) - - self.assertEqual(num, DESIRED_NUM_DNSSRP_ANYCAST) - - service_data = '%02x%02x' % (ANYCAST_SERVICE_NUM, int(ANYCAST_SEQ_NUM)) - for node in [router3, router4, router5]: - node.add_service(str(THREAD_ENTERPRISE_NUMBER), service_data, '00') - node.register_netdata() - self.simulator.go(WAIT_TIME) - - services = leader.get_services() - self.assertEqual(len(services), num) - self.verify_anycast_services(services) - - service_rlocs = [int(service[4], 16) for service in services] - test_routers.append(node) - - for router in test_routers: - self.assertIn(router.get_addr16(), service_rlocs) - - #--------------------------------------------------------------------------------- - # On-mesh prefix - - # Publish the same on-mesh prefix on different nodes (low - # preference on end-devices, medium preference on routers, and - # high on leader) one by one and then unpublish them one by one. - # Verify that at each step the entries in the network data are - # correct. Particularly verify that that higher preference - # entries replace lower preference ones even when there are - # already desired number in network data. - - num_low = 0 - num_med = 0 - num_high = 0 - - for node in end_devs: - node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'low') - self.simulator.go(WAIT_TIME) - num_low += 1 - prefixes = leader.get_prefixes() - self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) - - # Now add the entry as 'med' on routers and check that we see those in the list. - for node in routers: - node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') - self.simulator.go(WAIT_TIME) - num_med += 1 - prefixes = leader.get_prefixes() - self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) - - leader.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'high') - self.simulator.go(WAIT_TIME) - num_high += 1 - prefixes = leader.get_prefixes() - self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) - - for node in routers: - node.netdata_unpublish_prefix(ON_MESH_PREFIX) - self.simulator.go(WAIT_TIME) - num_med -= 1 - prefixes = leader.get_prefixes() - self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) - - leader.netdata_unpublish_prefix(ON_MESH_PREFIX) - self.simulator.go(WAIT_TIME) - num_high -= 1 - prefixes = leader.get_prefixes() - self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) - - for node in end_devs: - node.netdata_unpublish_prefix(ON_MESH_PREFIX) - self.simulator.go(WAIT_TIME) - num_low -= 1 - prefixes = leader.get_prefixes() - self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) - - #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Verify that when removing extra entries, non-preferred entries - # are removed first over preferred ones. Entries from routers are - # preferred over similar entries from end-devices. - - # Publish prefix entry on `end_dev1` and verify that it is added. - - end_dev1.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') - self.simulator.go(WAIT_TIME) - prefixes = leader.get_prefixes() - self.check_num_of_prefixes(prefixes, 0, 1, 0) - - # Publish same prefix on all routers (again as `med` preference). - # Verify that we reach the desired number of prefix entries in network - # data and that the entry from `end_dev1` is present in network data. - - for node in routers: - node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') - self.simulator.go(WAIT_TIME) - prefixes = leader.get_prefixes() - self.check_num_of_prefixes(prefixes, 0, 1 + len(routers), 0) - self.assertTrue(1 + len(routers) >= DESIRED_NUM_ON_MESH_PREFIX) - # `prefixes` is a list of format 'fd00:1234:0:0::/64 paos low a802' - rlocs = [int(prefix.split(' ')[3], 16) for prefix in prefixes] - self.assertTrue(rlocs.count(end_dev1.get_addr16()) == 1) - - # Publish same prefix now with `high` preference on leader. - # Since it is `high` preference, it is added to network data - # which leads to total number of entries to go above the desired - # number temporarily and trigger other nodes to try to remove - # their entry. The entries from routers should be preferred over - # the one from `end_dev1` so that is the one we expect to be - # removed. We check that this is the case (i.e., the entry from - # `end_dev1` is no longer present in network data). - - leader.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'high') - self.simulator.go(WAIT_TIME) - prefixes = leader.get_prefixes() - self.check_num_of_prefixes(prefixes, 0, 1 + len(routers), 1) - rlocs = [int(prefix.split(' ')[3], 16) for prefix in prefixes] - self.assertTrue(rlocs.count(end_dev1.get_addr16()) == 0) - - #--------------------------------------------------------------------------------- - # External route - - # Publish same external route on all nodes with low preference. - - num = 0 - for node in nodes: - node.netdata_publish_route(EXTERNAL_ROUTE, EXTERNAL_FLAGS, 'low') - self.simulator.go(WAIT_TIME) - num += 1 - routes = leader.get_routes() - self.check_num_of_routes(routes, num, 0, 0) - - # Change the preference level of the existing entry on leader to high. - - leader.netdata_publish_route(EXTERNAL_ROUTE, EXTERNAL_FLAGS, 'high') - self.simulator.go(WAIT_TIME) - routes = leader.get_routes() - self.check_num_of_routes(routes, num - 1, 0, 1) - - # Replace the published route on leader with '::/0'. - leader.netdata_publish_replace(EXTERNAL_ROUTE, '::/0', EXTERNAL_FLAGS, 'med') - self.simulator.go(1) - routes = leader.get_routes() - self.assertEqual([route.split(' ')[0] == '::/0' for route in routes].count(True), 1) - - # Replace it back to the original route. - leader.netdata_publish_replace('::/0', EXTERNAL_ROUTE, EXTERNAL_FLAGS, 'high') - self.simulator.go(WAIT_TIME) - routes = leader.get_routes() - self.assertEqual([route.split(' ')[0] == '::/0' for route in routes].count(True), 0) - self.check_num_of_routes(routes, num - 1, 0, 1) - - # Publish the same prefix on leader as an on-mesh prefix. Make - # sure it is removed from external routes and now seen in the - # prefix list. - - leader.netdata_publish_prefix(EXTERNAL_ROUTE, ON_MESH_FLAGS, 'low') - self.simulator.go(WAIT_TIME) - routes = leader.get_routes() - self.check_num_of_routes(routes, num - 1, 0, 0) - - prefixes = leader.get_prefixes() - print(prefixes) - self.assertIn(EXTERNAL_ROUTE, [prefix.split()[0] for prefix in prefixes]) - - -if __name__ == '__main__': - unittest.main()