[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:
Abtin Keshavarzian
2024-03-26 11:13:41 -07:00
committed by GitHub
parent 4db6520d17
commit 4545daf010
3 changed files with 188 additions and 78 deletions
+63 -70
View File
@@ -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
+23 -5
View File
@@ -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);
+102 -3
View File
@@ -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