aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAbtin Keshavarzian <abtink@google.com>2022-07-21 21:43:11 -0700
committerGitHub <noreply@github.com>2022-07-21 21:43:11 -0700
commit19ca3a0a3c66cb1cf785fdff5bd7c0ff1c5dfb09 (patch)
tree4db558631d049ee68455accdf878c20e13c32c2b
parent96819f934ac715a2cc45cc5ae4c963ad2f4951b2 (diff)
downloadopenthread-19ca3a0a3c66cb1cf785fdff5bd7c0ff1c5dfb09.tar.gz
[test] add unit test `test_routing_manager` (#7927)
This commit adds a unit test for `RoutingManager` which builds a foundation for writing test cases covering different behaviors of `RoutingManager`, e.g., validating emitted router advertisements, sending custom RA messages from different routers on AIL, adding or removing entries in Network Data while monitoring the behavior of `RoutingManager`.
-rw-r--r--tests/unit/CMakeLists.txt21
-rw-r--r--tests/unit/Makefile.am5
-rw-r--r--tests/unit/test_routing_manager.cpp844
3 files changed, 870 insertions, 0 deletions
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index 6d7b62dea..ae1e210af 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -857,6 +857,27 @@ add_executable(ot-test-serial-number
test_serial_number.cpp
)
+add_executable(ot-test-routing-manager
+ test_routing_manager.cpp
+)
+
+target_include_directories(ot-test-routing-manager
+ PRIVATE
+ ${COMMON_INCLUDES}
+)
+
+target_compile_options(ot-test-routing-manager
+ PRIVATE
+ ${COMMON_COMPILE_OPTIONS}
+)
+
+target_link_libraries(ot-test-routing-manager
+ PRIVATE
+ ${COMMON_LIBS}
+)
+
+add_test(NAME ot-test-routing-manager COMMAND ot-test-routing-manager)
+
target_include_directories(ot-test-serial-number
PRIVATE
${COMMON_INCLUDES}
diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am
index 2063dedd4..a99b2d861 100644
--- a/tests/unit/Makefile.am
+++ b/tests/unit/Makefile.am
@@ -148,6 +148,7 @@ check_PROGRAMS += \
ot-test-priority-queue \
ot-test-pskc \
ot-test-serial-number \
+ ot-test-routing-manager \
ot-test-smart-ptrs \
ot-test-string \
ot-test-timer \
@@ -347,6 +348,10 @@ ot_test_meshcop_LDADD = $(COMMON_LDADD)
ot_test_meshcop_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS)
ot_test_meshcop_SOURCES = $(COMMON_SOURCES) test_meshcop.cpp
+ot_test_routing_manager_LDADD = $(COMMON_LDADD)
+ot_test_routing_manager_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS)
+ot_test_routing_manager_SOURCES = $(COMMON_SOURCES) test_routing_manager.cpp
+
ot_test_serial_number_LDADD = $(COMMON_LDADD)
ot_test_serial_number_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS)
ot_test_serial_number_SOURCES = $(COMMON_SOURCES) test_serial_number.cpp
diff --git a/tests/unit/test_routing_manager.cpp b/tests/unit/test_routing_manager.cpp
new file mode 100644
index 000000000..cf48e8119
--- /dev/null
+++ b/tests/unit/test_routing_manager.cpp
@@ -0,0 +1,844 @@
+/*
+ * Copyright (c) 2022, 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 <openthread/config.h>
+
+#include "test_platform.h"
+#include "test_util.hpp"
+
+#include <openthread/thread.h>
+
+#include "border_router/routing_manager.hpp"
+#include "common/arg_macros.hpp"
+#include "common/instance.hpp"
+#include "net/icmp6.hpp"
+#include "net/nd6.hpp"
+
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+using namespace ot;
+
+// Logs a message and adds current time (sNow) as "<hours>:<min>:<secs>.<msec>"
+#define Log(...) \
+ printf("%02u:%02u:%02u.%03u " OT_FIRST_ARG(__VA_ARGS__) "\n", (sNow / 36000000), (sNow / 60000) % 60, \
+ (sNow / 1000) % 60, sNow % 1000 OT_REST_ARGS(__VA_ARGS__))
+
+static constexpr uint32_t kInfraIfIndex = 1;
+static const char kInfraIfAddress[] = "fe80::1";
+
+static constexpr uint32_t kValidLitime = 2000;
+static constexpr uint32_t kPreferredLifetime = 1800;
+
+static otInstance *sInstance;
+
+static uint32_t sNow = 0;
+static uint32_t sAlarmTime;
+static bool sAlarmOn = false;
+
+static otRadioFrame sRadioTxFrame;
+static uint8_t sRadioTxFramePsdu[OT_RADIO_FRAME_MAX_SIZE];
+static bool sRadioTxOngoing = false;
+
+using Icmp6Packet = Ip6::Nd::RouterAdvertMessage::Icmp6Packet;
+
+enum ExpectedPio
+{
+ kNoPio, // Expect to see no PIO in RA.
+ kPioAdvertisingLocalOnLink, // Expect to see local on-link prefix advertised (non-zero preferred lifetime).
+ kPioDeprecatingLocalOnLink, // Expect to see local on-link prefix deprecated (zero preferred lifetime).
+};
+
+static Ip6::Address sInfraIfAddress;
+
+bool sRsEmitted; // Indicates if an RS message was emitted by BR.
+bool sRaValidated; // Indicates if an RA was emitted by BR and successfully validated.
+bool sSawExpectedRio; // Indicates if the emitted RA by BR contained an RIO with `sExpectedRioPrefix`
+ExpectedPio sExpectedPio; // Expected PIO in the emitted RA by BR (MUST be seen in RA to set `sRaValidated`).
+Ip6::Prefix sExpectedRioPrefix; // Expected RIO prefix to see in RA (MUST be seen to set `sSawExpectedRio`).
+
+//----------------------------------------------------------------------------------------------------------------------
+// Function prototypes
+
+void ProcessRadioTxAndTasklets(void);
+void AdvanceTime(uint32_t aDuration);
+void LogRouterAdvert(const Icmp6Packet &aPacket);
+void ValidateRouterAdvert(const Icmp6Packet &aPacket);
+const char *PreferenceToString(uint8_t aPreference);
+void SendRouterAdvert(const Ip6::Address &aAddress, const Icmp6Packet &aPacket);
+
+//----------------------------------------------------------------------------------------------------------------------
+// `otPlatRadio
+
+extern "C" {
+
+otError otPlatRadioTransmit(otInstance *, otRadioFrame *)
+{
+ sRadioTxOngoing = true;
+
+ return OT_ERROR_NONE;
+}
+
+otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *)
+{
+ return &sRadioTxFrame;
+}
+
+//----------------------------------------------------------------------------------------------------------------------
+// `otPlatAlaram
+
+void otPlatAlarmMilliStop(otInstance *)
+{
+ sAlarmOn = false;
+}
+
+void otPlatAlarmMilliStartAt(otInstance *, uint32_t aT0, uint32_t aDt)
+{
+ sAlarmOn = true;
+ sAlarmTime = aT0 + aDt;
+}
+
+uint32_t otPlatAlarmMilliGetNow(void)
+{
+ return sNow;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+// otPlatInfraIf
+
+bool otPlatInfraIfHasAddress(uint32_t aInfraIfIndex, const otIp6Address *aAddress)
+{
+ VerifyOrQuit(aInfraIfIndex == kInfraIfIndex);
+
+ return AsCoreType(aAddress) == sInfraIfAddress;
+}
+
+otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex,
+ const otIp6Address *aDestAddress,
+ const uint8_t * aBuffer,
+ uint16_t aBufferLength)
+{
+ Icmp6Packet packet;
+
+ Log("otPlatInfraIfSendIcmp6Nd(aDestAddr: %s, aBufferLength:%u)", AsCoreType(aDestAddress).ToString().AsCString(),
+ aBufferLength);
+
+ VerifyOrQuit(aInfraIfIndex == kInfraIfIndex);
+
+ packet.Init(aBuffer, aBufferLength);
+
+ VerifyOrQuit(aBufferLength >= sizeof(Ip6::Icmp::Header));
+
+ switch (reinterpret_cast<const Ip6::Icmp::Header *>(aBuffer)->GetType())
+ {
+ case Ip6::Icmp::Header::kTypeRouterSolicit:
+ Log(" Router Solicit message");
+ sRsEmitted = true;
+ break;
+
+ case Ip6::Icmp::Header::kTypeRouterAdvert:
+ Log(" Router Advertisement message");
+ LogRouterAdvert(packet);
+ ValidateRouterAdvert(packet);
+ sRaValidated = true;
+ break;
+
+ default:
+ VerifyOrQuit(false, "Bad ICMP6 type");
+ }
+
+ return OT_ERROR_NONE;
+}
+
+} // extern "C"
+
+//---------------------------------------------------------------------------------------------------------------------
+
+void ProcessRadioTxAndTasklets(void)
+{
+ do
+ {
+ if (sRadioTxOngoing)
+ {
+ sRadioTxOngoing = false;
+ otPlatRadioTxStarted(sInstance, &sRadioTxFrame);
+ otPlatRadioTxDone(sInstance, &sRadioTxFrame, nullptr, OT_ERROR_NONE);
+ }
+
+ otTaskletsProcess(sInstance);
+ } while (otTaskletsArePending(sInstance));
+}
+
+void AdvanceTime(uint32_t aDuration)
+{
+ uint32_t time = sNow + aDuration;
+
+ Log("AdvanceTime for %u.%03u", aDuration / 1000, aDuration % 1000);
+
+ while (sAlarmTime <= time)
+ {
+ ProcessRadioTxAndTasklets();
+ sNow = sAlarmTime;
+ otPlatAlarmMilliFired(sInstance);
+ }
+
+ ProcessRadioTxAndTasklets();
+ sNow = time;
+}
+
+void ValidateRouterAdvert(const Icmp6Packet &aPacket)
+{
+ Ip6::Nd::RouterAdvertMessage raMsg(aPacket);
+
+ VerifyOrQuit(raMsg.IsValid());
+
+ for (const Ip6::Nd::Option &option : raMsg)
+ {
+ switch (option.GetType())
+ {
+ case Ip6::Nd::Option::kTypePrefixInfo:
+ {
+ const Ip6::Nd::PrefixInfoOption &pio = static_cast<const Ip6::Nd::PrefixInfoOption &>(option);
+ Ip6::Prefix prefix;
+ Ip6::Prefix localOnLink;
+
+ VerifyOrQuit(pio.IsValid());
+ pio.GetPrefix(prefix);
+
+ VerifyOrQuit(sExpectedPio != kNoPio, "Received RA contain an unexpected PIO");
+
+ SuccessOrQuit(otBorderRoutingGetOnLinkPrefix(sInstance, &localOnLink));
+ VerifyOrQuit(prefix == localOnLink);
+
+ if (sExpectedPio == kPioAdvertisingLocalOnLink)
+ {
+ VerifyOrQuit(pio.GetPreferredLifetime() > 0, "On link prefix is deprecated unexpectedly");
+ }
+ else
+ {
+ VerifyOrQuit(pio.GetPreferredLifetime() == 0, "On link prefix is not deprecated");
+ }
+
+ break;
+ }
+
+ case Ip6::Nd::Option::kTypeRouteInfo:
+ {
+ const Ip6::Nd::RouteInfoOption &rio = static_cast<const Ip6::Nd::RouteInfoOption &>(option);
+ Ip6::Prefix prefix;
+
+ VerifyOrQuit(rio.IsValid());
+ rio.GetPrefix(prefix);
+
+ if (prefix == sExpectedRioPrefix)
+ {
+ sSawExpectedRio = true;
+ }
+
+ break;
+ }
+
+ default:
+ VerifyOrQuit(false, "Unexpected option type in RA msg");
+ }
+ }
+}
+
+void LogRouterAdvert(const Icmp6Packet &aPacket)
+{
+ Ip6::Nd::RouterAdvertMessage raMsg(aPacket);
+
+ VerifyOrQuit(raMsg.IsValid());
+
+ Log(" RA header - lifetime %u, prf:%s", raMsg.GetHeader().GetRouterLifetime(),
+ PreferenceToString(raMsg.GetHeader().GetDefaultRouterPreference()));
+
+ for (const Ip6::Nd::Option &option : raMsg)
+ {
+ switch (option.GetType())
+ {
+ case Ip6::Nd::Option::kTypePrefixInfo:
+ {
+ const Ip6::Nd::PrefixInfoOption &pio = static_cast<const Ip6::Nd::PrefixInfoOption &>(option);
+ Ip6::Prefix prefix;
+
+ VerifyOrQuit(pio.IsValid());
+ pio.GetPrefix(prefix);
+ Log(" PIO - %s, flags:%s%s, valid:%u, preferred:%u", prefix.ToString().AsCString(),
+ pio.IsOnLinkFlagSet() ? "L" : "", pio.IsAutoAddrConfigFlagSet() ? "A" : "", pio.GetValidLifetime(),
+ pio.GetPreferredLifetime());
+ break;
+ }
+
+ case Ip6::Nd::Option::kTypeRouteInfo:
+ {
+ const Ip6::Nd::RouteInfoOption &rio = static_cast<const Ip6::Nd::RouteInfoOption &>(option);
+ Ip6::Prefix prefix;
+
+ VerifyOrQuit(rio.IsValid());
+ rio.GetPrefix(prefix);
+ Log(" RIO - %s, prf:%s, lifetime:%u", prefix.ToString().AsCString(),
+ PreferenceToString(rio.GetPreference()), rio.GetRouteLifetime());
+ break;
+ }
+
+ default:
+ VerifyOrQuit(false, "Bad option type in RA msg");
+ }
+ }
+}
+
+const char *PreferenceToString(uint8_t aPreference)
+{
+ const char *str = "";
+
+ switch (aPreference)
+ {
+ case NetworkData::kRoutePreferenceLow:
+ str = "low";
+ break;
+
+ case NetworkData::kRoutePreferenceMedium:
+ str = "med";
+ break;
+
+ case NetworkData::kRoutePreferenceHigh:
+ str = "high";
+ break;
+
+ default:
+ break;
+ }
+
+ return str;
+}
+
+void SendRouterAdvert(const Ip6::Address &aAddress, const Icmp6Packet &aPacket)
+{
+ otPlatInfraIfRecvIcmp6Nd(sInstance, kInfraIfIndex, &aAddress, aPacket.GetBytes(), aPacket.GetLength());
+}
+
+Ip6::Prefix PrefixFromString(const char *aString, uint8_t aPrefixLength)
+{
+ Ip6::Prefix prefix;
+
+ SuccessOrQuit(AsCoreType(&prefix.mPrefix).FromString(aString));
+ prefix.mLength = aPrefixLength;
+
+ return prefix;
+}
+
+Ip6::Address AddressFromString(const char *aString)
+{
+ Ip6::Address address;
+
+ SuccessOrQuit(address.FromString(aString));
+
+ return address;
+}
+
+void TestRoutingManager(void)
+{
+ Instance & instance = *static_cast<Instance *>(testInitInstance());
+ BorderRouter::RoutingManager & rm = instance.Get<BorderRouter::RoutingManager>();
+ Ip6::Prefix localOnLink;
+ Ip6::Prefix localOmr;
+ Ip6::Prefix onLinkPrefix = PrefixFromString("2000:abba:baba::", 64);
+ Ip6::Prefix routePrefix = PrefixFromString("2000:1234:5678::", 64);
+ Ip6::Prefix omrPrefix = PrefixFromString("2000:0000:1111:4444::", 64);
+ Ip6::Address routerAddressA = AddressFromString("fd00::aaaa");
+ Ip6::Address routerAddressB = AddressFromString("fd00::bbbb");
+ BorderRouter::RoutingManager::PrefixTableIterator iter;
+ BorderRouter::RoutingManager::PrefixTableEntry entry;
+ NetworkData::Iterator iterator;
+ NetworkData::OnMeshPrefixConfig prefixConfig;
+ NetworkData::ExternalRouteConfig routeConfig;
+ uint8_t counter;
+ uint8_t buffer[800];
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Initialize OT instance.
+
+ sNow = 0;
+ sInstance = &instance;
+
+ memset(&sRadioTxFrame, 0, sizeof(sRadioTxFrame));
+ sRadioTxFrame.mPsdu = sRadioTxFramePsdu;
+
+ SuccessOrQuit(sInfraIfAddress.FromString(kInfraIfAddress));
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Initialize Border Router and start Thread operation.
+
+ SuccessOrQuit(otBorderRoutingInit(sInstance, kInfraIfIndex, /* aInfraIfIsRunning */ true));
+
+ SuccessOrQuit(otLinkSetPanId(sInstance, 0x1234));
+ SuccessOrQuit(otIp6SetEnabled(sInstance, true));
+ SuccessOrQuit(otThreadSetEnabled(sInstance, true));
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Ensure device starts as leader.
+
+ AdvanceTime(10000);
+
+ VerifyOrQuit(otThreadGetDeviceRole(sInstance) == OT_DEVICE_ROLE_LEADER);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Start Routing Manager. Check emitted RS and RA messages.
+
+ sRsEmitted = false;
+ sRaValidated = false;
+ sSawExpectedRio = false;
+ sExpectedPio = kPioAdvertisingLocalOnLink;
+
+ Log("Starting RoutingManager");
+
+ SuccessOrQuit(rm.SetEnabled(true));
+
+ SuccessOrQuit(rm.GetOnLinkPrefix(localOnLink));
+ SuccessOrQuit(rm.GetOmrPrefix(localOmr));
+
+ Log("Local on-link prefix is %s", localOnLink.ToString().AsCString());
+ Log("Local OMR prefix is %s", localOmr.ToString().AsCString());
+
+ sExpectedRioPrefix = localOmr;
+
+ AdvanceTime(30000);
+
+ VerifyOrQuit(sRsEmitted);
+ VerifyOrQuit(sRaValidated);
+ VerifyOrQuit(sSawExpectedRio);
+ Log("Received RA was validated");
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Check Network Data to include the local OMR and on-link prefix.
+
+ iterator = NetworkData::kIteratorInit;
+
+ // We expect to see OMR prefix in net data as on-mesh prefix.
+ SuccessOrQuit(instance.Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig));
+ VerifyOrQuit(prefixConfig.GetPrefix() == localOmr);
+ VerifyOrQuit(prefixConfig.mStable == true);
+ VerifyOrQuit(prefixConfig.mSlaac == true);
+ VerifyOrQuit(prefixConfig.mPreferred == true);
+ VerifyOrQuit(prefixConfig.mOnMesh == true);
+ VerifyOrQuit(prefixConfig.mDefaultRoute == false);
+
+ VerifyOrQuit(instance.Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNotFound);
+
+ iterator = NetworkData::kIteratorInit;
+
+ // We expect to see local on-link prefix in net data as external route.
+ SuccessOrQuit(instance.Get<NetworkData::Leader>().GetNextExternalRoute(iterator, routeConfig));
+ VerifyOrQuit(routeConfig.GetPrefix() == localOnLink);
+ VerifyOrQuit(routeConfig.mStable == true);
+ VerifyOrQuit(routeConfig.mPreference == NetworkData::kRoutePreferenceMedium);
+
+ VerifyOrQuit(instance.Get<NetworkData::Leader>().GetNextExternalRoute(iterator, routeConfig) == kErrorNotFound);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Send an RA from router A with a new on-link (PIO) and route prefix (RIO).
+
+ {
+ Ip6::Nd::RouterAdvertMessage raMsg(Ip6::Nd::RouterAdvertMessage::Header(), buffer);
+
+ SuccessOrQuit(raMsg.AppendPrefixInfoOption(onLinkPrefix, kValidLitime, kPreferredLifetime));
+ SuccessOrQuit(raMsg.AppendRouteInfoOption(routePrefix, kValidLitime, NetworkData::kRoutePreferenceMedium));
+
+ SendRouterAdvert(routerAddressA, raMsg.GetAsPacket());
+
+ Log("Send RA from router A");
+ LogRouterAdvert(raMsg.GetAsPacket());
+ }
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Check that the local on-link prefix is now deprecating in the new RA.
+
+ sRaValidated = false;
+ sSawExpectedRio = false;
+ sExpectedPio = kPioDeprecatingLocalOnLink;
+
+ AdvanceTime(10000);
+ VerifyOrQuit(sRaValidated);
+ VerifyOrQuit(sSawExpectedRio);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Check the discovered prefix table and ensure info from router A
+ // is present in the table.
+
+ counter = 0;
+
+ rm.InitPrefixTableIterator(iter);
+
+ while (rm.GetNextPrefixTableEntry(iter, entry) == kErrorNone)
+ {
+ counter++;
+ VerifyOrQuit(AsCoreType(&entry.mRouterAddress) == routerAddressA);
+
+ if (entry.mIsOnLink)
+ {
+ VerifyOrQuit(AsCoreType(&entry.mPrefix) == onLinkPrefix);
+ VerifyOrQuit(entry.mValidLifetime = kValidLitime);
+ VerifyOrQuit(entry.mPreferredLifetime = kPreferredLifetime);
+ }
+ else
+ {
+ VerifyOrQuit(AsCoreType(&entry.mPrefix) == routePrefix);
+ VerifyOrQuit(entry.mValidLifetime = kValidLitime);
+ VerifyOrQuit(static_cast<int8_t>(entry.mRoutePreference) == NetworkData::kRoutePreferenceMedium);
+ }
+ }
+
+ VerifyOrQuit(counter == 2);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Check Network Data to include new prefixes from router A.
+
+ iterator = NetworkData::kIteratorInit;
+
+ // We expect to see OMR prefix in net data as on-mesh prefix.
+ SuccessOrQuit(instance.Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig));
+ VerifyOrQuit(prefixConfig.GetPrefix() == localOmr);
+ VerifyOrQuit(prefixConfig.mStable == true);
+ VerifyOrQuit(prefixConfig.mSlaac == true);
+ VerifyOrQuit(prefixConfig.mPreferred == true);
+ VerifyOrQuit(prefixConfig.mOnMesh == true);
+ VerifyOrQuit(prefixConfig.mDefaultRoute == false);
+
+ VerifyOrQuit(instance.Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNotFound);
+
+ iterator = NetworkData::kIteratorInit;
+
+ counter = 0;
+
+ // We expect to see 3 entries, our local on link and new prefixes from router A.
+ while (instance.Get<NetworkData::Leader>().GetNextExternalRoute(iterator, routeConfig) == kErrorNone)
+ {
+ VerifyOrQuit((routeConfig.GetPrefix() == localOnLink) || (routeConfig.GetPrefix() == onLinkPrefix) ||
+ (routeConfig.GetPrefix() == routePrefix));
+ VerifyOrQuit(static_cast<int8_t>(routeConfig.mPreference) == NetworkData::kRoutePreferenceMedium);
+ counter++;
+ }
+
+ VerifyOrQuit(counter == 3);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Send an RA from router B with same route prefix (RIO) but with
+ // high route preference.
+
+ {
+ Ip6::Nd::RouterAdvertMessage raMsg(Ip6::Nd::RouterAdvertMessage::Header(), buffer);
+
+ SuccessOrQuit(raMsg.AppendRouteInfoOption(routePrefix, kValidLitime, NetworkData::kRoutePreferenceHigh));
+
+ SendRouterAdvert(routerAddressB, raMsg.GetAsPacket());
+
+ Log("Send RA from router B");
+ LogRouterAdvert(raMsg.GetAsPacket());
+ }
+
+ AdvanceTime(10000);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Check the discovered prefix table and ensure info from router B
+ // is also included in the table.
+
+ counter = 0;
+
+ rm.InitPrefixTableIterator(iter);
+
+ while (rm.GetNextPrefixTableEntry(iter, entry) == kErrorNone)
+ {
+ const Ip6::Address &routerAddr = AsCoreType(&entry.mRouterAddress);
+ counter++;
+
+ if (routerAddr == routerAddressA)
+ {
+ if (entry.mIsOnLink)
+ {
+ VerifyOrQuit(AsCoreType(&entry.mPrefix) == onLinkPrefix);
+ VerifyOrQuit(entry.mValidLifetime = kValidLitime);
+ VerifyOrQuit(entry.mPreferredLifetime = kPreferredLifetime);
+ }
+ else
+ {
+ VerifyOrQuit(AsCoreType(&entry.mPrefix) == routePrefix);
+ VerifyOrQuit(entry.mValidLifetime = kValidLitime);
+ VerifyOrQuit(static_cast<int8_t>(entry.mRoutePreference) == NetworkData::kRoutePreferenceMedium);
+ }
+ }
+ else if (routerAddr == routerAddressB)
+ {
+ VerifyOrQuit(!entry.mIsOnLink);
+ VerifyOrQuit(AsCoreType(&entry.mPrefix) == routePrefix);
+ VerifyOrQuit(entry.mValidLifetime = kValidLitime);
+ VerifyOrQuit(static_cast<int8_t>(entry.mRoutePreference) == NetworkData::kRoutePreferenceHigh);
+ }
+ else
+ {
+ VerifyOrQuit(false, "Unexpected entry in prefix table with unknown router address");
+ }
+ }
+
+ VerifyOrQuit(counter == 3);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Check Network Data.
+
+ iterator = NetworkData::kIteratorInit;
+
+ // We expect to see OMR prefix in net data as on-mesh prefix
+ SuccessOrQuit(instance.Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig));
+ VerifyOrQuit(prefixConfig.GetPrefix() == localOmr);
+ VerifyOrQuit(prefixConfig.mStable == true);
+ VerifyOrQuit(prefixConfig.mSlaac == true);
+ VerifyOrQuit(prefixConfig.mPreferred == true);
+ VerifyOrQuit(prefixConfig.mOnMesh == true);
+ VerifyOrQuit(prefixConfig.mDefaultRoute == false);
+
+ VerifyOrQuit(instance.Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNotFound);
+
+ iterator = NetworkData::kIteratorInit;
+
+ counter = 0;
+
+ // We expect to see 3 entries, our local on link and new prefixes
+ // from router A and B. The `routePrefix` now should have high
+ // preference.
+
+ while (instance.Get<NetworkData::Leader>().GetNextExternalRoute(iterator, routeConfig) == kErrorNone)
+ {
+ counter++;
+
+ if (routeConfig.GetPrefix() == routePrefix)
+ {
+ VerifyOrQuit(static_cast<int8_t>(routeConfig.mPreference) == NetworkData::kRoutePreferenceHigh);
+ }
+ else
+ {
+ VerifyOrQuit((routeConfig.GetPrefix() == localOnLink) || (routeConfig.GetPrefix() == onLinkPrefix));
+ VerifyOrQuit(static_cast<int8_t>(routeConfig.mPreference) == NetworkData::kRoutePreferenceMedium);
+ }
+ }
+
+ VerifyOrQuit(counter == 3);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Send an RA from router B removing the route prefix.
+
+ {
+ Ip6::Nd::RouterAdvertMessage raMsg(Ip6::Nd::RouterAdvertMessage::Header(), buffer);
+
+ SuccessOrQuit(raMsg.AppendRouteInfoOption(routePrefix, 0, NetworkData::kRoutePreferenceHigh));
+
+ SendRouterAdvert(routerAddressB, raMsg.GetAsPacket());
+
+ Log("Send RA from router B");
+ LogRouterAdvert(raMsg.GetAsPacket());
+ }
+
+ AdvanceTime(10000);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Check the discovered prefix table and ensure info from router B
+ // is now removed from the table.
+
+ counter = 0;
+
+ rm.InitPrefixTableIterator(iter);
+
+ while (rm.GetNextPrefixTableEntry(iter, entry) == kErrorNone)
+ {
+ counter++;
+
+ VerifyOrQuit(AsCoreType(&entry.mRouterAddress) == routerAddressA);
+
+ if (entry.mIsOnLink)
+ {
+ VerifyOrQuit(AsCoreType(&entry.mPrefix) == onLinkPrefix);
+ VerifyOrQuit(entry.mValidLifetime = kValidLitime);
+ VerifyOrQuit(entry.mPreferredLifetime = kPreferredLifetime);
+ }
+ else
+ {
+ VerifyOrQuit(AsCoreType(&entry.mPrefix) == routePrefix);
+ VerifyOrQuit(entry.mValidLifetime = kValidLitime);
+ VerifyOrQuit(static_cast<int8_t>(entry.mRoutePreference) == NetworkData::kRoutePreferenceMedium);
+ }
+ }
+
+ VerifyOrQuit(counter == 2);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Check Network Data, all prefixes should be again at medium preference.
+
+ counter = 0;
+ iterator = NetworkData::kIteratorInit;
+
+ while (instance.Get<NetworkData::Leader>().GetNextExternalRoute(iterator, routeConfig) == kErrorNone)
+ {
+ counter++;
+
+ VerifyOrQuit((routeConfig.GetPrefix() == localOnLink) || (routeConfig.GetPrefix() == onLinkPrefix) ||
+ (routeConfig.GetPrefix() == routePrefix));
+ VerifyOrQuit(static_cast<int8_t>(routeConfig.mPreference) == NetworkData::kRoutePreferenceMedium);
+ }
+
+ VerifyOrQuit(counter == 3);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Add a new OMR prefix directly into net data. The new prefix should
+ // be favored over the local OMR prefix.
+
+ prefixConfig.Clear();
+ prefixConfig.mPrefix = omrPrefix;
+ prefixConfig.mStable = true;
+ prefixConfig.mSlaac = true;
+ prefixConfig.mPreferred = true;
+ prefixConfig.mOnMesh = true;
+ prefixConfig.mDefaultRoute = false;
+ prefixConfig.mPreference = NetworkData::kRoutePreferenceMedium;
+
+ SuccessOrQuit(otBorderRouterAddOnMeshPrefix(sInstance, &prefixConfig));
+ SuccessOrQuit(otBorderRouterRegister(sInstance));
+
+ AdvanceTime(100);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Make sure BR emits RA with new OMR prefix now.
+
+ sRaValidated = false;
+ sSawExpectedRio = false;
+ sExpectedRioPrefix = omrPrefix;
+
+ AdvanceTime(20000);
+
+ VerifyOrQuit(sRaValidated);
+ VerifyOrQuit(sSawExpectedRio);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Check Network Data. We should now see that the local OMR prefix
+ // is removed.
+
+ iterator = NetworkData::kIteratorInit;
+
+ // We expect to see new OMR prefix in net data.
+ SuccessOrQuit(instance.Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig));
+ VerifyOrQuit(prefixConfig.GetPrefix() == omrPrefix);
+ VerifyOrQuit(prefixConfig.mStable == true);
+ VerifyOrQuit(prefixConfig.mSlaac == true);
+ VerifyOrQuit(prefixConfig.mPreferred == true);
+ VerifyOrQuit(prefixConfig.mOnMesh == true);
+ VerifyOrQuit(prefixConfig.mDefaultRoute == false);
+
+ VerifyOrQuit(instance.Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNotFound);
+
+ counter = 0;
+ iterator = NetworkData::kIteratorInit;
+
+ while (instance.Get<NetworkData::Leader>().GetNextExternalRoute(iterator, routeConfig) == kErrorNone)
+ {
+ counter++;
+
+ VerifyOrQuit((routeConfig.GetPrefix() == localOnLink) || (routeConfig.GetPrefix() == onLinkPrefix) ||
+ (routeConfig.GetPrefix() == routePrefix));
+ VerifyOrQuit(static_cast<int8_t>(routeConfig.mPreference) == NetworkData::kRoutePreferenceMedium);
+ }
+
+ VerifyOrQuit(counter == 3);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Remove the OMR prefix previously added in net data.
+
+ SuccessOrQuit(otBorderRouterRemoveOnMeshPrefix(sInstance, &omrPrefix));
+ SuccessOrQuit(otBorderRouterRegister(sInstance));
+
+ AdvanceTime(100);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Make sure BR emits RA with local OMR prefix again.
+
+ sRaValidated = false;
+ sSawExpectedRio = false;
+ sExpectedRioPrefix = localOmr;
+
+ AdvanceTime(20000);
+
+ VerifyOrQuit(sRaValidated);
+ VerifyOrQuit(sSawExpectedRio);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Check Network Data. We should see that the local OMR prefix is
+ // added again.
+
+ iterator = NetworkData::kIteratorInit;
+
+ // We expect to see new OMR prefix in net data as on-mesh prefix.
+ SuccessOrQuit(instance.Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig));
+ VerifyOrQuit(prefixConfig.GetPrefix() == localOmr);
+ VerifyOrQuit(prefixConfig.mStable == true);
+ VerifyOrQuit(prefixConfig.mSlaac == true);
+ VerifyOrQuit(prefixConfig.mPreferred == true);
+ VerifyOrQuit(prefixConfig.mOnMesh == true);
+ VerifyOrQuit(prefixConfig.mDefaultRoute == false);
+
+ VerifyOrQuit(instance.Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNotFound);
+
+ counter = 0;
+ iterator = NetworkData::kIteratorInit;
+
+ while (instance.Get<NetworkData::Leader>().GetNextExternalRoute(iterator, routeConfig) == kErrorNone)
+ {
+ counter++;
+
+ VerifyOrQuit((routeConfig.GetPrefix() == localOnLink) || (routeConfig.GetPrefix() == onLinkPrefix) ||
+ (routeConfig.GetPrefix() == routePrefix));
+ VerifyOrQuit(static_cast<int8_t>(routeConfig.mPreference) == NetworkData::kRoutePreferenceMedium);
+ }
+
+ VerifyOrQuit(counter == 3);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ Log("End of test");
+
+ testFreeInstance(&instance);
+}
+
+#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+int main(void)
+{
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+ TestRoutingManager();
+ printf("All tests passed\n");
+#else
+ printf("BORDER_ROUTING feature is not enabled\n");
+#endif
+
+ return 0;
+}