/* * 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 #include "test_platform.h" #include "test_util.hpp" #include #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 "::." #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(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(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(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(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(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(testInitInstance()); BorderRouter::RoutingManager & rm = instance.Get(); 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().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().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNotFound); iterator = NetworkData::kIteratorInit; // We expect to see local on-link prefix in net data as external route. SuccessOrQuit(instance.Get().GetNextExternalRoute(iterator, routeConfig)); VerifyOrQuit(routeConfig.GetPrefix() == localOnLink); VerifyOrQuit(routeConfig.mStable == true); VerifyOrQuit(routeConfig.mPreference == NetworkData::kRoutePreferenceMedium); VerifyOrQuit(instance.Get().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(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().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().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().GetNextExternalRoute(iterator, routeConfig) == kErrorNone) { VerifyOrQuit((routeConfig.GetPrefix() == localOnLink) || (routeConfig.GetPrefix() == onLinkPrefix) || (routeConfig.GetPrefix() == routePrefix)); VerifyOrQuit(static_cast(routeConfig.mPreference) == NetworkData::kRoutePreferenceMedium); counter++; } VerifyOrQuit(counter == 3); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Send the same RA again from router A with the 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 the discovered prefix table and ensure info from router A // remains unchanged. 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(entry.mRoutePreference) == NetworkData::kRoutePreferenceMedium); } } VerifyOrQuit(counter == 2); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // 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(entry.mRoutePreference) == NetworkData::kRoutePreferenceMedium); } } else if (routerAddr == routerAddressB) { VerifyOrQuit(!entry.mIsOnLink); VerifyOrQuit(AsCoreType(&entry.mPrefix) == routePrefix); VerifyOrQuit(entry.mValidLifetime = kValidLitime); VerifyOrQuit(static_cast(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().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().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().GetNextExternalRoute(iterator, routeConfig) == kErrorNone) { counter++; if (routeConfig.GetPrefix() == routePrefix) { VerifyOrQuit(static_cast(routeConfig.mPreference) == NetworkData::kRoutePreferenceHigh); } else { VerifyOrQuit((routeConfig.GetPrefix() == localOnLink) || (routeConfig.GetPrefix() == onLinkPrefix)); VerifyOrQuit(static_cast(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(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().GetNextExternalRoute(iterator, routeConfig) == kErrorNone) { counter++; VerifyOrQuit((routeConfig.GetPrefix() == localOnLink) || (routeConfig.GetPrefix() == onLinkPrefix) || (routeConfig.GetPrefix() == routePrefix)); VerifyOrQuit(static_cast(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().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().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNotFound); counter = 0; iterator = NetworkData::kIteratorInit; while (instance.Get().GetNextExternalRoute(iterator, routeConfig) == kErrorNone) { counter++; VerifyOrQuit((routeConfig.GetPrefix() == localOnLink) || (routeConfig.GetPrefix() == onLinkPrefix) || (routeConfig.GetPrefix() == routePrefix)); VerifyOrQuit(static_cast(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().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().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNotFound); counter = 0; iterator = NetworkData::kIteratorInit; while (instance.Get().GetNextExternalRoute(iterator, routeConfig) == kErrorNone) { counter++; VerifyOrQuit((routeConfig.GetPrefix() == localOnLink) || (routeConfig.GetPrefix() == onLinkPrefix) || (routeConfig.GetPrefix() == routePrefix)); VerifyOrQuit(static_cast(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; }