mirror of
https://github.com/espressif/openthread.git
synced 2026-08-19 17:09:51 +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());
|
||||
|
||||
++mRaInfo.mTxCount;
|
||||
|
||||
destAddress.SetToLinkLocalAllNodesMulticast();
|
||||
raMsg.GetAsPacket(packet);
|
||||
|
||||
mRaInfo.IncrementTxCountAndSaveHash(packet);
|
||||
|
||||
SuccessOrExit(error = mInfraIf.Send(packet, destAddress));
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
// was not prepared and sent by `RoutingManager` itself.
|
||||
|
||||
VerifyOrExit(!IsReceivedRouterAdvertFromManager(*aRouterAdvertMessage));
|
||||
VerifyOrExit(!mRaInfo.IsRaFromManager(*aRouterAdvertMessage));
|
||||
}
|
||||
|
||||
oldHeader = mRaInfo.mHeader;
|
||||
@@ -3451,6 +3384,66 @@ exit:
|
||||
|
||||
#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
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
#include "common/pool.hpp"
|
||||
#include "common/string.hpp"
|
||||
#include "common/timer.hpp"
|
||||
#include "crypto/sha256.hpp"
|
||||
#include "net/ip6.hpp"
|
||||
#include "net/nat64_translator.hpp"
|
||||
#include "net/nd6.hpp"
|
||||
@@ -1160,26 +1161,44 @@ private:
|
||||
|
||||
struct RaInfo
|
||||
{
|
||||
// Tracks info about emitted RA messages: Number of RAs sent,
|
||||
// last tx time, header to use and whether the header is
|
||||
// discovered from receiving RAs from the host itself. This
|
||||
// ensures that if an entity on host is advertising certain
|
||||
// Tracks info about emitted RA messages:
|
||||
//
|
||||
// - Number of RAs sent
|
||||
// - 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
|
||||
// emit from `RoutingManager` also include the same header.
|
||||
|
||||
typedef Crypto::Sha256::Hash Hash;
|
||||
|
||||
static constexpr uint16_t kNumHashEntries = 5;
|
||||
|
||||
RaInfo(void)
|
||||
: mHeaderUpdateTime(TimerMilli::GetNow())
|
||||
, mIsHeaderFromHost(false)
|
||||
, mTxCount(0)
|
||||
, 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;
|
||||
TimeMilli mHeaderUpdateTime;
|
||||
bool mIsHeaderFromHost;
|
||||
uint32_t mTxCount;
|
||||
TimeMilli mLastTxTime;
|
||||
Hash mHashes[kNumHashEntries];
|
||||
uint16_t mLastHashIndex;
|
||||
};
|
||||
|
||||
void HandleRsSenderTimer(void) { mRsSender.HandleTimer(); }
|
||||
@@ -1296,7 +1315,6 @@ private:
|
||||
bool NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const;
|
||||
bool NetworkDataContainsUlaRoute(void) const;
|
||||
void UpdateRouterAdvertHeader(const RouterAdvert::RxMessage *aRouterAdvertMessage);
|
||||
bool IsReceivedRouterAdvertFromManager(const RouterAdvert::RxMessage &aRaMessage) const;
|
||||
void ResetDiscoveredPrefixStaleTimer(void);
|
||||
|
||||
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`).
|
||||
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
|
||||
{
|
||||
kRaHeaderFlagsSkipChecking, // Skip checking the RA header flags.
|
||||
@@ -424,7 +430,10 @@ void ValidateRouterAdvert(const Icmp6Packet &aPacket)
|
||||
|
||||
VerifyOrQuit(raMsg.IsValid());
|
||||
|
||||
VerifyOrQuit(raMsg.GetHeader().GetRouterLifetime() == 0);
|
||||
if (sCheckRaHeaderLifetime)
|
||||
{
|
||||
VerifyOrQuit(raMsg.GetHeader().GetRouterLifetime() == sExpectedRaHeaderLifetime);
|
||||
}
|
||||
|
||||
switch (sExpectedRaHeaderFlags)
|
||||
{
|
||||
@@ -1188,8 +1197,10 @@ void InitTest(bool aEnablBorderRouting = false, bool aAfterReset = false)
|
||||
sRaValidated = false;
|
||||
sExpectedPio = kNoPio;
|
||||
sExpectedRios.Clear();
|
||||
sRespondToNs = true;
|
||||
sExpectedRaHeaderFlags = kRaHeaderFlagsNone;
|
||||
sRespondToNs = true;
|
||||
sExpectedRaHeaderFlags = kRaHeaderFlagsNone;
|
||||
sCheckRaHeaderLifetime = true;
|
||||
sExpectedRaHeaderLifetime = 0;
|
||||
|
||||
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// Ensure device starts as leader.
|
||||
@@ -2950,6 +2961,93 @@ void TestLearningAndCopyingOfFlags(void)
|
||||
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)
|
||||
{
|
||||
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x08}};
|
||||
@@ -3921,6 +4019,7 @@ int main(void)
|
||||
ot::TestConflictingPrefix();
|
||||
ot::TestRouterNsProbe();
|
||||
ot::TestLearningAndCopyingOfFlags();
|
||||
ot::TestLearnRaHeader();
|
||||
#if OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE
|
||||
ot::TestSavedOnLinkPrefixes();
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user