diff --git a/include/openthread/icmp6.h b/include/openthread/icmp6.h index cefe596e4..c5fc276f9 100644 --- a/include/openthread/icmp6.h +++ b/include/openthread/icmp6.h @@ -66,6 +66,8 @@ typedef enum otIcmp6Type OT_ICMP6_TYPE_ECHO_REPLY = 129, ///< Echo Reply OT_ICMP6_TYPE_ROUTER_SOLICIT = 133, ///< Router Solicitation OT_ICMP6_TYPE_ROUTER_ADVERT = 134, ///< Router Advertisement + OT_ICMP6_TYPE_NEIGHBOR_SOLICIT = 135, ///< Neighbor Solicitation + OT_ICMP6_TYPE_NEIGHBOR_ADVERT = 136, ///< Neighbor Advertisement } otIcmp6Type; /** diff --git a/include/openthread/instance.h b/include/openthread/instance.h index f7953179a..65a2326ba 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (254) +#define OPENTHREAD_API_VERSION (255) /** * @addtogroup api-instance diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 7c4f3dac3..4b7a4978a 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -334,6 +334,9 @@ void RoutingManager::HandleReceived(const InfraIf::Icmp6Packet &aPacket, const I case Ip6::Icmp::Header::kTypeRouterSolicit: HandleRouterSolicit(aPacket, aSrcAddress); break; + case Ip6::Icmp::Header::kTypeNeighborAdvert: + HandleNeighborAdvertisement(aPacket, aSrcAddress); + break; default: break; } @@ -904,6 +907,19 @@ void RoutingManager::HandleRouterSolicit(const InfraIf::Icmp6Packet &aPacket, co ScheduleRoutingPolicyEvaluation(kToReplyToRs); } +void RoutingManager::HandleNeighborAdvertisement(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress) +{ + const Ip6::Nd::NeighborAdvertMessage *naMsg; + + VerifyOrExit(aPacket.GetLength() >= sizeof(naMsg)); + naMsg = reinterpret_cast(aPacket.GetBytes()); + + mDiscoveredPrefixTable.ProcessNeighborAdvertMessage(*naMsg, aSrcAddress); + +exit: + return; +} + void RoutingManager::HandleRouterAdvertisement(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress) { Ip6::Nd::RouterAdvertMessage routerAdvMessage(aPacket); @@ -1138,7 +1154,8 @@ void RoutingManager::ResetDiscoveredPrefixStaleTimer(void) RoutingManager::DiscoveredPrefixTable::DiscoveredPrefixTable(Instance &aInstance) : InstanceLocator(aInstance) - , mTimer(aInstance) + , mEntryTimer(aInstance) + , mRouterTimer(aInstance) , mSignalTask(aInstance) , mAllowDefaultRouteInNetData(false) { @@ -1190,6 +1207,8 @@ void RoutingManager::DiscoveredPrefixTable::ProcessRouterAdvertMessage(const Ip6 } } + UpdateRouterOnRx(*router); + RemoveRoutersWithNoEntries(); exit: @@ -1225,7 +1244,7 @@ void RoutingManager::DiscoveredPrefixTable::ProcessDefaultRoute(const Ip6::Nd::R entry->SetFrom(aRaHeader); } - mTimer.FireAtIfEarlier(entry->GetExpireTime()); + mEntryTimer.FireAtIfEarlier(entry->GetExpireTime()); Get().EvaluatePublishingPrefix(entry->GetPrefix()); SignalTableChanged(); @@ -1272,7 +1291,7 @@ void RoutingManager::DiscoveredPrefixTable::ProcessPrefixInfoOption(const Ip6::N entry->AdoptValidAndPreferredLiftimesFrom(newEntry); } - mTimer.FireAtIfEarlier(entry->GetExpireTime()); + mEntryTimer.FireAtIfEarlier(entry->GetExpireTime()); Get().EvaluatePublishingPrefix(entry->GetPrefix()); SignalTableChanged(); @@ -1316,7 +1335,7 @@ void RoutingManager::DiscoveredPrefixTable::ProcessRouteInfoOption(const Ip6::Nd entry->SetFrom(aRio); } - mTimer.FireAtIfEarlier(entry->GetExpireTime()); + mEntryTimer.FireAtIfEarlier(entry->GetExpireTime()); Get().EvaluatePublishingPrefix(entry->GetPrefix()); SignalTableChanged(); @@ -1445,7 +1464,7 @@ void RoutingManager::DiscoveredPrefixTable::RemoveAllEntries(void) } RemoveRoutersWithNoEntries(); - mTimer.Stop(); + mEntryTimer.Stop(); } void RoutingManager::DiscoveredPrefixTable::RemoveOrDeprecateOldEntries(TimeMilli aTimeThreshold) @@ -1476,6 +1495,36 @@ void RoutingManager::DiscoveredPrefixTable::RemoveOrDeprecateOldEntries(TimeMill RemoveExpiredEntries(); } +void RoutingManager::DiscoveredPrefixTable::RemoveOrDeprecateEntriesFromInactiveRouters(void) +{ + // Remove route prefix entries and deprecate on-link prefix entries + // in the table for routers that have reached the max NS probe + // attempts and considered as inactive. + + for (Router &router : mRouters) + { + if (router.mNsProbeCount <= Router::kMaxNsProbes) + { + continue; + } + + for (Entry &entry : router.mEntries) + { + if (entry.IsOnLinkPrefix() && !entry.IsDeprecated()) + { + entry.ClearPreferredLifetime(); + SignalTableChanged(); + } + else + { + entry.ClearValidLifetime(); + } + } + } + + RemoveExpiredEntries(); +} + TimeMilli RoutingManager::DiscoveredPrefixTable::CalculateNextStaleTime(TimeMilli aNow) const { TimeMilli onLinkStaleTime = aNow; @@ -1580,7 +1629,7 @@ exit: return shouldPublish; } -void RoutingManager::DiscoveredPrefixTable::HandleTimer(void) +void RoutingManager::DiscoveredPrefixTable::HandleEntryTimer(void) { RemoveExpiredEntries(); } @@ -1625,7 +1674,7 @@ void RoutingManager::DiscoveredPrefixTable::RemoveExpiredEntries(void) if (nextExpireTime != now.GetDistantFuture()) { - mTimer.FireAt(nextExpireTime); + mEntryTimer.FireAt(nextExpireTime); } } @@ -1634,6 +1683,91 @@ void RoutingManager::DiscoveredPrefixTable::SignalTableChanged(void) mSignalTask.Post(); } +void RoutingManager::DiscoveredPrefixTable::ProcessNeighborAdvertMessage( + const Ip6::Nd::NeighborAdvertMessage &aNaMessage, + const Ip6::Address & aSrcAddress) +{ + Router *router = mRouters.FindMatching(aSrcAddress); + + VerifyOrExit(router != nullptr); + VerifyOrExit(aNaMessage.IsValid()); + VerifyOrExit(aNaMessage.GetTargetAddress() == router->mAddress); + + LogInfo("Received NA from router %s", router->mAddress.ToString().AsCString()); + + UpdateRouterOnRx(*router); + +exit: + return; +} + +void RoutingManager::DiscoveredPrefixTable::UpdateRouterOnRx(Router &aRouter) +{ + aRouter.mNsProbeCount = 0; + aRouter.mTimeout = TimerMilli::GetNow() + Random::NonCrypto::AddJitter(Router::kActiveTimout, Router::kJitter); + + mRouterTimer.FireAtIfEarlier(aRouter.mTimeout); +} + +void RoutingManager::DiscoveredPrefixTable::HandleRouterTimer(void) +{ + TimeMilli now = TimerMilli::GetNow(); + TimeMilli nextTime = now.GetDistantFuture(); + + for (Router &router : mRouters) + { + if (router.mNsProbeCount > Router::kMaxNsProbes) + { + continue; + } + + if (router.mTimeout <= now) + { + router.mNsProbeCount++; + + if (router.mNsProbeCount > Router::kMaxNsProbes) + { + LogInfo("No response to all Neighbor Solicitations attempts from router %s", + router.mAddress.ToString().AsCString()); + continue; + } + + router.mTimeout = now + ((router.mNsProbeCount < Router::kMaxNsProbes) ? Router::kNsProbeRetryInterval + : Router::kNsProbeTimout); + + SendNeighborSolicitToRouter(router); + } + + nextTime = Min(nextTime, router.mTimeout); + } + + RemoveOrDeprecateEntriesFromInactiveRouters(); + + if (nextTime != now.GetDistantFuture()) + { + mRouterTimer.FireAtIfEarlier(nextTime); + } +} + +void RoutingManager::DiscoveredPrefixTable::SendNeighborSolicitToRouter(const Router &aRouter) +{ + InfraIf::Icmp6Packet packet; + Ip6::Nd::NeighborSolicitMessage neighborSolicitMsg; + + VerifyOrExit(!Get().mRsSender.IsInProgress()); + + neighborSolicitMsg.SetTargetAddress(aRouter.mAddress); + packet.InitFrom(neighborSolicitMsg); + + IgnoreError(Get().mInfraIf.Send(packet, aRouter.mAddress)); + + LogInfo("Sent Neighbor Solicitation to %s - attempt:%d/%d", aRouter.mAddress.ToString().AsCString(), + aRouter.mNsProbeCount, Router::kMaxNsProbes); + +exit: + return; +} + void RoutingManager::DiscoveredPrefixTable::InitIterator(PrefixTableIterator &aIterator) const { Iterator &iterator = static_cast(aIterator); diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 2ec5ebb04..b1a455b8c 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -421,7 +421,8 @@ private: }; void HandleDiscoveredPrefixTableChanged(void); // Declare early so we can use in `mSignalTask` - void HandleDiscoveredPrefixTableTimer(void) { mDiscoveredPrefixTable.HandleTimer(); } + void HandleDiscoveredPrefixTableEntryTimer(void) { mDiscoveredPrefixTable.HandleEntryTimer(); } + void HandleDiscoveredPrefixTableRouterTimer(void) { mDiscoveredPrefixTable.HandleRouterTimer(); } class DiscoveredPrefixTable : public InstanceLocator { @@ -448,6 +449,8 @@ private: void ProcessRouterAdvertMessage(const Ip6::Nd::RouterAdvertMessage &aRaMessage, const Ip6::Address & aSrcAddress); + void ProcessNeighborAdvertMessage(const Ip6::Nd::NeighborAdvertMessage &aNaMessage, + const Ip6::Address & aSrcAddress); void SetAllowDefaultRouteInNetData(bool aAllow); @@ -468,7 +471,8 @@ private: void InitIterator(PrefixTableIterator &aIterator) const; Error GetNextEntry(PrefixTableIterator &aIterator, PrefixTableEntry &aEntry) const; - void HandleTimer(void); + void HandleEntryTimer(void); + void HandleRouterTimer(void); private: static constexpr uint16_t kMaxRouters = OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_DISCOVERED_ROUTERS; @@ -550,6 +554,17 @@ private: struct Router { + // The timeout (in msec) for router staying in active state + // before starting the Neighbor Solicitation (NS) probes. + static constexpr uint32_t kActiveTimout = OPENTHREAD_CONFIG_BORDER_ROUTING_ROUTER_ACTIVE_CHECK_TIMEOUT; + + static constexpr uint8_t kMaxNsProbes = 5; // Max number of NS probe attempts. + static constexpr uint32_t kNsProbeRetryInterval = 1000; // In msec. Time between NS probe attempts. + static constexpr uint32_t kNsProbeTimout = 2000; // In msec. Max Wait time after last NS probe. + static constexpr uint32_t kJitter = 2000; // In msec. Jitter to randomize probe starts. + + static_assert(kMaxNsProbes < 255, "kMaxNsProbes MUST not be 255"); + enum EmptyChecker : uint8_t { kContainsNoEntries @@ -560,6 +575,8 @@ private: Ip6::Address mAddress; LinkedList mEntries; + TimeMilli mTimeout; + uint8_t mNsProbeCount; }; class Iterator : public PrefixTableIterator @@ -578,6 +595,7 @@ private: void ProcessRouteInfoOption(const Ip6::Nd::RouteInfoOption &aRio, Router &aRouter); bool ContainsPrefix(const Entry::Matcher &aMatcher) const; void RemovePrefix(const Entry::Matcher &aMatcher); + void RemoveOrDeprecateEntriesFromInactiveRouters(void); void RemoveRoutersWithNoEntries(void); Entry * AllocateEntry(void) { return mEntryPool.Allocate(); } void FreeEntry(Entry &aEntry) { mEntryPool.Free(aEntry); } @@ -586,13 +604,17 @@ private: const Entry *FindFavoredEntryToPublish(const Ip6::Prefix &aPrefix) const; void RemoveExpiredEntries(void); void SignalTableChanged(void); + void UpdateRouterOnRx(Router &aRouter); + void SendNeighborSolicitToRouter(const Router &aRouter); - using SignalTask = TaskletIn; - using TableTimer = TimerMilliIn; + using SignalTask = TaskletIn; + using EntryTimer = TimerMilliIn; + using RouterTimer = TimerMilliIn; Array mRouters; Pool mEntryPool; - TableTimer mTimer; + EntryTimer mEntryTimer; + RouterTimer mRouterTimer; SignalTask mSignalTask; bool mAllowDefaultRouteInNetData; }; @@ -827,8 +849,9 @@ private: void HandleDiscoveredPrefixStaleTimer(void); - void HandleRouterSolicit(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress); void HandleRouterAdvertisement(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress); + void HandleRouterSolicit(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress); + void HandleNeighborAdvertisement(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress); bool ShouldProcessPrefixInfoOption(const Ip6::Nd::PrefixInfoOption &aPio, const Ip6::Prefix &aPrefix); bool ShouldProcessRouteInfoOption(const Ip6::Nd::RouteInfoOption &aRio, const Ip6::Prefix &aPrefix); void UpdateDiscoveredPrefixTableOnNetDataChange(void); diff --git a/src/core/config/border_routing.h b/src/core/config/border_routing.h index bc3131b47..1327901ea 100644 --- a/src/core/config/border_routing.h +++ b/src/core/config/border_routing.h @@ -85,4 +85,23 @@ #ifndef OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_OLD_ON_LINK_PREFIXES #define OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_OLD_ON_LINK_PREFIXES 3 #endif + +/** + * @def OPENTHREAD_CONFIG_BORDER_ROUTING_ROUTER_ACTIVE_CHECK_TIMEOUT + * + * Specifies the timeout in msec for a discovered router on infra link side. + * + * This parameter is related to mechanism to check that a discovered router is still active. + * + * After this timeout elapses since the last received message (a Router or Neighbor Advertisement) from the router, + * routing manager will start sending Neighbor Solidification (NS) probes to the router to check that it is still + * active. + * + * This parameter can be considered to large value to practically disable this behavior. + * + */ +#ifndef OPENTHREAD_CONFIG_BORDER_ROUTING_ROUTER_ACTIVE_CHECK_TIMEOUT +#define OPENTHREAD_CONFIG_BORDER_ROUTING_ROUTER_ACTIVE_CHECK_TIMEOUT (60 * 1000) // (in msec). +#endif + #endif // CONFIG_BORDER_ROUTING_H_ diff --git a/src/core/net/icmp6.hpp b/src/core/net/icmp6.hpp index 594da57b1..2d073c538 100644 --- a/src/core/net/icmp6.hpp +++ b/src/core/net/icmp6.hpp @@ -92,6 +92,8 @@ public: kTypeEchoReply = OT_ICMP6_TYPE_ECHO_REPLY, ///< Echo Reply kTypeRouterSolicit = OT_ICMP6_TYPE_ROUTER_SOLICIT, ///< Router Solicitation kTypeRouterAdvert = OT_ICMP6_TYPE_ROUTER_ADVERT, ///< Router Advertisement + kTypeNeighborSolicit = OT_ICMP6_TYPE_NEIGHBOR_SOLICIT, ///< Neighbor Solicitation + kTypeNeighborAdvert = OT_ICMP6_TYPE_NEIGHBOR_ADVERT, ///< Neighbor Advertisement }; /** diff --git a/src/core/net/nd6.cpp b/src/core/net/nd6.cpp index 5b57e1be9..78c7d8353 100644 --- a/src/core/net/nd6.cpp +++ b/src/core/net/nd6.cpp @@ -269,7 +269,7 @@ exit: } //---------------------------------------------------------------------------------------------------------------------- -// RouterAdvMessage +// RouterSolicitMessage RouterSolicitMessage::RouterSolicitMessage(void) { @@ -277,6 +277,30 @@ RouterSolicitMessage::RouterSolicitMessage(void) mHeader.SetType(Icmp::Header::kTypeRouterSolicit); } +//---------------------------------------------------------------------------------------------------------------------- +// NeighborSolicitMessage + +NeighborSolicitMessage::NeighborSolicitMessage(void) +{ + OT_UNUSED_VARIABLE(mChecksum); + OT_UNUSED_VARIABLE(mReserved); + + Clear(); + mType = Icmp::Header::kTypeNeighborSolicit; +} + +//---------------------------------------------------------------------------------------------------------------------- +// NeighborAdvertMessage + +NeighborAdvertMessage::NeighborAdvertMessage(void) +{ + OT_UNUSED_VARIABLE(mChecksum); + OT_UNUSED_VARIABLE(mReserved); + + Clear(); + mType = Icmp::Header::kTypeNeighborAdvert; +} + } // namespace Nd } // namespace Ip6 } // namespace ot diff --git a/src/core/net/nd6.hpp b/src/core/net/nd6.hpp index 21b29c2a8..275f5b6b2 100644 --- a/src/core/net/nd6.hpp +++ b/src/core/net/nd6.hpp @@ -698,6 +698,190 @@ private: static_assert(sizeof(RouterSolicitMessage) == 8, "invalid RouterSolicitMessage structure"); +/** + * This class represents a Neighbor Solicitation (NS) message. + * + */ +OT_TOOL_PACKED_BEGIN +class NeighborSolicitMessage : public Clearable +{ +public: + /** + * This constructor initializes the Neighbor Solicitation message. + * + */ + NeighborSolicitMessage(void); + + /** + * This method indicates whether the Neighbor Solicitation message is valid (proper Type and Code). + * + * @retval TRUE If the message is valid. + * @retval FALSE If the message is not valid. + * + */ + bool IsValid(void) const { return (mType == Icmp::Header::kTypeNeighborSolicit) && (mCode == 0); } + + /** + * This method gets the Target Address field. + * + * @returns The Target Address. + * + */ + const Address &GetTargetAddress(void) const { return mTargetAddress; } + + /** + * This method sets the Target Address field. + * + * @param[in] aTargetAddress The Target Address. + * + */ + void SetTargetAddress(const Address &aTargetAddress) { mTargetAddress = aTargetAddress; } + +private: + // Neighbor Solicitation Message (RFC 4861) + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Type | Code | Checksum | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Reserved | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // + + + // | | + // + Target Address + + // | | + // + + + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Options ... + // +-+-+-+-+-+-+-+-+-+-+-+- + + uint8_t mType; + uint8_t mCode; + uint16_t mChecksum; + uint32_t mReserved; + Address mTargetAddress; +} OT_TOOL_PACKED_END; + +static_assert(sizeof(NeighborSolicitMessage) == 24, "Invalid NeighborSolicitMessage definition"); + +/** + * This class represents a Neighbor Advertisement (NA) message. + * + */ +OT_TOOL_PACKED_BEGIN +class NeighborAdvertMessage : public Clearable +{ +public: + NeighborAdvertMessage(void); + + /** + * This method indicates whether the Neighbor Advertisement message is valid (proper Type and Code). + * + * @retval TRUE If the message is valid. + * @retval FALSE If the message is not valid. + * + */ + bool IsValid(void) const { return (mType == Icmp::Header::kTypeNeighborAdvert) && (mCode == 0); } + + /** + * This method indicates whether or not the Router Flag is set in the NA message. + * + * @retval TRUE The Router Flag is set. + * @retval FALSE The Router Flag is not set. + * + */ + bool IsRouterFlagSet(void) const { return (mFlags & kRouterFlag) != 0; } + + /** + * This method sets the Router Flag in the NA message. + * + */ + void SetRouterFlag(void) { mFlags |= kRouterFlag; } + + /** + * This method indicates whether or not the Solicited Flag is set in the NA message. + * + * @retval TRUE The Solicited Flag is set. + * @retval FALSE The Solicited Flag is not set. + * + */ + bool IsSolicitedFlagSet(void) const { return (mFlags & kSolicitedFlag) != 0; } + + /** + * This method sets the Solicited Flag in the NA message. + * + */ + void SetSolicitedFlag(void) { mFlags |= kSolicitedFlag; } + + /** + * This method indicates whether or not the Override Flag is set in the NA message. + * + * @retval TRUE The Override Flag is set. + * @retval FALSE The Override Flag is not set. + * + */ + bool IsOverrideFlagSet(void) const { return (mFlags & kOverrideFlag) != 0; } + + /** + * This method sets the Override Flag in the NA message. + * + */ + void SetOverrideFlag(void) { mFlags |= kOverrideFlag; } + + /** + * This method gets the Target Address field. + * + * @returns The Target Address. + * + */ + const Address &GetTargetAddress(void) const { return mTargetAddress; } + + /** + * This method sets the Target Address field. + * + * @param[in] aTargetAddress The Target Address. + * + */ + void SetTargetAddress(const Address &aTargetAddress) { mTargetAddress = aTargetAddress; } + +private: + // Neighbor Advertisement Message (RFC 4861) + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Type | Code | Checksum | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |R|S|O| Reserved | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // + + + // | | + // + Target Address + + // | | + // + + + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Options ... + // +-+-+-+-+-+-+-+-+-+-+-+- + + static constexpr uint8_t kRouterFlag = (1 << 7); + static constexpr uint8_t kSolicitedFlag = (1 << 6); + static constexpr uint8_t kOverrideFlag = (1 << 5); + + uint8_t mType; + uint8_t mCode; + uint16_t mChecksum; + uint8_t mFlags; + uint8_t mReserved[3]; + Address mTargetAddress; +} OT_TOOL_PACKED_END; + +static_assert(sizeof(NeighborAdvertMessage) == 24, "Invalid NeighborAdvertMessage definition"); + } // namespace Nd } // namespace Ip6 } // namespace ot diff --git a/tests/unit/test_routing_manager.cpp b/tests/unit/test_routing_manager.cpp index c9db297db..838802a9b 100644 --- a/tests/unit/test_routing_manager.cpp +++ b/tests/unit/test_routing_manager.cpp @@ -98,6 +98,8 @@ 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 sNsEmitted; // Indicates if an NS message was emitted by BR. +bool sRespondToNs; // Indicates whether or not to respond to NS. ExpectedPio sExpectedPio; // Expected PIO in the emitted RA by BR (MUST be seen in RA to set `sRaValidated`). uint32_t sOnLinkLifetime; // Valid lifetime for local on-link prefix from the last processed RA. @@ -155,6 +157,7 @@ void LogRouterAdvert(const Icmp6Packet &aPacket); void ValidateRouterAdvert(const Icmp6Packet &aPacket); const char *PreferenceToString(int8_t aPreference); void SendRouterAdvert(const Ip6::Address &aAddress, const Icmp6Packet &aPacket); +void SendNeighborAdvert(const Ip6::Address &aAddress, const Ip6::Nd::NeighborAdvertMessage &aNaMessage); #if OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...) @@ -247,6 +250,30 @@ otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex, ValidateRouterAdvert(packet); break; + case Ip6::Icmp::Header::kTypeNeighborSolicit: + { + const Ip6::Nd::NeighborSolicitMessage *nsMsg = + reinterpret_cast(packet.GetBytes()); + + Log(" Neighbor Solicit message"); + + VerifyOrQuit(packet.GetLength() >= sizeof(Ip6::Nd::NeighborSolicitMessage)); + VerifyOrQuit(nsMsg->IsValid()); + sNsEmitted = true; + + if (sRespondToNs) + { + Ip6::Nd::NeighborAdvertMessage naMsg; + + naMsg.SetTargetAddress(nsMsg->GetTargetAddress()); + naMsg.SetRouterFlag(); + naMsg.SetSolicitedFlag(); + SendNeighborAdvert(AsCoreType(aDestAddress), naMsg); + } + + break; + } + default: VerifyOrQuit(false, "Bad ICMP6 type"); } @@ -467,6 +494,13 @@ void SendRouterAdvert(const Ip6::Address &aAddress, const Icmp6Packet &aPacket) otPlatInfraIfRecvIcmp6Nd(sInstance, kInfraIfIndex, &aAddress, aPacket.GetBytes(), aPacket.GetLength()); } +void SendNeighborAdvert(const Ip6::Address &aAddress, const Ip6::Nd::NeighborAdvertMessage &aNaMessage) +{ + Log("Sending NA from %s", aAddress.ToString().AsCString()); + otPlatInfraIfRecvIcmp6Nd(sInstance, kInfraIfIndex, &aAddress, reinterpret_cast(&aNaMessage), + sizeof(aNaMessage)); +} + Ip6::Prefix PrefixFromString(const char *aString, uint8_t aPrefixLength) { Ip6::Prefix prefix; @@ -705,6 +739,19 @@ struct RoutePrefix : public Rio template void VerifyPrefixTable(const OnLinkPrefix (&aOnLinkPrefixes)[kNumOnLinkPrefixes], const RoutePrefix (&aRoutePrefixes)[kNumRoutePrefixes]) +{ + VerifyPrefixTable(aOnLinkPrefixes, kNumOnLinkPrefixes, aRoutePrefixes, kNumRoutePrefixes); +} + +template void VerifyPrefixTable(const OnLinkPrefix (&aOnLinkPrefixes)[kNumOnLinkPrefixes]) +{ + VerifyPrefixTable(aOnLinkPrefixes, kNumOnLinkPrefixes, nullptr, 0); +} + +void VerifyPrefixTable(const OnLinkPrefix *aOnLinkPrefixes, + uint16_t aNumOnLinkPrefixes, + const RoutePrefix * aRoutePrefixes, + uint16_t aNumRoutePrefixes) { BorderRouter::RoutingManager::PrefixTableIterator iter; BorderRouter::RoutingManager::PrefixTableEntry entry; @@ -721,14 +768,16 @@ void VerifyPrefixTable(const OnLinkPrefix (&aOnLinkPrefixes)[kNumOnLinkPrefixes] if (entry.mIsOnLink) { - Log(" on-link prefix:%s, valid:%u, preferred:%u, router:%s", + Log(" on-link prefix:%s, valid:%u, preferred:%u, router:%s, age:%u", AsCoreType(&entry.mPrefix).ToString().AsCString(), entry.mValidLifetime, entry.mPreferredLifetime, - AsCoreType(&entry.mRouterAddress).ToString().AsCString()); + AsCoreType(&entry.mRouterAddress).ToString().AsCString(), entry.mMsecSinceLastUpdate / 1000); onLinkPrefixCount++; - for (const OnLinkPrefix &onLinkPrefix : aOnLinkPrefixes) + for (uint16_t index = 0; index < aNumOnLinkPrefixes; index++) { + const OnLinkPrefix &onLinkPrefix = aOnLinkPrefixes[index]; + if ((onLinkPrefix.mPrefix == AsCoreType(&entry.mPrefix)) && (AsCoreType(&entry.mRouterAddress) == onLinkPrefix.mRouterAddress)) { @@ -741,14 +790,17 @@ void VerifyPrefixTable(const OnLinkPrefix (&aOnLinkPrefixes)[kNumOnLinkPrefixes] } else { - Log(" route prefix:%s, valid:%u, prf:%s, router:%s", AsCoreType(&entry.mPrefix).ToString().AsCString(), - entry.mValidLifetime, PreferenceToString(entry.mRoutePreference), - AsCoreType(&entry.mRouterAddress).ToString().AsCString()); + Log(" route prefix:%s, valid:%u, prf:%s, router:%s, age:%u", + AsCoreType(&entry.mPrefix).ToString().AsCString(), entry.mValidLifetime, + PreferenceToString(entry.mRoutePreference), AsCoreType(&entry.mRouterAddress).ToString().AsCString(), + entry.mMsecSinceLastUpdate / 1000); routePrefixCount++; - for (const RoutePrefix &routePrefix : aRoutePrefixes) + for (uint16_t index = 0; index < aNumRoutePrefixes; index++) { + const RoutePrefix &routePrefix = aRoutePrefixes[index]; + if ((routePrefix.mPrefix == AsCoreType(&entry.mPrefix)) && (AsCoreType(&entry.mRouterAddress) == routePrefix.mRouterAddress)) { @@ -763,8 +815,8 @@ void VerifyPrefixTable(const OnLinkPrefix (&aOnLinkPrefixes)[kNumOnLinkPrefixes] VerifyOrQuit(didFind); } - VerifyOrQuit(onLinkPrefixCount == kNumOnLinkPrefixes); - VerifyOrQuit(routePrefixCount == kNumRoutePrefixes); + VerifyOrQuit(onLinkPrefixCount == aNumOnLinkPrefixes); + VerifyOrQuit(routePrefixCount == aNumRoutePrefixes); } void InitTest(void) @@ -801,6 +853,7 @@ void InitTest(void) sRaValidated = false; sExpectedPio = kNoPio; sExpectedRios.Clear(); + sRespondToNs = true; } //--------------------------------------------------------------------------------------------------------------------- @@ -1453,7 +1506,7 @@ void TestDomainPrefixAsOmr(void) AdvanceTime(100); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Make sure BR emits RA without domain prfix or previous local OMR. + // Make sure BR emits RA without domain prefix or previous local OMR. sRaValidated = false; sExpectedPio = kPioAdvertisingLocalOnLink; @@ -1465,7 +1518,7 @@ void TestDomainPrefixAsOmr(void) VerifyOrQuit(sRaValidated); - // We should see RIO removing the local OMR prefix with lifetiem zero + // We should see RIO removing the local OMR prefix with lifetime zero // and should not see the domain prefix as RIO. VerifyOrQuit(sExpectedRios[0].mPrefix == domainPrefix); @@ -2051,7 +2104,154 @@ void TestExtPanIdChange(void) //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("End of TestExtPanIdChange"); + testFreeInstance(sInstance); +} +void TestRouterNsProbe(void) +{ + Ip6::Prefix localOnLink; + Ip6::Prefix localOmr; + Ip6::Prefix onLinkPrefix = PrefixFromString("2000:abba:baba::", 64); + Ip6::Prefix routePrefix = PrefixFromString("2000:1234:5678::", 64); + Ip6::Address routerAddressA = AddressFromString("fd00::aaaa"); + Ip6::Address routerAddressB = AddressFromString("fd00::bbbb"); + + Log("--------------------------------------------------------------------------------------------"); + Log("TestRouterNsProbe"); + + InitTest(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start Routing Manager. Check emitted RS and RA messages. + + sRsEmitted = false; + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; + sExpectedRios.Clear(); + + SuccessOrQuit(sInstance->Get().SetEnabled(true)); + + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(localOnLink)); + SuccessOrQuit(sInstance->Get().GetOmrPrefix(localOmr)); + + Log("Local on-link prefix is %s", localOnLink.ToString().AsCString()); + Log("Local OMR prefix is %s", localOmr.ToString().AsCString()); + + sExpectedRios.Add(localOmr); + + AdvanceTime(30000); + + VerifyOrQuit(sRsEmitted); + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sExpectedRios.SawAll()); + Log("Received RA was validated"); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router A with a new on-link (PIO) and route prefix (RIO). + + SendRouterAdvert(routerAddressA, {Pio(onLinkPrefix, kValidLitime, kPreferredLifetime)}, + {Rio(routePrefix, kValidLitime, NetworkData::kRoutePreferenceMedium)}); + + sExpectedPio = kPioDeprecatingLocalOnLink; + + AdvanceTime(10); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Check the discovered prefix table and ensure info from router A + // is present in the table. + + VerifyPrefixTable({OnLinkPrefix(onLinkPrefix, kValidLitime, kPreferredLifetime, routerAddressA)}, + {RoutePrefix(routePrefix, kValidLitime, NetworkData::kRoutePreferenceMedium, routerAddressA)}); + + AdvanceTime(30000); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router B with same route prefix (RIO) but with + // high route preference. + + SendRouterAdvert(routerAddressB, {Rio(routePrefix, kValidLitime, NetworkData::kRoutePreferenceHigh)}); + + AdvanceTime(200); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Check the discovered prefix table and ensure entries from + // both router A and B are seen. + + VerifyPrefixTable({OnLinkPrefix(onLinkPrefix, kValidLitime, kPreferredLifetime, routerAddressA)}, + {RoutePrefix(routePrefix, kValidLitime, NetworkData::kRoutePreferenceMedium, routerAddressA), + RoutePrefix(routePrefix, kValidLitime, NetworkData::kRoutePreferenceHigh, routerAddressB)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Check that BR emitted an NS to ensure routers are active. + + sNsEmitted = false; + sRsEmitted = false; + + AdvanceTime(160 * 1000); + + VerifyOrQuit(sNsEmitted); + VerifyOrQuit(!sRsEmitted); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Disallow responding to NS message. + // + // This should trigger `RoutingManager` to send RS (which will get + // no response as well) and then remove all router entries. + + sRespondToNs = false; + + sExpectedPio = kPioAdvertisingLocalOnLink; + sRaValidated = false; + sNsEmitted = false; + + AdvanceTime(240 * 1000); + + VerifyOrQuit(sNsEmitted); + VerifyOrQuit(sRaValidated); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Check the discovered prefix table. We should see the on-link entry from + // router A as deprecated and no route prefix. + + VerifyPrefixTable({OnLinkPrefix(onLinkPrefix, kValidLitime, 0, routerAddressA)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Verify that no more NS is being sent (since there is no more valid + // router entry in the table). + + sExpectedPio = kPioAdvertisingLocalOnLink; + sRaValidated = false; + sNsEmitted = false; + + AdvanceTime(6 * 60 * 1000); + + VerifyOrQuit(!sNsEmitted); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router B and verify that we see router B + // entry in prefix table. + + SendRouterAdvert(routerAddressB, {Rio(routePrefix, kValidLitime, NetworkData::kRoutePreferenceHigh)}); + + VerifyPrefixTable({OnLinkPrefix(onLinkPrefix, kValidLitime, 0, routerAddressA)}, + {RoutePrefix(routePrefix, kValidLitime, NetworkData::kRoutePreferenceHigh, routerAddressB)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Wait for longer than router active time before NS probe. + // Check again that NS are sent again. + + sRespondToNs = true; + sNsEmitted = false; + sRsEmitted = false; + + AdvanceTime(3 * 60 * 1000); + + VerifyOrQuit(sNsEmitted); + VerifyOrQuit(!sRsEmitted); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + Log("End of TestRouterNsProbe"); testFreeInstance(sInstance); } @@ -2502,6 +2702,7 @@ int main(void) #endif TestExtPanIdChange(); TestConflictingPrefix(); + TestRouterNsProbe(); #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE TestAutoEnableOfSrpServer(); #endif