mirror of
https://github.com/espressif/openthread.git
synced 2026-09-06 01:00:09 +00:00
[routing-manager] employ RA hash tracking to detect self-originating RAs (#9939)
This commit updates how `RoutingManager` differentiates between self-generated RA messages and those from other sources sending RA on the same device. This enables learning of the RA header, specifically the default route lifetime. This commit introduces a new mechanism to calculate and store a SHA256 hash of recently emitted RAs. Received RAs are cross-referenced against the stored hashes to determine their origin. This replaces the prior method, which relied on parsing and analyzing the included options in the received RA. A new test case is added in `test_routing_manager` to validate the learning of RA header from other sources on same device.
This commit is contained in:
@@ -609,11 +609,11 @@ void RoutingManager::SendRouterAdvertisement(RouterAdvTxMode aRaTxMode)
|
|||||||
|
|
||||||
VerifyOrExit(raMsg.ContainsAnyOptions());
|
VerifyOrExit(raMsg.ContainsAnyOptions());
|
||||||
|
|
||||||
++mRaInfo.mTxCount;
|
|
||||||
|
|
||||||
destAddress.SetToLinkLocalAllNodesMulticast();
|
destAddress.SetToLinkLocalAllNodesMulticast();
|
||||||
raMsg.GetAsPacket(packet);
|
raMsg.GetAsPacket(packet);
|
||||||
|
|
||||||
|
mRaInfo.IncrementTxCountAndSaveHash(packet);
|
||||||
|
|
||||||
SuccessOrExit(error = mInfraIf.Send(packet, destAddress));
|
SuccessOrExit(error = mInfraIf.Send(packet, destAddress));
|
||||||
|
|
||||||
mRaInfo.mLastTxTime = TimerMilli::GetNow();
|
mRaInfo.mLastTxTime = TimerMilli::GetNow();
|
||||||
@@ -629,73 +629,6 @@ exit:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RoutingManager::IsReceivedRouterAdvertFromManager(const RouterAdvert::RxMessage &aRaMessage) const
|
|
||||||
{
|
|
||||||
// Determines whether or not a received RA message was prepared by
|
|
||||||
// by `RoutingManager` itself.
|
|
||||||
|
|
||||||
bool isFromManager = false;
|
|
||||||
uint16_t rioCount = 0;
|
|
||||||
Ip6::Prefix prefix;
|
|
||||||
|
|
||||||
VerifyOrExit(aRaMessage.ContainsAnyOptions());
|
|
||||||
|
|
||||||
for (const Option &option : aRaMessage)
|
|
||||||
{
|
|
||||||
switch (option.GetType())
|
|
||||||
{
|
|
||||||
case Option::kTypePrefixInfo:
|
|
||||||
{
|
|
||||||
const PrefixInfoOption &pio = static_cast<const PrefixInfoOption &>(option);
|
|
||||||
|
|
||||||
VerifyOrExit(pio.IsValid());
|
|
||||||
pio.GetPrefix(prefix);
|
|
||||||
|
|
||||||
// If it is a non-deprecated PIO, it should match the
|
|
||||||
// local on-link prefix.
|
|
||||||
|
|
||||||
if (pio.GetPreferredLifetime() > 0)
|
|
||||||
{
|
|
||||||
VerifyOrExit(prefix == mOnLinkPrefixManager.GetLocalPrefix());
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Option::kTypeRouteInfo:
|
|
||||||
{
|
|
||||||
// RIO (with non-zero lifetime) should match entries from
|
|
||||||
// `mRioAdvertiser`. We keep track of the number of matched
|
|
||||||
// RIOs and check after the loop ends that all entries were
|
|
||||||
// seen.
|
|
||||||
|
|
||||||
const RouteInfoOption &rio = static_cast<const RouteInfoOption &>(option);
|
|
||||||
|
|
||||||
VerifyOrExit(rio.IsValid());
|
|
||||||
rio.GetPrefix(prefix);
|
|
||||||
|
|
||||||
if (rio.GetRouteLifetime() != 0)
|
|
||||||
{
|
|
||||||
VerifyOrExit(mRioAdvertiser.HasAdvertised(prefix));
|
|
||||||
rioCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
ExitNow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VerifyOrExit(rioCount == mRioAdvertiser.GetAdvertisedRioCount());
|
|
||||||
|
|
||||||
isFromManager = true;
|
|
||||||
|
|
||||||
exit:
|
|
||||||
return isFromManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RoutingManager::IsValidBrUlaPrefix(const Ip6::Prefix &aBrUlaPrefix)
|
bool RoutingManager::IsValidBrUlaPrefix(const Ip6::Prefix &aBrUlaPrefix)
|
||||||
{
|
{
|
||||||
return aBrUlaPrefix.mLength == kBrUlaPrefixLength && aBrUlaPrefix.mPrefix.mFields.m8[0] == 0xfd;
|
return aBrUlaPrefix.mLength == kBrUlaPrefixLength && aBrUlaPrefix.mPrefix.mFields.m8[0] == 0xfd;
|
||||||
@@ -941,7 +874,7 @@ void RoutingManager::UpdateRouterAdvertHeader(const RouterAdvert::RxMessage *aRo
|
|||||||
// We skip and do not update RA header if the received RA message
|
// We skip and do not update RA header if the received RA message
|
||||||
// was not prepared and sent by `RoutingManager` itself.
|
// was not prepared and sent by `RoutingManager` itself.
|
||||||
|
|
||||||
VerifyOrExit(!IsReceivedRouterAdvertFromManager(*aRouterAdvertMessage));
|
VerifyOrExit(!mRaInfo.IsRaFromManager(*aRouterAdvertMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
oldHeader = mRaInfo.mHeader;
|
oldHeader = mRaInfo.mHeader;
|
||||||
@@ -3451,6 +3384,66 @@ exit:
|
|||||||
|
|
||||||
#endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
|
#endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------------------------------------------------
|
||||||
|
// RaInfo
|
||||||
|
|
||||||
|
void RoutingManager::RaInfo::IncrementTxCountAndSaveHash(const InfraIf::Icmp6Packet &aRaMessage)
|
||||||
|
{
|
||||||
|
mTxCount++;
|
||||||
|
mLastHashIndex++;
|
||||||
|
|
||||||
|
if (mLastHashIndex == kNumHashEntries)
|
||||||
|
{
|
||||||
|
mLastHashIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CalculateHash(aRaMessage, mHashes[mLastHashIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RoutingManager::RaInfo::IsRaFromManager(const Ip6::Nd::RouterAdvert::RxMessage &aRaMessage) const
|
||||||
|
{
|
||||||
|
// Determines whether or not a received RA message was prepared by
|
||||||
|
// by `RoutingManager` itself (is present in the saved `mHashes`).
|
||||||
|
|
||||||
|
bool isFromManager = false;
|
||||||
|
uint16_t hashIndex = mLastHashIndex;
|
||||||
|
uint32_t count = Min<uint32_t>(mTxCount, kNumHashEntries);
|
||||||
|
Hash hash;
|
||||||
|
|
||||||
|
CalculateHash(aRaMessage.GetAsPacket(), hash);
|
||||||
|
|
||||||
|
for (; count > 0; count--)
|
||||||
|
{
|
||||||
|
if (mHashes[hashIndex] == hash)
|
||||||
|
{
|
||||||
|
isFromManager = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to the previous index (ring buffer)
|
||||||
|
|
||||||
|
if (hashIndex == 0)
|
||||||
|
{
|
||||||
|
hashIndex = kNumHashEntries - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hashIndex--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isFromManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoutingManager::RaInfo::CalculateHash(const InfraIf::Icmp6Packet &aRaMessage, Hash &aHash)
|
||||||
|
{
|
||||||
|
Crypto::Sha256 sha256;
|
||||||
|
|
||||||
|
sha256.Start();
|
||||||
|
sha256.Update(aRaMessage.GetBytes(), aRaMessage.GetLength());
|
||||||
|
sha256.Finish(aHash);
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------------------------
|
//---------------------------------------------------------------------------------------------------------------------
|
||||||
// RsSender
|
// RsSender
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
#include "common/pool.hpp"
|
#include "common/pool.hpp"
|
||||||
#include "common/string.hpp"
|
#include "common/string.hpp"
|
||||||
#include "common/timer.hpp"
|
#include "common/timer.hpp"
|
||||||
|
#include "crypto/sha256.hpp"
|
||||||
#include "net/ip6.hpp"
|
#include "net/ip6.hpp"
|
||||||
#include "net/nat64_translator.hpp"
|
#include "net/nat64_translator.hpp"
|
||||||
#include "net/nd6.hpp"
|
#include "net/nd6.hpp"
|
||||||
@@ -1160,26 +1161,44 @@ private:
|
|||||||
|
|
||||||
struct RaInfo
|
struct RaInfo
|
||||||
{
|
{
|
||||||
// Tracks info about emitted RA messages: Number of RAs sent,
|
// Tracks info about emitted RA messages:
|
||||||
// last tx time, header to use and whether the header is
|
//
|
||||||
// discovered from receiving RAs from the host itself. This
|
// - Number of RAs sent
|
||||||
// ensures that if an entity on host is advertising certain
|
// - Last RA TX time
|
||||||
|
// - Hashes of last TX RAs (to tell if a received RA is from
|
||||||
|
// `RoutingManager` itself)
|
||||||
|
// - RA header to use, and
|
||||||
|
// - Whether the RA header is discovered from receiving RAs
|
||||||
|
// from the host itself.
|
||||||
|
//
|
||||||
|
// This ensures that if an entity on host is advertising certain
|
||||||
// info in its RA header (e.g., a default route), the RAs we
|
// info in its RA header (e.g., a default route), the RAs we
|
||||||
// emit from `RoutingManager` also include the same header.
|
// emit from `RoutingManager` also include the same header.
|
||||||
|
|
||||||
|
typedef Crypto::Sha256::Hash Hash;
|
||||||
|
|
||||||
|
static constexpr uint16_t kNumHashEntries = 5;
|
||||||
|
|
||||||
RaInfo(void)
|
RaInfo(void)
|
||||||
: mHeaderUpdateTime(TimerMilli::GetNow())
|
: mHeaderUpdateTime(TimerMilli::GetNow())
|
||||||
, mIsHeaderFromHost(false)
|
, mIsHeaderFromHost(false)
|
||||||
, mTxCount(0)
|
, mTxCount(0)
|
||||||
, mLastTxTime(TimerMilli::GetNow() - kMinDelayBetweenRtrAdvs)
|
, mLastTxTime(TimerMilli::GetNow() - kMinDelayBetweenRtrAdvs)
|
||||||
|
, mLastHashIndex(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IncrementTxCountAndSaveHash(const InfraIf::Icmp6Packet &aRaMessage);
|
||||||
|
bool IsRaFromManager(const Ip6::Nd::RouterAdvert::RxMessage &aRaMessage) const;
|
||||||
|
static void CalculateHash(const InfraIf::Icmp6Packet &aRaMessage, Hash &aHash);
|
||||||
|
|
||||||
RouterAdvert::Header mHeader;
|
RouterAdvert::Header mHeader;
|
||||||
TimeMilli mHeaderUpdateTime;
|
TimeMilli mHeaderUpdateTime;
|
||||||
bool mIsHeaderFromHost;
|
bool mIsHeaderFromHost;
|
||||||
uint32_t mTxCount;
|
uint32_t mTxCount;
|
||||||
TimeMilli mLastTxTime;
|
TimeMilli mLastTxTime;
|
||||||
|
Hash mHashes[kNumHashEntries];
|
||||||
|
uint16_t mLastHashIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
void HandleRsSenderTimer(void) { mRsSender.HandleTimer(); }
|
void HandleRsSenderTimer(void) { mRsSender.HandleTimer(); }
|
||||||
@@ -1296,7 +1315,6 @@ private:
|
|||||||
bool NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const;
|
bool NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const;
|
||||||
bool NetworkDataContainsUlaRoute(void) const;
|
bool NetworkDataContainsUlaRoute(void) const;
|
||||||
void UpdateRouterAdvertHeader(const RouterAdvert::RxMessage *aRouterAdvertMessage);
|
void UpdateRouterAdvertHeader(const RouterAdvert::RxMessage *aRouterAdvertMessage);
|
||||||
bool IsReceivedRouterAdvertFromManager(const RouterAdvert::RxMessage &aRaMessage) const;
|
|
||||||
void ResetDiscoveredPrefixStaleTimer(void);
|
void ResetDiscoveredPrefixStaleTimer(void);
|
||||||
|
|
||||||
static bool IsValidBrUlaPrefix(const Ip6::Prefix &aBrUlaPrefix);
|
static bool IsValidBrUlaPrefix(const Ip6::Prefix &aBrUlaPrefix);
|
||||||
|
|||||||
@@ -160,6 +160,12 @@ 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`).
|
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.
|
uint32_t sOnLinkLifetime; // Valid lifetime for local on-link prefix from the last processed RA.
|
||||||
|
|
||||||
|
// Indicate whether or not to check the emitted RA header (default route) lifetime
|
||||||
|
bool sCheckRaHeaderLifetime;
|
||||||
|
|
||||||
|
// Expected default route lifetime in emitted RA header by BR.
|
||||||
|
uint32_t sExpectedRaHeaderLifetime;
|
||||||
|
|
||||||
enum ExpectedRaHeaderFlags
|
enum ExpectedRaHeaderFlags
|
||||||
{
|
{
|
||||||
kRaHeaderFlagsSkipChecking, // Skip checking the RA header flags.
|
kRaHeaderFlagsSkipChecking, // Skip checking the RA header flags.
|
||||||
@@ -424,7 +430,10 @@ void ValidateRouterAdvert(const Icmp6Packet &aPacket)
|
|||||||
|
|
||||||
VerifyOrQuit(raMsg.IsValid());
|
VerifyOrQuit(raMsg.IsValid());
|
||||||
|
|
||||||
VerifyOrQuit(raMsg.GetHeader().GetRouterLifetime() == 0);
|
if (sCheckRaHeaderLifetime)
|
||||||
|
{
|
||||||
|
VerifyOrQuit(raMsg.GetHeader().GetRouterLifetime() == sExpectedRaHeaderLifetime);
|
||||||
|
}
|
||||||
|
|
||||||
switch (sExpectedRaHeaderFlags)
|
switch (sExpectedRaHeaderFlags)
|
||||||
{
|
{
|
||||||
@@ -1188,8 +1197,10 @@ void InitTest(bool aEnablBorderRouting = false, bool aAfterReset = false)
|
|||||||
sRaValidated = false;
|
sRaValidated = false;
|
||||||
sExpectedPio = kNoPio;
|
sExpectedPio = kNoPio;
|
||||||
sExpectedRios.Clear();
|
sExpectedRios.Clear();
|
||||||
sRespondToNs = true;
|
sRespondToNs = true;
|
||||||
sExpectedRaHeaderFlags = kRaHeaderFlagsNone;
|
sExpectedRaHeaderFlags = kRaHeaderFlagsNone;
|
||||||
|
sCheckRaHeaderLifetime = true;
|
||||||
|
sExpectedRaHeaderLifetime = 0;
|
||||||
|
|
||||||
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
// Ensure device starts as leader.
|
// Ensure device starts as leader.
|
||||||
@@ -2950,6 +2961,93 @@ void TestLearningAndCopyingOfFlags(void)
|
|||||||
FinalizeTest();
|
FinalizeTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestLearnRaHeader(void)
|
||||||
|
{
|
||||||
|
Ip6::Prefix localOnLink;
|
||||||
|
Ip6::Prefix localOmr;
|
||||||
|
Ip6::Prefix onLinkPrefix = PrefixFromString("2000:abba:baba::", 64);
|
||||||
|
uint16_t heapAllocations;
|
||||||
|
|
||||||
|
Log("--------------------------------------------------------------------------------------------");
|
||||||
|
Log("TestLearnRaHeader");
|
||||||
|
|
||||||
|
InitTest();
|
||||||
|
|
||||||
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
// Start Routing Manager. Check emitted RS and RA messages.
|
||||||
|
|
||||||
|
sRsEmitted = false;
|
||||||
|
sRaValidated = false;
|
||||||
|
sExpectedPio = kPioAdvertisingLocalOnLink;
|
||||||
|
sExpectedRios.Clear();
|
||||||
|
|
||||||
|
heapAllocations = sHeapAllocatedPtrs.GetLength();
|
||||||
|
SuccessOrQuit(sInstance->Get<BorderRouter::RoutingManager>().SetEnabled(true));
|
||||||
|
|
||||||
|
SuccessOrQuit(sInstance->Get<BorderRouter::RoutingManager>().GetOnLinkPrefix(localOnLink));
|
||||||
|
SuccessOrQuit(sInstance->Get<BorderRouter::RoutingManager>().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");
|
||||||
|
|
||||||
|
VerifyDiscoveredRoutersIsEmpty();
|
||||||
|
|
||||||
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
// Send an RA from the same address (another entity on the device)
|
||||||
|
// advertising a default route.
|
||||||
|
|
||||||
|
SendRouterAdvert(sInfraIfAddress, DefaultRoute(1000, NetworkData::kRoutePreferenceLow));
|
||||||
|
|
||||||
|
AdvanceTime(1);
|
||||||
|
VerifyDiscoveredRouters({InfraRouter(sInfraIfAddress, /* M */ false, /* O */ false, /* StubRouter */ false)});
|
||||||
|
|
||||||
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
// RoutingManager should learn the header from the
|
||||||
|
// received RA (from same address) and start advertising
|
||||||
|
// the same default route lifetime in the emitted RAs.
|
||||||
|
|
||||||
|
sRaValidated = false;
|
||||||
|
sCheckRaHeaderLifetime = true;
|
||||||
|
sExpectedRaHeaderLifetime = 1000;
|
||||||
|
|
||||||
|
AdvanceTime(30 * 1000);
|
||||||
|
VerifyOrQuit(sRaValidated);
|
||||||
|
|
||||||
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
// Wait for longer than entry lifetime (for it to expire) and
|
||||||
|
// make sure `RoutingManager` stops advertising default route.
|
||||||
|
|
||||||
|
sCheckRaHeaderLifetime = false;
|
||||||
|
|
||||||
|
AdvanceTime(1000 * 1000);
|
||||||
|
|
||||||
|
sRaValidated = false;
|
||||||
|
sCheckRaHeaderLifetime = true;
|
||||||
|
sExpectedRaHeaderLifetime = 0;
|
||||||
|
|
||||||
|
AdvanceTime(700 * 1000);
|
||||||
|
VerifyOrQuit(sRaValidated);
|
||||||
|
|
||||||
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
SuccessOrQuit(sInstance->Get<BorderRouter::RoutingManager>().SetEnabled(false));
|
||||||
|
VerifyDiscoveredRoutersIsEmpty();
|
||||||
|
|
||||||
|
VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength());
|
||||||
|
|
||||||
|
Log("End of TestLearnRaHeader");
|
||||||
|
FinalizeTest();
|
||||||
|
}
|
||||||
|
|
||||||
void TestConflictingPrefix(void)
|
void TestConflictingPrefix(void)
|
||||||
{
|
{
|
||||||
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x08}};
|
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x08}};
|
||||||
@@ -3921,6 +4019,7 @@ int main(void)
|
|||||||
ot::TestConflictingPrefix();
|
ot::TestConflictingPrefix();
|
||||||
ot::TestRouterNsProbe();
|
ot::TestRouterNsProbe();
|
||||||
ot::TestLearningAndCopyingOfFlags();
|
ot::TestLearningAndCopyingOfFlags();
|
||||||
|
ot::TestLearnRaHeader();
|
||||||
#if OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE
|
#if OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE
|
||||||
ot::TestSavedOnLinkPrefixes();
|
ot::TestSavedOnLinkPrefixes();
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user