[ip6] use OffsetRange for parsing options in extension headers (#10466)

This commit updates `Ip6` and `Lowpan` to use `OffsetRange` when
iterating and parsing options in an HBH extension header. It also
simplifies the `RemoveMplOption()` method by adding an `Action` enum,
which determines how to remove the MPL option: whether to shrink the
HBH header, fully remove the HBH header (if it contains no other
options), or replace MPL option with padding.
This commit is contained in:
Abtin Keshavarzian
2024-07-03 11:17:47 -07:00
committed by GitHub
parent 408f3f205f
commit 5248408076
7 changed files with 97 additions and 76 deletions
+73 -52
View File
@@ -309,30 +309,35 @@ exit:
Error Ip6::RemoveMplOption(Message &aMessage)
{
Error error = kErrorNone;
enum Action : uint8_t
{
kNoMplOption,
kShrinkHbh,
kRemoveHbh,
kReplaceMplWithPad,
};
Error error = kErrorNone;
Action action = kNoMplOption;
Header ip6Header;
HopByHopHeader hbh;
Option option;
uint16_t offset;
uint16_t endOffset;
uint16_t mplOffset = 0;
uint8_t mplLength = 0;
bool remove = false;
OffsetRange offsetRange;
OffsetRange mplOffsetRange;
PadOption padOption;
offsetRange.InitFromMessageFullLength(aMessage);
IgnoreError(aMessage.Read(offsetRange, ip6Header));
offsetRange.AdvanceOffset(sizeof(ip6Header));
offset = 0;
IgnoreError(aMessage.Read(offset, ip6Header));
offset += sizeof(ip6Header);
VerifyOrExit(ip6Header.GetNextHeader() == kProtoHopOpts);
IgnoreError(aMessage.Read(offset, hbh));
endOffset = offset + hbh.GetSize();
VerifyOrExit(aMessage.GetLength() >= endOffset, error = kErrorParse);
SuccessOrExit(error = ReadHopByHopHeader(aMessage, offsetRange, hbh));
offset += sizeof(hbh);
for (; offset < endOffset; offset += option.GetSize())
for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(option.GetSize()))
{
IgnoreError(option.ParseFrom(aMessage, offset, endOffset));
SuccessOrExit(error = option.ParseFrom(aMessage, offsetRange));
if (option.IsPadding())
{
@@ -342,44 +347,49 @@ Error Ip6::RemoveMplOption(Message &aMessage)
if (option.GetType() == MplOption::kType)
{
// If multiple MPL options exist, discard packet
VerifyOrExit(mplOffset == 0, error = kErrorParse);
VerifyOrExit(action == kNoMplOption, error = kErrorParse);
mplOffset = offset;
mplLength = option.GetLength();
// `Option::ParseFrom()` already validated that the entire
// option is present in the `offsetRange`.
VerifyOrExit(mplLength <= sizeof(MplOption) - sizeof(Option), error = kErrorParse);
mplOffsetRange = offsetRange;
mplOffsetRange.ShrinkLength(option.GetSize());
if (mplOffset == sizeof(ip6Header) + sizeof(hbh) && hbh.GetLength() == 0)
VerifyOrExit(option.GetSize() <= sizeof(MplOption), error = kErrorParse);
if (mplOffsetRange.GetOffset() == sizeof(ip6Header) + sizeof(hbh) && hbh.GetLength() == 0)
{
// First and only IPv6 Option, remove IPv6 HBH Option header
remove = true;
action = kRemoveHbh;
}
else if (mplOffset + ExtensionHeader::kLengthUnitSize == endOffset)
else if (mplOffsetRange.GetOffset() + ExtensionHeader::kLengthUnitSize == offsetRange.GetEndOffset())
{
// Last IPv6 Option, remove the last 8 bytes
remove = true;
// Last IPv6 Option, shrink the last 8 bytes
action = kShrinkHbh;
}
}
else
else if (action != kNoMplOption)
{
// Encountered another option, now just replace
// MPL Option with Pad Option
remove = false;
action = kReplaceMplWithPad;
}
}
// verify that IPv6 Options header is properly formed
VerifyOrExit(offset == endOffset, error = kErrorParse);
if (remove)
switch (action)
{
case kNoMplOption:
break;
case kShrinkHbh:
case kRemoveHbh:
// Last IPv6 Option, shrink HBH Option header by
// 8 bytes (`kLengthUnitSize`)
aMessage.RemoveHeader(endOffset - ExtensionHeader::kLengthUnitSize, ExtensionHeader::kLengthUnitSize);
aMessage.RemoveHeader(offsetRange.GetEndOffset() - ExtensionHeader::kLengthUnitSize,
ExtensionHeader::kLengthUnitSize);
if (mplOffset == sizeof(ip6Header) + sizeof(hbh))
if (action == kRemoveHbh)
{
// Remove entire HBH header
ip6Header.SetNextHeader(hbh.GetNextHeader());
}
else
@@ -393,14 +403,12 @@ Error Ip6::RemoveMplOption(Message &aMessage)
ip6Header.SetPayloadLength(ip6Header.GetPayloadLength() - ExtensionHeader::kLengthUnitSize);
aMessage.Write(0, ip6Header);
}
else if (mplOffset != 0)
{
// Replace MPL Option with Pad Option
PadOption padOption;
break;
padOption.InitForPadSize(sizeof(Option) + mplLength);
aMessage.WriteBytes(mplOffset, &padOption, padOption.GetSize());
case kReplaceMplWithPad:
padOption.InitForPadSize(static_cast<uint8_t>(mplOffsetRange.GetLength()));
aMessage.WriteBytes(mplOffsetRange.GetOffset(), &padOption, padOption.GetSize());
break;
}
exit:
@@ -501,24 +509,37 @@ void Ip6::HandleSendQueue(void)
}
}
Error Ip6::ReadHopByHopHeader(const Message &aMessage, OffsetRange &aOffsetRange, HopByHopHeader &aHbhHeader) const
{
// Reads the HBH header from the message at the given offset range.
// On success, updates `aOffsetRange` to indicate the location of
// options within the HBH header.
Error error;
SuccessOrExit(error = aMessage.Read(aOffsetRange, aHbhHeader));
VerifyOrExit(aOffsetRange.Contains(aHbhHeader.GetSize()), error = kErrorParse);
aOffsetRange.ShrinkLength(aHbhHeader.GetSize());
aOffsetRange.AdvanceOffset(sizeof(HopByHopHeader));
exit:
return error;
}
Error Ip6::HandleOptions(Message &aMessage, Header &aHeader, bool &aReceive)
{
Error error = kErrorNone;
HopByHopHeader hbhHeader;
Option option;
uint16_t offset = aMessage.GetOffset();
uint16_t endOffset;
OffsetRange offsetRange;
SuccessOrExit(error = aMessage.Read(offset, hbhHeader));
offsetRange.InitFromMessageOffsetToEnd(aMessage);
endOffset = offset + hbhHeader.GetSize();
VerifyOrExit(endOffset <= aMessage.GetLength(), error = kErrorParse);
SuccessOrExit(error = ReadHopByHopHeader(aMessage, offsetRange, hbhHeader));
offset += sizeof(HopByHopHeader);
for (; offset < endOffset; offset += option.GetSize())
for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(option.GetSize()))
{
SuccessOrExit(error = option.ParseFrom(aMessage, offset, endOffset));
SuccessOrExit(error = option.ParseFrom(aMessage, offsetRange));
if (option.IsPadding())
{
@@ -527,14 +548,14 @@ Error Ip6::HandleOptions(Message &aMessage, Header &aHeader, bool &aReceive)
if (option.GetType() == MplOption::kType)
{
SuccessOrExit(error = mMpl.ProcessOption(aMessage, offset, aHeader.GetSource(), aReceive));
SuccessOrExit(error = mMpl.ProcessOption(aMessage, offsetRange, aHeader.GetSource(), aReceive));
continue;
}
VerifyOrExit(option.GetAction() == Option::kActionSkip, error = kErrorDrop);
}
aMessage.SetOffset(offset);
aMessage.SetOffset(offsetRange.GetEndOffset());
exit:
return error;
+1
View File
@@ -402,6 +402,7 @@ private:
void UpdateReassemblyList(void);
void SendIcmpError(Message &aMessage, Icmp::Header::Type aIcmpType, Icmp::Header::Code aIcmpCode);
#endif
Error ReadHopByHopHeader(const Message &aMessage, OffsetRange &aOffsetRange, HopByHopHeader &aHbhHeader) const;
Error AddMplOption(Message &aMessage, Header &aHeader);
Error PrepareMulticastToLargerThanRealmLocal(Message &aMessage, const Header &aHeader);
Error InsertMplOption(Message &aMessage, Header &aHeader);
+4 -5
View File
@@ -69,14 +69,14 @@ bool Header::IsValid(void) const
//---------------------------------------------------------------------------------------------------------------------
// Option
Error Option::ParseFrom(const Message &aMessage, uint16_t aOffset, uint16_t aEndOffset)
Error Option::ParseFrom(const Message &aMessage, const OffsetRange &aOffsetRange)
{
Error error;
// Read the Type first to check for the Pad1 Option.
// If it is not, then we read the full `Option` header.
SuccessOrExit(error = aMessage.Read(aOffset, this, sizeof(mType)));
SuccessOrExit(error = aMessage.Read(aOffsetRange, this, sizeof(mType)));
if (mType == kTypePad1)
{
@@ -84,9 +84,8 @@ Error Option::ParseFrom(const Message &aMessage, uint16_t aOffset, uint16_t aEnd
ExitNow();
}
SuccessOrExit(error = aMessage.Read(aOffset, *this));
VerifyOrExit(aOffset + GetSize() <= aEndOffset, error = kErrorParse);
SuccessOrExit(error = aMessage.Read(aOffsetRange, *this));
VerifyOrExit(aOffsetRange.Contains(GetSize()), error = kErrorParse);
exit:
return error;
+5 -6
View File
@@ -517,17 +517,16 @@ public:
* Parses and validates the IPv6 Option from a given message.
*
* The Option is read from @p aOffset in @p aMessage. This method then checks that the entire Option is present
* in @p aMessage before the @p aEndOffset.
* within @p aOffsetRange.
*
* @param[in] aMessage The IPv6 message.
* @param[in] aOffset The offset in @p aMessage to read the IPv6 Option.
* @param[in] aEndOffset The end offset in @p aMessage.
* @param[in] aMessage The IPv6 message.
* @param[in] aOffsetRange The offset range in @p aMessage to read the IPv6 Option.
*
* @retval kErrorNone Successfully parsed the IPv6 option from @p aMessage.
* @retval kErrorParse Malformed IPv6 Option or Option is not contained within @p aMessage by @p aEndOffset.
* @retval kErrorParse Malformed IPv6 Option or Option is not contained within @p aMessage and @p aOffsetRange.
*
*/
Error ParseFrom(const Message &aMessage, uint16_t aOffset, uint16_t aEndOffset);
Error ParseFrom(const Message &aMessage, const OffsetRange &aOffsetRange);
protected:
static constexpr uint8_t kTypePad1 = 0x00; ///< Pad1 Option Type.
+3 -3
View File
@@ -90,14 +90,14 @@ void Mpl::InitOption(MplOption &aOption, const Address &aAddress)
aOption.SetSequence(mSequence++);
}
Error Mpl::ProcessOption(Message &aMessage, uint16_t aOffset, const Address &aAddress, bool &aReceive)
Error Mpl::ProcessOption(Message &aMessage, const OffsetRange &aOffsetRange, const Address &aAddress, bool &aReceive)
{
Error error;
MplOption option;
// Read the min size bytes first, then check the expected
// `SeedIdLength` and read the full `MplOption` if needed.
SuccessOrExit(error = aMessage.Read(aOffset, &option, MplOption::kMinSize));
SuccessOrExit(error = aMessage.Read(aOffsetRange, &option, MplOption::kMinSize));
switch (option.GetSeedIdLength())
{
@@ -108,7 +108,7 @@ Error Mpl::ProcessOption(Message &aMessage, uint16_t aOffset, const Address &aAd
break;
case MplOption::kSeedIdLength2:
SuccessOrExit(error = aMessage.Read(aOffset, option));
SuccessOrExit(error = aMessage.Read(aOffsetRange, option));
break;
case MplOption::kSeedIdLength8:
+6 -6
View File
@@ -191,17 +191,17 @@ public:
* MPL Seed it allows to send the first MPL Data Message directly, then sets up Trickle
* timer expirations for subsequent retransmissions.
*
* @param[in] aMessage A reference to the message.
* @param[in] aOffset The offset in @p aMessage to read the MPL option.
* @param[in] aAddress A reference to the IPv6 Source Address.
* @param[out] aReceive Set to FALSE if the MPL message is a duplicate and must not
* go through the receiving process again, untouched otherwise.
* @param[in] aMessage A reference to the message.
* @param[in] aOffsetRange The offset range in @p aMessage to read the MPL option.
* @param[in] aAddress A reference to the IPv6 Source Address.
* @param[out] aReceive Set to FALSE if the MPL message is a duplicate and must not
* go through the receiving process again, untouched otherwise.
*
* @retval kErrorNone Successfully processed the MPL option.
* @retval kErrorDrop The MPL message is a duplicate and should be dropped.
*
*/
Error ProcessOption(Message &aMessage, uint16_t aOffset, const Address &aAddress, bool &aReceive);
Error ProcessOption(Message &aMessage, const OffsetRange &aOffsetRange, const Address &aAddress, bool &aReceive);
#if OPENTHREAD_FTD
/**
+5 -4
View File
@@ -470,14 +470,15 @@ Error Lowpan::CompressExtensionHeader(Message &aMessage, FrameBuilder &aFrameBui
// Pad1 or PadN option MAY be elided by the compressor."
if (aNextHeader == Ip6::kProtoHopOpts || aNextHeader == Ip6::kProtoDstOpts)
{
uint16_t offset = aMessage.GetOffset();
uint16_t endOffset = offset + len;
OffsetRange offsetRange;
bool hasOption = false;
Ip6::Option option;
for (; offset < endOffset; offset += option.GetSize())
offsetRange.Init(aMessage.GetOffset(), len);
for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(option.GetSize()))
{
SuccessOrExit(error = option.ParseFrom(aMessage, offset, endOffset));
SuccessOrExit(error = option.ParseFrom(aMessage, offsetRange));
hasOption = true;
}