| /* |
| * Copyright (c) 2016, The OpenThread Authors. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the copyright holder nor the |
| * names of its contributors may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * @file |
| * This file implements IPv6 networking. |
| */ |
| |
| #include "ip6.hpp" |
| |
| #include "instance/instance.hpp" |
| |
| using IcmpType = ot::Ip6::Icmp::Header::Type; |
| |
| static const IcmpType kForwardIcmpTypes[] = { |
| IcmpType::kTypeDstUnreach, IcmpType::kTypePacketToBig, IcmpType::kTypeTimeExceeded, |
| IcmpType::kTypeParameterProblem, IcmpType::kTypeEchoRequest, IcmpType::kTypeEchoReply, |
| }; |
| |
| namespace ot { |
| namespace Ip6 { |
| |
| RegisterLogModule("Ip6"); |
| |
| Ip6::Ip6(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mReceiveFilterEnabled(false) |
| #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE |
| , mTmfOriginFilterEnabled(true) |
| #endif |
| , mSendQueueTask(aInstance) |
| , mIcmp(aInstance) |
| , mUdp(aInstance) |
| , mMpl(aInstance) |
| #if OPENTHREAD_CONFIG_TCP_ENABLE |
| , mTcp(aInstance) |
| #endif |
| { |
| #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE |
| ResetBorderRoutingCounters(); |
| #endif |
| } |
| |
| Message *Ip6::NewMessageFromData(const uint8_t *aData, uint16_t aDataLength, const Message::Settings &aSettings) |
| { |
| Message *message = nullptr; |
| Message::Settings settings = aSettings; |
| const Header *header; |
| |
| VerifyOrExit((aData != nullptr) && (aDataLength >= sizeof(Header))); |
| |
| // Determine priority from IPv6 header |
| header = reinterpret_cast<const Header *>(aData); |
| VerifyOrExit(header->IsValid()); |
| VerifyOrExit(sizeof(Header) + header->GetPayloadLength() == aDataLength); |
| settings.mPriority = DscpToPriority(header->GetDscp()); |
| |
| message = Get<MessagePool>().Allocate(Message::kTypeIp6, /* aReserveHeader */ 0, settings); |
| |
| VerifyOrExit(message != nullptr); |
| |
| if (message->AppendBytes(aData, aDataLength) != kErrorNone) |
| { |
| message->Free(); |
| message = nullptr; |
| } |
| |
| exit: |
| return message; |
| } |
| |
| Message::Priority Ip6::DscpToPriority(uint8_t aDscp) |
| { |
| Message::Priority priority; |
| uint8_t cs = aDscp & kDscpCsMask; |
| |
| switch (cs) |
| { |
| case kDscpCs1: |
| case kDscpCs2: |
| priority = Message::kPriorityLow; |
| break; |
| |
| case kDscpCs0: |
| case kDscpCs3: |
| priority = Message::kPriorityNormal; |
| break; |
| |
| case kDscpCs4: |
| case kDscpCs5: |
| case kDscpCs6: |
| case kDscpCs7: |
| priority = Message::kPriorityHigh; |
| break; |
| |
| default: |
| priority = Message::kPriorityNormal; |
| break; |
| } |
| |
| return priority; |
| } |
| |
| uint8_t Ip6::PriorityToDscp(Message::Priority aPriority) |
| { |
| uint8_t dscp = kDscpCs0; |
| |
| switch (aPriority) |
| { |
| case Message::kPriorityLow: |
| dscp = kDscpCs1; |
| break; |
| |
| case Message::kPriorityNormal: |
| case Message::kPriorityNet: |
| dscp = kDscpCs0; |
| break; |
| |
| case Message::kPriorityHigh: |
| dscp = kDscpCs4; |
| break; |
| } |
| |
| return dscp; |
| } |
| |
| Error Ip6::AddMplOption(Message &aMessage, Header &aHeader) |
| { |
| Error error = kErrorNone; |
| HopByHopHeader hbhHeader; |
| MplOption mplOption; |
| PadOption padOption; |
| |
| hbhHeader.SetNextHeader(aHeader.GetNextHeader()); |
| hbhHeader.SetLength(0); |
| mMpl.InitOption(mplOption, aHeader.GetSource()); |
| |
| // Check if MPL option may require padding |
| if (padOption.InitToPadHeaderWithSize(sizeof(HopByHopHeader) + mplOption.GetSize()) == kErrorNone) |
| { |
| SuccessOrExit(error = aMessage.PrependBytes(&padOption, padOption.GetSize())); |
| } |
| |
| SuccessOrExit(error = aMessage.PrependBytes(&mplOption, mplOption.GetSize())); |
| SuccessOrExit(error = aMessage.Prepend(hbhHeader)); |
| aHeader.SetPayloadLength(aHeader.GetPayloadLength() + sizeof(hbhHeader) + sizeof(mplOption)); |
| aHeader.SetNextHeader(kProtoHopOpts); |
| |
| exit: |
| return error; |
| } |
| |
| Error Ip6::PrepareMulticastToLargerThanRealmLocal(Message &aMessage, const Header &aHeader) |
| { |
| Error error = kErrorNone; |
| Header tunnelHeader; |
| |
| #if OPENTHREAD_FTD |
| if (aHeader.GetDestination().IsMulticastLargerThanRealmLocal() && |
| Get<ChildTable>().HasSleepyChildWithAddress(aHeader.GetDestination())) |
| { |
| Message *messageCopy = aMessage.Clone<kSameReservedHeader>(); |
| |
| if (messageCopy != nullptr) |
| { |
| EnqueueDatagram(*messageCopy); |
| } |
| else |
| { |
| LogWarn("Failed to clone mcast message for indirect tx to sleepy children"); |
| } |
| } |
| #endif |
| |
| // Use IP-in-IP encapsulation (RFC2473) and ALL_MPL_FORWARDERS address. |
| tunnelHeader.InitVersionTrafficClassFlow(); |
| tunnelHeader.SetHopLimit(kDefaultHopLimit); |
| tunnelHeader.SetPayloadLength(aHeader.GetPayloadLength() + sizeof(tunnelHeader)); |
| tunnelHeader.SetSource(Get<Mle::Mle>().GetMeshLocalRloc()); |
| tunnelHeader.SetDestination(Address::GetRealmLocalAllMplForwarders()); |
| tunnelHeader.SetNextHeader(kProtoIp6); |
| |
| SuccessOrExit(error = AddMplOption(aMessage, tunnelHeader)); |
| SuccessOrExit(error = aMessage.Prepend(tunnelHeader)); |
| |
| exit: |
| return error; |
| } |
| |
| Error Ip6::InsertMplOption(Message &aMessage, Header &aHeader) |
| { |
| Error error = kErrorNone; |
| |
| if (aHeader.GetDestination().IsMulticastLargerThanRealmLocal()) |
| { |
| error = PrepareMulticastToLargerThanRealmLocal(aMessage, aHeader); |
| ExitNow(); |
| } |
| |
| VerifyOrExit(aHeader.GetDestination().IsRealmLocalMulticast()); |
| |
| aMessage.RemoveHeader(sizeof(aHeader)); |
| |
| if (aHeader.GetNextHeader() == kProtoHopOpts) |
| { |
| HopByHopHeader hbh; |
| uint16_t hbhSize; |
| MplOption mplOption; |
| PadOption padOption; |
| |
| // Read existing hop-by-hop option header |
| SuccessOrExit(error = aMessage.Read(0, hbh)); |
| hbhSize = hbh.GetSize(); |
| |
| VerifyOrExit(hbhSize <= aHeader.GetPayloadLength(), error = kErrorParse); |
| |
| // Increment hop-by-hop option header length by one which |
| // increases its total size by 8 bytes. |
| hbh.SetLength(hbh.GetLength() + 1); |
| aMessage.Write(0, hbh); |
| |
| // Make space for MPL Option + padding (8 bytes) at the end |
| // of hop-by-hop header |
| SuccessOrExit(error = aMessage.InsertHeader(hbhSize, ExtensionHeader::kLengthUnitSize)); |
| |
| // Insert MPL Option |
| mMpl.InitOption(mplOption, aHeader.GetSource()); |
| aMessage.WriteBytes(hbhSize, &mplOption, mplOption.GetSize()); |
| |
| // Insert Pad Option (if needed) |
| if (padOption.InitToPadHeaderWithSize(mplOption.GetSize()) == kErrorNone) |
| { |
| aMessage.WriteBytes(hbhSize + mplOption.GetSize(), &padOption, padOption.GetSize()); |
| } |
| |
| // Update IPv6 Payload Length |
| aHeader.SetPayloadLength(aHeader.GetPayloadLength() + ExtensionHeader::kLengthUnitSize); |
| } |
| else |
| { |
| SuccessOrExit(error = AddMplOption(aMessage, aHeader)); |
| } |
| |
| SuccessOrExit(error = aMessage.Prepend(aHeader)); |
| |
| exit: |
| return error; |
| } |
| |
| Error Ip6::RemoveMplOption(Message &aMessage) |
| { |
| enum Action : uint8_t |
| { |
| kNoMplOption, |
| kShrinkHbh, |
| kRemoveHbh, |
| kReplaceMplWithPad, |
| }; |
| |
| Error error = kErrorNone; |
| Action action = kNoMplOption; |
| Header ip6Header; |
| HopByHopHeader hbh; |
| Option option; |
| OffsetRange offsetRange; |
| OffsetRange mplOffsetRange; |
| PadOption padOption; |
| |
| offsetRange.InitFromMessageFullLength(aMessage); |
| |
| IgnoreError(aMessage.Read(offsetRange, ip6Header)); |
| offsetRange.AdvanceOffset(sizeof(ip6Header)); |
| |
| VerifyOrExit(ip6Header.GetNextHeader() == kProtoHopOpts); |
| |
| SuccessOrExit(error = ReadHopByHopHeader(aMessage, offsetRange, hbh)); |
| |
| for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(option.GetSize())) |
| { |
| SuccessOrExit(error = option.ParseFrom(aMessage, offsetRange)); |
| |
| if (option.IsPadding()) |
| { |
| continue; |
| } |
| |
| if (option.GetType() == MplOption::kType) |
| { |
| // If multiple MPL options exist, discard packet |
| VerifyOrExit(action == kNoMplOption, error = kErrorParse); |
| |
| // `Option::ParseFrom()` already validated that the entire |
| // option is present in the `offsetRange`. |
| |
| mplOffsetRange = offsetRange; |
| mplOffsetRange.ShrinkLength(option.GetSize()); |
| |
| 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 |
| action = kRemoveHbh; |
| } |
| else if (mplOffsetRange.GetOffset() + ExtensionHeader::kLengthUnitSize == offsetRange.GetEndOffset()) |
| { |
| // Last IPv6 Option, shrink the last 8 bytes |
| action = kShrinkHbh; |
| } |
| } |
| else if (action != kNoMplOption) |
| { |
| // Encountered another option, now just replace |
| // MPL Option with Pad Option |
| action = kReplaceMplWithPad; |
| } |
| } |
| |
| switch (action) |
| { |
| case kNoMplOption: |
| break; |
| |
| case kShrinkHbh: |
| case kRemoveHbh: |
| // Last IPv6 Option, shrink HBH Option header by |
| // 8 bytes (`kLengthUnitSize`) |
| aMessage.RemoveHeader(offsetRange.GetEndOffset() - ExtensionHeader::kLengthUnitSize, |
| ExtensionHeader::kLengthUnitSize); |
| |
| if (action == kRemoveHbh) |
| { |
| ip6Header.SetNextHeader(hbh.GetNextHeader()); |
| } |
| else |
| { |
| // Update HBH header length, decrement by one |
| // which decreases its total size by 8 bytes. |
| |
| hbh.SetLength(hbh.GetLength() - 1); |
| aMessage.Write(sizeof(ip6Header), hbh); |
| } |
| |
| ip6Header.SetPayloadLength(ip6Header.GetPayloadLength() - ExtensionHeader::kLengthUnitSize); |
| aMessage.Write(0, ip6Header); |
| break; |
| |
| case kReplaceMplWithPad: |
| padOption.InitForPadSize(static_cast<uint8_t>(mplOffsetRange.GetLength())); |
| aMessage.WriteBytes(mplOffsetRange.GetOffset(), &padOption, padOption.GetSize()); |
| break; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| void Ip6::EnqueueDatagram(Message &aMessage) |
| { |
| mSendQueue.Enqueue(aMessage); |
| mSendQueueTask.Post(); |
| } |
| |
| Error Ip6::SendDatagram(Message &aMessage, MessageInfo &aMessageInfo, uint8_t aIpProto) |
| { |
| Error error = kErrorNone; |
| Header header; |
| uint8_t dscp; |
| uint16_t payloadLength = aMessage.GetLength(); |
| |
| if ((aIpProto == kProtoUdp) && |
| Get<Tmf::Agent>().IsTmfMessage(aMessageInfo.GetSockAddr(), aMessageInfo.GetPeerAddr(), |
| aMessageInfo.GetPeerPort())) |
| { |
| dscp = Tmf::Agent::PriorityToDscp(aMessage.GetPriority()); |
| } |
| else |
| { |
| dscp = PriorityToDscp(aMessage.GetPriority()); |
| } |
| |
| header.InitVersionTrafficClassFlow(); |
| header.SetDscp(dscp); |
| header.SetEcn(aMessageInfo.GetEcn()); |
| header.SetPayloadLength(payloadLength); |
| header.SetNextHeader(aIpProto); |
| |
| if (aMessageInfo.GetHopLimit() != 0 || aMessageInfo.ShouldAllowZeroHopLimit()) |
| { |
| header.SetHopLimit(aMessageInfo.GetHopLimit()); |
| } |
| else |
| { |
| header.SetHopLimit(kDefaultHopLimit); |
| } |
| |
| if (aMessageInfo.GetSockAddr().IsUnspecified() || aMessageInfo.GetSockAddr().IsMulticast()) |
| { |
| const Address *source = SelectSourceAddress(aMessageInfo.GetPeerAddr()); |
| |
| VerifyOrExit(source != nullptr, error = kErrorInvalidSourceAddress); |
| header.SetSource(*source); |
| } |
| else |
| { |
| header.SetSource(aMessageInfo.GetSockAddr()); |
| } |
| |
| header.SetDestination(aMessageInfo.GetPeerAddr()); |
| |
| if (aMessageInfo.GetPeerAddr().IsRealmLocalMulticast()) |
| { |
| SuccessOrExit(error = AddMplOption(aMessage, header)); |
| } |
| |
| SuccessOrExit(error = aMessage.Prepend(header)); |
| |
| Checksum::UpdateMessageChecksum(aMessage, header.GetSource(), header.GetDestination(), aIpProto); |
| |
| if (aMessageInfo.GetPeerAddr().IsMulticastLargerThanRealmLocal()) |
| { |
| SuccessOrExit(error = PrepareMulticastToLargerThanRealmLocal(aMessage, header)); |
| } |
| |
| aMessage.SetMulticastLoop(aMessageInfo.GetMulticastLoop()); |
| |
| if (aMessage.GetLength() > kMaxDatagramLength) |
| { |
| error = FragmentDatagram(aMessage, aIpProto); |
| } |
| else |
| { |
| EnqueueDatagram(aMessage); |
| } |
| |
| exit: |
| |
| return error; |
| } |
| |
| void Ip6::HandleSendQueue(void) |
| { |
| Message *message; |
| |
| while ((message = mSendQueue.GetHead()) != nullptr) |
| { |
| mSendQueue.Dequeue(*message); |
| IgnoreError(HandleDatagram(OwnedPtr<Message>(message))); |
| } |
| } |
| |
| 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, const Header &aHeader, bool &aReceive) |
| { |
| Error error = kErrorNone; |
| bool hasMplOption = false; |
| HopByHopHeader hbhHeader; |
| Option option; |
| OffsetRange offsetRange; |
| MplOption mplOption; |
| |
| offsetRange.InitFromMessageOffsetToEnd(aMessage); |
| |
| SuccessOrExit(error = ReadHopByHopHeader(aMessage, offsetRange, hbhHeader)); |
| |
| // `ReadHopByHopHeader()` updates `offsetRange` to refer to the |
| // location of the options within the HBH header. We first |
| // validate all options, ensuring there is at most one MPL |
| // option, before processing it. |
| |
| for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(option.GetSize())) |
| { |
| SuccessOrExit(error = option.ParseFrom(aMessage, offsetRange)); |
| |
| if (option.IsPadding()) |
| { |
| continue; |
| } |
| |
| if (option.GetType() == MplOption::kType) |
| { |
| VerifyOrExit(!hasMplOption, error = kErrorDrop); |
| hasMplOption = true; |
| |
| SuccessOrExit(error = mMpl.ReadAndValidateOption(aMessage, offsetRange, aHeader.GetSource(), mplOption)); |
| |
| continue; |
| } |
| |
| VerifyOrExit(option.GetAction() == Option::kActionSkip, error = kErrorDrop); |
| } |
| |
| if (hasMplOption) |
| { |
| SuccessOrExit(error = mMpl.ProcessOption(aMessage, mplOption, aReceive)); |
| } |
| |
| aMessage.SetOffset(offsetRange.GetEndOffset()); |
| |
| exit: |
| return error; |
| } |
| |
| #if OPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE |
| Error Ip6::FragmentDatagram(Message &aMessage, uint8_t aIpProto) |
| { |
| Error error = kErrorNone; |
| Header header; |
| FragmentHeader fragmentHeader; |
| Message *fragment = nullptr; |
| uint16_t fragmentCnt = 0; |
| uint16_t payloadFragment = 0; |
| uint16_t offset = 0; |
| |
| uint16_t maxPayloadFragment = |
| FragmentHeader::MakeDivisibleByEight(kMinimalMtu - aMessage.GetOffset() - sizeof(fragmentHeader)); |
| uint16_t payloadLeft = aMessage.DetermineLengthAfterOffset(); |
| |
| SuccessOrExit(error = aMessage.Read(0, header)); |
| header.SetNextHeader(kProtoFragment); |
| |
| fragmentHeader.Init(); |
| fragmentHeader.SetIdentification(Random::NonCrypto::GetUint32()); |
| fragmentHeader.SetNextHeader(aIpProto); |
| fragmentHeader.SetMoreFlag(); |
| |
| while (payloadLeft != 0) |
| { |
| if (payloadLeft < maxPayloadFragment) |
| { |
| fragmentHeader.ClearMoreFlag(); |
| |
| payloadFragment = payloadLeft; |
| payloadLeft = 0; |
| |
| LogDebg("Last Fragment"); |
| } |
| else |
| { |
| payloadLeft -= maxPayloadFragment; |
| payloadFragment = maxPayloadFragment; |
| } |
| |
| offset = fragmentCnt * FragmentHeader::BytesToFragmentOffset(maxPayloadFragment); |
| fragmentHeader.SetOffset(offset); |
| |
| VerifyOrExit((fragment = NewMessage()) != nullptr, error = kErrorNoBufs); |
| IgnoreError(fragment->SetPriority(aMessage.GetPriority())); |
| SuccessOrExit(error = fragment->SetLength(aMessage.GetOffset() + sizeof(fragmentHeader) + payloadFragment)); |
| |
| header.SetPayloadLength(payloadFragment + sizeof(fragmentHeader)); |
| fragment->Write(0, header); |
| |
| fragment->SetOffset(aMessage.GetOffset()); |
| fragment->Write(aMessage.GetOffset(), fragmentHeader); |
| |
| fragment->WriteBytesFromMessage( |
| /* aWriteOffset */ aMessage.GetOffset() + sizeof(fragmentHeader), aMessage, |
| /* aReadOffset */ aMessage.GetOffset() + FragmentHeader::FragmentOffsetToBytes(offset), |
| /* aLength */ payloadFragment); |
| |
| EnqueueDatagram(*fragment); |
| |
| fragmentCnt++; |
| fragment = nullptr; |
| |
| LogInfo("Fragment %d with %d bytes sent", fragmentCnt, payloadFragment); |
| } |
| |
| aMessage.Free(); |
| |
| exit: |
| |
| if (error == kErrorNoBufs) |
| { |
| LogWarn("No buffer for Ip6 fragmentation"); |
| } |
| |
| FreeMessageOnError(fragment, error); |
| return error; |
| } |
| |
| Error Ip6::HandleFragment(Message &aMessage) |
| { |
| Error error = kErrorNone; |
| Header header, headerBuffer; |
| FragmentHeader fragmentHeader; |
| Message *message = nullptr; |
| uint16_t offset = 0; |
| uint16_t payloadFragment = 0; |
| bool isFragmented = true; |
| |
| SuccessOrExit(error = aMessage.Read(0, header)); |
| SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), fragmentHeader)); |
| |
| if (fragmentHeader.GetOffset() == 0 && !fragmentHeader.IsMoreFlagSet()) |
| { |
| isFragmented = false; |
| aMessage.MoveOffset(sizeof(fragmentHeader)); |
| ExitNow(); |
| } |
| |
| for (Message &msg : mReassemblyList) |
| { |
| SuccessOrExit(error = msg.Read(0, headerBuffer)); |
| |
| if (msg.GetDatagramTag() == fragmentHeader.GetIdentification() && |
| headerBuffer.GetSource() == header.GetSource() && headerBuffer.GetDestination() == header.GetDestination()) |
| { |
| message = &msg; |
| break; |
| } |
| } |
| |
| offset = FragmentHeader::FragmentOffsetToBytes(fragmentHeader.GetOffset()); |
| payloadFragment = aMessage.GetLength() - aMessage.GetOffset() - sizeof(fragmentHeader); |
| |
| LogInfo("Fragment with id %lu received > %u bytes, offset %u", ToUlong(fragmentHeader.GetIdentification()), |
| payloadFragment, offset); |
| |
| if (offset + payloadFragment + aMessage.GetOffset() > kMaxAssembledDatagramLength) |
| { |
| LogWarn("Packet too large for fragment buffer"); |
| ExitNow(error = kErrorNoBufs); |
| } |
| |
| if (message == nullptr) |
| { |
| LogDebg("start reassembly"); |
| VerifyOrExit((message = NewMessage()) != nullptr, error = kErrorNoBufs); |
| mReassemblyList.Enqueue(*message); |
| |
| message->SetTimestampToNow(); |
| message->SetOffset(0); |
| message->SetDatagramTag(fragmentHeader.GetIdentification()); |
| |
| // copying the non-fragmentable header to the fragmentation buffer |
| SuccessOrExit(error = message->AppendBytesFromMessage(aMessage, 0, aMessage.GetOffset())); |
| |
| Get<TimeTicker>().RegisterReceiver(TimeTicker::kIp6FragmentReassembler); |
| } |
| |
| // increase message buffer if necessary |
| if (message->GetLength() < offset + payloadFragment + aMessage.GetOffset()) |
| { |
| SuccessOrExit(error = message->SetLength(offset + payloadFragment + aMessage.GetOffset())); |
| } |
| |
| // copy the fragment payload into the message buffer |
| message->WriteBytesFromMessage( |
| /* aWriteOffset */ aMessage.GetOffset() + offset, aMessage, |
| /* aReadOffset */ aMessage.GetOffset() + sizeof(fragmentHeader), /* aLength */ payloadFragment); |
| |
| // check if it is the last frame |
| if (!fragmentHeader.IsMoreFlagSet()) |
| { |
| // use the offset value for the whole ip message length |
| message->SetOffset(aMessage.GetOffset() + offset + payloadFragment); |
| |
| // creates the header for the reassembled ipv6 package |
| SuccessOrExit(error = aMessage.Read(0, header)); |
| header.SetPayloadLength(message->GetLength() - sizeof(header)); |
| header.SetNextHeader(fragmentHeader.GetNextHeader()); |
| message->Write(0, header); |
| |
| LogDebg("Reassembly complete."); |
| |
| mReassemblyList.Dequeue(*message); |
| |
| IgnoreError(HandleDatagram(OwnedPtr<Message>(message), /* aIsReassembled */ true)); |
| } |
| |
| exit: |
| if (error != kErrorDrop && error != kErrorNone && isFragmented) |
| { |
| if (message != nullptr) |
| { |
| mReassemblyList.DequeueAndFree(*message); |
| } |
| |
| LogWarnOnError(error, "reassemble"); |
| } |
| |
| if (isFragmented) |
| { |
| // drop all fragments, the payload is stored in the fragment buffer |
| error = kErrorDrop; |
| } |
| |
| return error; |
| } |
| |
| void Ip6::CleanupFragmentationBuffer(void) { mReassemblyList.DequeueAndFreeAll(); } |
| |
| void Ip6::HandleTimeTick(void) |
| { |
| UpdateReassemblyList(); |
| |
| if (mReassemblyList.GetHead() == nullptr) |
| { |
| Get<TimeTicker>().UnregisterReceiver(TimeTicker::kIp6FragmentReassembler); |
| } |
| } |
| |
| void Ip6::UpdateReassemblyList(void) |
| { |
| TimeMilli now = TimerMilli::GetNow(); |
| |
| for (Message &message : mReassemblyList) |
| { |
| if (now - message.GetTimestamp() >= TimeMilli::SecToMsec(kReassemblyTimeout)) |
| { |
| LogInfo("Reassembly timeout."); |
| SendIcmpError(message, Icmp::Header::kTypeTimeExceeded, Icmp::Header::kCodeFragmReasTimeEx); |
| |
| mReassemblyList.DequeueAndFree(message); |
| } |
| } |
| } |
| |
| void Ip6::SendIcmpError(Message &aMessage, Icmp::Header::Type aIcmpType, Icmp::Header::Code aIcmpCode) |
| { |
| Error error = kErrorNone; |
| Header header; |
| MessageInfo messageInfo; |
| |
| SuccessOrExit(error = aMessage.Read(0, header)); |
| |
| messageInfo.SetPeerAddr(header.GetSource()); |
| messageInfo.SetSockAddr(header.GetDestination()); |
| messageInfo.SetHopLimit(header.GetHopLimit()); |
| |
| error = mIcmp.SendError(aIcmpType, aIcmpCode, messageInfo, aMessage); |
| |
| exit: |
| LogWarnOnError(error, "send ICMP"); |
| OT_UNUSED_VARIABLE(error); |
| } |
| |
| #else |
| Error Ip6::FragmentDatagram(Message &aMessage, uint8_t aIpProto) |
| { |
| OT_UNUSED_VARIABLE(aIpProto); |
| |
| EnqueueDatagram(aMessage); |
| |
| return kErrorNone; |
| } |
| |
| Error Ip6::HandleFragment(Message &aMessage) |
| { |
| Error error; |
| FragmentHeader fragmentHeader; |
| |
| SuccessOrExit(error = aMessage.ReadAtAndAdvanceOffset(fragmentHeader)); |
| |
| VerifyOrExit(fragmentHeader.GetOffset() == 0 && !fragmentHeader.IsMoreFlagSet(), error = kErrorDrop); |
| |
| exit: |
| return error; |
| } |
| #endif // OPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE |
| |
| Error Ip6::HandleExtensionHeaders(OwnedPtr<Message> &aMessagePtr, |
| const Header &aHeader, |
| uint8_t &aNextHeader, |
| bool &aReceive) |
| { |
| Error error = kErrorNone; |
| ExtensionHeader extHeader; |
| |
| while (aReceive || aNextHeader == kProtoHopOpts) |
| { |
| SuccessOrExit(error = aMessagePtr->Read(aMessagePtr->GetOffset(), extHeader)); |
| |
| switch (aNextHeader) |
| { |
| case kProtoHopOpts: |
| case kProtoDstOpts: |
| SuccessOrExit(error = HandleOptions(*aMessagePtr, aHeader, aReceive)); |
| break; |
| |
| case kProtoFragment: |
| IgnoreError(PassToHost(aMessagePtr, aHeader, aNextHeader, aReceive, kCopyMessageToUse)); |
| SuccessOrExit(error = HandleFragment(*aMessagePtr)); |
| break; |
| |
| case kProtoIp6: |
| ExitNow(); |
| |
| case kProtoRouting: |
| case kProtoNone: |
| ExitNow(error = kErrorDrop); |
| |
| default: |
| ExitNow(); |
| } |
| |
| aNextHeader = extHeader.GetNextHeader(); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Error Ip6::TakeOrCopyMessagePtr(OwnedPtr<Message> &aTargetPtr, |
| OwnedPtr<Message> &aMessagePtr, |
| MessageOwnership aMessageOwnership) |
| { |
| switch (aMessageOwnership) |
| { |
| case kTakeMessageCustody: |
| aTargetPtr = aMessagePtr.PassOwnership(); |
| break; |
| |
| case kCopyMessageToUse: |
| aTargetPtr.Reset(aMessagePtr->Clone<kNoReservedHeader>()); |
| break; |
| } |
| |
| return (aTargetPtr != nullptr) ? kErrorNone : kErrorNoBufs; |
| } |
| |
| Error Ip6::Receive(Header &aIp6Header, |
| OwnedPtr<Message> &aMessagePtr, |
| uint8_t aIpProto, |
| MessageOwnership aMessageOwnership) |
| { |
| Error error = kErrorNone; |
| OwnedPtr<Message> messagePtr; |
| MessageInfo messageInfo; |
| |
| messageInfo.Clear(); |
| messageInfo.SetPeerAddr(aIp6Header.GetSource()); |
| messageInfo.SetSockAddr(aIp6Header.GetDestination()); |
| messageInfo.SetHopLimit(aIp6Header.GetHopLimit()); |
| messageInfo.SetEcn(aIp6Header.GetEcn()); |
| |
| switch (aIpProto) |
| { |
| case kProtoUdp: |
| case kProtoIcmp6: |
| break; |
| #if OPENTHREAD_CONFIG_TCP_ENABLE |
| case kProtoTcp: |
| break; |
| #endif |
| default: |
| ExitNow(); |
| } |
| |
| SuccessOrExit(error = TakeOrCopyMessagePtr(messagePtr, aMessagePtr, aMessageOwnership)); |
| |
| switch (aIpProto) |
| { |
| #if OPENTHREAD_CONFIG_TCP_ENABLE |
| case kProtoTcp: |
| error = mTcp.HandleMessage(aIp6Header, *messagePtr, messageInfo); |
| break; |
| #endif |
| case kProtoUdp: |
| error = mUdp.HandleMessage(*messagePtr, messageInfo); |
| break; |
| |
| case kProtoIcmp6: |
| error = mIcmp.HandleMessage(*messagePtr, messageInfo); |
| break; |
| |
| default: |
| break; |
| } |
| |
| exit: |
| LogWarnOnError(error, "handle payload"); |
| return error; |
| } |
| |
| Error Ip6::PassToHost(OwnedPtr<Message> &aMessagePtr, |
| const Header &aHeader, |
| uint8_t aIpProto, |
| bool aReceive, |
| MessageOwnership aMessageOwnership) |
| { |
| // This method passes the message to host by invoking the |
| // registered IPv6 receive callback. When NAT64 is enabled, it |
| // may also perform translation and invoke IPv4 receive |
| // callback. |
| |
| Error error = kErrorNone; |
| OwnedPtr<Message> messagePtr; |
| |
| VerifyOrExit(aMessagePtr->IsLoopbackToHostAllowed()); |
| |
| VerifyOrExit(mReceiveCallback.IsSet(), error = kErrorNoRoute); |
| |
| // Do not pass IPv6 packets that exceed kMinimalMtu. |
| VerifyOrExit(aMessagePtr->GetLength() <= kMinimalMtu, error = kErrorDrop); |
| |
| #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTING_REACHABILITY_CHECK_ICMP6_ERROR_ENABLE |
| if (!aReceive) |
| { |
| Get<BorderRouter::RoutingManager>().CheckReachabilityToSendIcmpError(*aMessagePtr, aHeader); |
| } |
| #endif |
| |
| // If the sender used mesh-local address as source, do not pass to |
| // host unless this message is intended for this device itself. |
| if (Get<Mle::Mle>().IsMeshLocalAddress(aHeader.GetSource())) |
| { |
| VerifyOrExit(aReceive, error = kErrorDrop); |
| } |
| |
| if (mReceiveFilterEnabled && aReceive && !aHeader.GetDestination().IsMulticastLargerThanRealmLocal()) |
| { |
| switch (aIpProto) |
| { |
| case kProtoIcmp6: |
| if (mIcmp.ShouldHandleEchoRequest(aHeader.GetDestination())) |
| { |
| Icmp::Header icmp; |
| |
| IgnoreError(aMessagePtr->Read(aMessagePtr->GetOffset(), icmp)); |
| VerifyOrExit(icmp.GetType() != Icmp::Header::kTypeEchoRequest, error = kErrorDrop); |
| } |
| |
| break; |
| |
| case kProtoUdp: |
| { |
| Udp::Header udp; |
| |
| IgnoreError(aMessagePtr->Read(aMessagePtr->GetOffset(), udp)); |
| VerifyOrExit(!Get<Udp>().IsPortInUse(udp.GetDestinationPort()), error = kErrorNoRoute); |
| break; |
| } |
| |
| #if OPENTHREAD_CONFIG_TCP_ENABLE |
| // Do not pass TCP message to avoid dual processing from both |
| // OpenThread and POSIX TCP stacks. |
| case kProtoTcp: |
| error = kErrorNoRoute; |
| ExitNow(); |
| #endif |
| |
| default: |
| break; |
| } |
| } |
| |
| SuccessOrExit(error = TakeOrCopyMessagePtr(messagePtr, aMessagePtr, aMessageOwnership)); |
| |
| IgnoreError(RemoveMplOption(*messagePtr)); |
| |
| #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE |
| error = Get<Nat64::Translator>().TranslateIp6ToIp4(*messagePtr); |
| |
| if (error == kErrorAbort) // `kErrorAbort` indicates no translation was needed or performed. |
| { |
| error = kErrorNone; |
| } |
| else |
| { |
| SuccessOrExit(error); |
| VerifyOrExit(mIp4ReceiveCallback.IsSet(), error = kErrorNoRoute); |
| // Pass message to callback transferring its ownership. |
| mIp4ReceiveCallback.Invoke(messagePtr.Release()); |
| ExitNow(); |
| } |
| #endif |
| |
| #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE |
| UpdateBorderRoutingCounters(aHeader, messagePtr->GetLength(), /* aIsInbound */ false); |
| #endif |
| |
| #if OPENTHREAD_CONFIG_IP6_RESTRICT_FORWARDING_LARGER_SCOPE_MCAST_WITH_LOCAL_SRC |
| // Some platforms (e.g. Android) currently doesn't restrict link-local/mesh-local source |
| // addresses when forwarding multicast packets. |
| // For a multicast packet sent from link-local/mesh-local address to scope larger |
| // than realm-local, set the hop limit to 1 before sending to host, so this packet |
| // will not be forwarded by host. |
| if (aHeader.GetDestination().IsMulticastLargerThanRealmLocal() && |
| (aHeader.GetSource().IsLinkLocalUnicast() || (Get<Mle::Mle>().IsMeshLocalAddress(aHeader.GetSource())))) |
| { |
| messagePtr->Write<uint8_t>(Header::kHopLimitFieldOffset, 1); |
| } |
| #endif |
| |
| // Pass message to callback transferring its ownership. |
| mReceiveCallback.Invoke(messagePtr.Release()); |
| |
| exit: |
| return error; |
| } |
| |
| Error Ip6::SendRaw(OwnedPtr<Message> aMessagePtr) |
| { |
| Error error = kErrorNone; |
| Header header; |
| |
| SuccessOrExit(error = header.ParseFrom(*aMessagePtr)); |
| VerifyOrExit(!header.GetSource().IsMulticast(), error = kErrorInvalidSourceAddress); |
| |
| #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE |
| // The filtering rules don't apply to packets from DUA. |
| if (!Get<BackboneRouter::Leader>().IsDomainUnicast(header.GetSource())) |
| #endif |
| { |
| // When the packet is forwarded from host to Thread, if its source is on-mesh or its destination is |
| // mesh-local, we'll drop the packet unless the packet originates from this device. |
| if (Get<NetworkData::Leader>().IsOnMesh(header.GetSource()) || |
| Get<Mle::Mle>().IsMeshLocalAddress(header.GetDestination())) |
| { |
| VerifyOrExit(Get<ThreadNetif>().HasUnicastAddress(header.GetSource()), error = kErrorDrop); |
| } |
| } |
| |
| if (header.GetDestination().IsMulticast()) |
| { |
| SuccessOrExit(error = InsertMplOption(*aMessagePtr, header)); |
| } |
| |
| #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE |
| UpdateBorderRoutingCounters(header, aMessagePtr->GetLength(), /* aIsInbound */ true); |
| #endif |
| |
| error = HandleDatagram(aMessagePtr.PassOwnership()); |
| |
| exit: |
| return error; |
| } |
| |
| void Ip6::DetermineAction(const Message &aMessage, |
| const Header &aHeader, |
| bool &aForwardThread, |
| bool &aForwardHost, |
| bool &aReceive) const |
| { |
| // Determine `aForwardThread`, `aForwardHost` and `aReceive` |
| // based on the destination address and message origin. |
| |
| uint16_t rloc16; |
| |
| aForwardThread = false; |
| aForwardHost = false; |
| aReceive = false; |
| |
| if (aHeader.GetDestination().IsMulticast()) |
| { |
| // Destination is multicast |
| |
| if (aHeader.GetDestination().IsMulticastLargerThanRealmLocal()) |
| { |
| // Multicast messages with scope larger than realm-local |
| // use IP-in-IP encapsulation destined to the "All MPL |
| // Forwarders" multicast address. Both the encapsulated |
| // (outer) and embedded (inner) messages are processed. |
| // |
| // For the inner message, we set `aForwardThread` if the |
| // device has a sleepy child that is subscribed to the |
| // destination address. `MeshForwarder::SendMessage()` on |
| // an FTD will then check for this and schedules the |
| // indirect tx to such children. This is skipped on an MTD |
| // (`aForwardThread` remains `false`) since an MTD cannot |
| // have children. |
| |
| #if OPENTHREAD_FTD |
| aForwardThread = Get<ChildTable>().HasSleepyChildWithAddress(aHeader.GetDestination()); |
| #endif |
| } |
| else |
| { |
| // For all other multicast messages, we forward to the |
| // Thread network unless the message was received on the |
| // Thread netif. |
| |
| aForwardThread = !aMessage.IsOriginThreadNetif(); |
| } |
| |
| // Always forward multicast packets to host network stack |
| aForwardHost = true; |
| |
| // Determine `aReceive` for a multicast message destined for |
| // an address to which this device is subscribed. |
| // |
| // 1) Accept if `MulticastLoop` is enabled. |
| // 2) Otherwise, if it originates from the Thread Netif: |
| // - Always accept if the device is non-sleepy. |
| // - If sleepy, only if the source is NOT the SED itself. |
| // |
| // For non-sleepy devices, duplicate multicast detection is |
| // handled in `Mpl::ProcessOption()`, which will update |
| // `aReceive` if the message was seen before (based on the |
| // MPL Seed). |
| // |
| // To optimize SED behavior, MPL processing is fully skipped |
| // on SEDs. Therefore, the check above is added to handle the |
| // specific case where an SED sends a multicast message to a |
| // group it is subscribed to. The parent can send it back to |
| // the SED (since the child is subscribed), we check and skip |
| // processing it. |
| |
| if (Get<ThreadNetif>().IsMulticastSubscribed(aHeader.GetDestination())) |
| { |
| if (aMessage.GetMulticastLoop()) |
| { |
| aReceive = true; |
| } |
| else if (aMessage.IsOriginThreadNetif()) |
| { |
| if (Get<Mle::Mle>().IsRxOnWhenIdle()) |
| { |
| aReceive = true; |
| } |
| else |
| { |
| aReceive = !Get<ThreadNetif>().HasUnicastAddress(aHeader.GetSource()); |
| } |
| } |
| } |
| |
| ExitNow(); |
| } |
| |
| // Destination is unicast |
| |
| if (Get<ThreadNetif>().HasUnicastAddress(aHeader.GetDestination())) |
| { |
| aReceive = true; |
| ExitNow(); |
| } |
| |
| if (aHeader.GetDestination().IsLinkLocalUnicast()) |
| { |
| // Forward a message with a link-local destination address |
| // to thread, unless it is received on the Thread netif. |
| |
| aForwardThread = !aMessage.IsOriginThreadNetif(); |
| ExitNow(); |
| } |
| |
| if (IsOnLink(aHeader.GetDestination())) |
| { |
| #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE |
| aForwardThread = (!aMessage.IsLoopbackToHostAllowed() || |
| !Get<BackboneRouter::Manager>().ShouldForwardDuaToBackbone(aHeader.GetDestination())); |
| aForwardHost = !aForwardThread; |
| #else |
| aForwardThread = true; |
| #endif |
| ExitNow(); |
| } |
| |
| if (Get<NetworkData::Leader>().RouteLookup(aHeader.GetSource(), aHeader.GetDestination(), rloc16) != kErrorNone) |
| { |
| // No route in mesh, forward to host (as a last resort). |
| LogInfo("Failed to find valid route for: %s", aHeader.GetDestination().ToString().AsCString()); |
| aForwardHost = true; |
| ExitNow(); |
| } |
| |
| // `RouteLookup()` found a destination within the mesh. If we are |
| // the destination (device is acting as a Border Router), forward |
| // it to the host. Otherwise, forward to Thread. |
| |
| if (Get<Mle::Mle>().HasRloc16(rloc16)) |
| { |
| aForwardHost = true; |
| } |
| else |
| { |
| aForwardThread = true; |
| } |
| |
| exit: |
| return; |
| } |
| |
| Error Ip6::HandleDatagram(OwnedPtr<Message> aMessagePtr, bool aIsReassembled) |
| { |
| Error error; |
| Header header; |
| bool receive; |
| bool forwardThread; |
| bool forwardHost; |
| uint8_t nextHeader; |
| |
| SuccessOrExit(error = header.ParseFrom(*aMessagePtr)); |
| |
| if (!aMessagePtr->IsOriginHostTrusted()) |
| { |
| VerifyOrExit(!header.GetSource().IsLoopback() && !header.GetDestination().IsLoopback(), error = kErrorDrop); |
| } |
| |
| DetermineAction(*aMessagePtr, header, forwardThread, forwardHost, receive); |
| |
| aMessagePtr->SetOffset(sizeof(header)); |
| |
| // Process IPv6 Extension Headers |
| nextHeader = header.GetNextHeader(); |
| SuccessOrExit(error = HandleExtensionHeaders(aMessagePtr, header, nextHeader, receive)); |
| |
| if (receive && (nextHeader == kProtoIp6)) |
| { |
| // Process the embedded IPv6 message in an IPv6 tunnel message. |
| // If we need to `forwardThread` we create a copy by cloning |
| // the message, otherwise we take ownership of `aMessage` |
| // itself and use it directly. The encapsulating header is |
| // then removed before processing the embedded message. |
| |
| OwnedPtr<Message> messagePtr; |
| bool multicastLoop = aMessagePtr->GetMulticastLoop(); |
| |
| SuccessOrExit(error = TakeOrCopyMessagePtr(messagePtr, aMessagePtr, |
| forwardThread ? kCopyMessageToUse : kTakeMessageCustody)); |
| messagePtr->SetMulticastLoop(multicastLoop); |
| messagePtr->RemoveHeader(messagePtr->GetOffset()); |
| |
| Get<MeshForwarder>().LogMessage(MeshForwarder::kMessageReceive, *messagePtr); |
| |
| IgnoreError(HandleDatagram(messagePtr.PassOwnership(), aIsReassembled)); |
| |
| receive = false; |
| forwardHost = false; |
| } |
| |
| if ((forwardHost || receive) && !aIsReassembled) |
| { |
| error = PassToHost(aMessagePtr, header, nextHeader, receive, |
| (receive || forwardThread) ? kCopyMessageToUse : kTakeMessageCustody); |
| } |
| |
| if (receive) |
| { |
| error = Receive(header, aMessagePtr, nextHeader, forwardThread ? kCopyMessageToUse : kTakeMessageCustody); |
| } |
| |
| if (forwardThread) |
| { |
| if (aMessagePtr->IsOriginThreadNetif()) |
| { |
| VerifyOrExit(Get<Mle::Mle>().IsRouterOrLeader()); |
| header.SetHopLimit(header.GetHopLimit() - 1); |
| } |
| |
| VerifyOrExit(header.GetHopLimit() > 0, error = kErrorDrop); |
| |
| aMessagePtr->Write<uint8_t>(Header::kHopLimitFieldOffset, header.GetHopLimit()); |
| |
| if (nextHeader == kProtoIcmp6) |
| { |
| uint8_t icmpType; |
| |
| SuccessOrExit(error = aMessagePtr->Read(aMessagePtr->GetOffset(), icmpType)); |
| |
| error = kErrorDrop; |
| |
| for (IcmpType type : kForwardIcmpTypes) |
| { |
| if (icmpType == type) |
| { |
| error = kErrorNone; |
| break; |
| } |
| } |
| |
| SuccessOrExit(error); |
| } |
| |
| #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE |
| if (mTmfOriginFilterEnabled) |
| #endif |
| { |
| if (aMessagePtr->IsOriginHostUntrusted() && (nextHeader == kProtoUdp)) |
| { |
| Udp::Header udpHeader; |
| |
| SuccessOrExit(error = aMessagePtr->Read(aMessagePtr->GetOffset(), udpHeader)); |
| |
| if (udpHeader.GetDestinationPort() == Tmf::kUdpPort) |
| { |
| LogInfo("Dropping TMF message from untrusted origin"); |
| ExitNow(error = kErrorDrop); |
| } |
| } |
| } |
| |
| #if OPENTHREAD_CONFIG_MULTI_RADIO |
| // Since the message will be forwarded, we clear the radio |
| // type on the message to allow the radio type for tx to be |
| // selected later (based on the radios supported by the next |
| // hop). |
| aMessagePtr->ClearRadioType(); |
| #endif |
| |
| Get<MeshForwarder>().SendMessage(aMessagePtr.PassOwnership()); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Error Ip6::SelectSourceAddress(MessageInfo &aMessageInfo) const |
| { |
| Error error = kErrorNone; |
| const Address *source; |
| |
| source = SelectSourceAddress(aMessageInfo.GetPeerAddr()); |
| VerifyOrExit(source != nullptr, error = kErrorNotFound); |
| aMessageInfo.SetSockAddr(*source); |
| |
| exit: |
| return error; |
| } |
| |
| const Address *Ip6::SelectSourceAddress(const Address &aDestination) const |
| { |
| uint8_t destScope = aDestination.GetScope(); |
| bool destIsRloc = Get<Mle::Mle>().IsRoutingLocator(aDestination); |
| const Netif::UnicastAddress *bestAddr = nullptr; |
| uint8_t bestMatchLen = 0; |
| |
| for (const Netif::UnicastAddress &addr : Get<ThreadNetif>().GetUnicastAddresses()) |
| { |
| bool newAddrIsPreferred = false; |
| uint8_t matchLen; |
| uint8_t overrideScope; |
| |
| if (Get<Mle::Mle>().IsAnycastLocator(addr.GetAddress())) |
| { |
| // Don't use anycast address as source address. |
| continue; |
| } |
| |
| matchLen = aDestination.PrefixMatch(addr.GetAddress()); |
| |
| if (matchLen >= addr.mPrefixLength) |
| { |
| matchLen = addr.mPrefixLength; |
| overrideScope = addr.GetScope(); |
| } |
| else |
| { |
| overrideScope = destScope; |
| } |
| |
| if (addr.GetAddress() == aDestination) |
| { |
| // Rule 1: Prefer same address |
| bestAddr = &addr; |
| ExitNow(); |
| } |
| |
| if (bestAddr == nullptr) |
| { |
| newAddrIsPreferred = true; |
| } |
| else if (addr.GetScope() < bestAddr->GetScope()) |
| { |
| // Rule 2: Prefer appropriate scope |
| newAddrIsPreferred = (addr.GetScope() >= overrideScope); |
| } |
| else if (addr.GetScope() > bestAddr->GetScope()) |
| { |
| newAddrIsPreferred = (bestAddr->GetScope() < overrideScope); |
| } |
| else if (addr.mPreferred != bestAddr->mPreferred) |
| { |
| // Rule 3: Avoid deprecated addresses |
| newAddrIsPreferred = addr.mPreferred; |
| } |
| else if (matchLen > bestMatchLen) |
| { |
| // Rule 6: Prefer matching label |
| // Rule 7: Prefer public address |
| // Rule 8: Use longest prefix matching |
| |
| newAddrIsPreferred = true; |
| } |
| else if ((matchLen == bestMatchLen) && (destIsRloc == Get<Mle::Mle>().IsRoutingLocator(addr.GetAddress()))) |
| { |
| // Additional rule: Prefer RLOC source for RLOC destination, EID source for anything else |
| newAddrIsPreferred = true; |
| } |
| |
| if (newAddrIsPreferred) |
| { |
| bestAddr = &addr; |
| bestMatchLen = matchLen; |
| |
| // Infer destination scope based on prefix match |
| if (bestMatchLen >= bestAddr->mPrefixLength) |
| { |
| destScope = bestAddr->GetScope(); |
| } |
| } |
| } |
| |
| exit: |
| return (bestAddr != nullptr) ? &bestAddr->GetAddress() : nullptr; |
| } |
| |
| bool Ip6::IsOnLink(const Address &aAddress) const |
| { |
| bool isOnLink = false; |
| |
| if (Get<NetworkData::Leader>().IsOnMesh(aAddress)) |
| { |
| ExitNow(isOnLink = true); |
| } |
| |
| for (const Netif::UnicastAddress &unicastAddr : Get<ThreadNetif>().GetUnicastAddresses()) |
| { |
| if (unicastAddr.GetAddress().PrefixMatch(aAddress) >= unicastAddr.mPrefixLength) |
| { |
| ExitNow(isOnLink = true); |
| } |
| } |
| |
| exit: |
| return isOnLink; |
| } |
| |
| #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE |
| |
| void Ip6::UpdateBorderRoutingCounters(const Header &aHeader, uint16_t aMessageLength, bool aIsInbound) |
| { |
| static constexpr uint8_t kPrefixLength = 48; |
| |
| otPacketsAndBytes *counter = nullptr; |
| otPacketsAndBytes *internetCounter = nullptr; |
| |
| VerifyOrExit(!aHeader.GetSource().IsLinkLocalUnicast()); |
| VerifyOrExit(!aHeader.GetDestination().IsLinkLocalUnicast()); |
| VerifyOrExit(!Get<Mle::Mle>().IsMeshLocalAddress(aHeader.GetSource())); |
| VerifyOrExit(!Get<Mle::Mle>().IsMeshLocalAddress(aHeader.GetDestination())); |
| |
| if (aIsInbound) |
| { |
| VerifyOrExit(!Get<Netif>().HasUnicastAddress(aHeader.GetSource())); |
| |
| if (!aHeader.GetSource().MatchesPrefix(aHeader.GetDestination().GetPrefix().m8, kPrefixLength)) |
| { |
| internetCounter = &mBrCounters.mInboundInternet; |
| } |
| |
| if (aHeader.GetDestination().IsMulticast()) |
| { |
| VerifyOrExit(aHeader.GetDestination().IsMulticastLargerThanRealmLocal()); |
| counter = &mBrCounters.mInboundMulticast; |
| } |
| else |
| { |
| counter = &mBrCounters.mInboundUnicast; |
| } |
| } |
| else |
| { |
| VerifyOrExit(!Get<Netif>().HasUnicastAddress(aHeader.GetDestination())); |
| |
| if (!aHeader.GetSource().MatchesPrefix(aHeader.GetDestination().GetPrefix().m8, kPrefixLength)) |
| { |
| internetCounter = &mBrCounters.mOutboundInternet; |
| } |
| |
| if (aHeader.GetDestination().IsMulticast()) |
| { |
| VerifyOrExit(aHeader.GetDestination().IsMulticastLargerThanRealmLocal()); |
| counter = &mBrCounters.mOutboundMulticast; |
| } |
| else |
| { |
| counter = &mBrCounters.mOutboundUnicast; |
| } |
| } |
| |
| exit: |
| |
| if (counter) |
| { |
| counter->mPackets++; |
| counter->mBytes += aMessageLength; |
| } |
| if (internetCounter) |
| { |
| internetCounter->mPackets++; |
| internetCounter->mBytes += aMessageLength; |
| } |
| } |
| |
| #endif // OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE |
| |
| // LCOV_EXCL_START |
| |
| const char *Ip6::IpProtoToString(uint8_t aIpProto) |
| { |
| static constexpr Stringify::Entry kIpProtoTable[] = { |
| {kProtoHopOpts, "HopOpts"}, {kProtoTcp, "TCP"}, {kProtoUdp, "UDP"}, |
| {kProtoIp6, "IP6"}, {kProtoRouting, "Routing"}, {kProtoFragment, "Frag"}, |
| {kProtoIcmp6, "ICMP6"}, {kProtoNone, "None"}, {kProtoDstOpts, "DstOpts"}, |
| }; |
| |
| static_assert(Stringify::IsSorted(kIpProtoTable), "kIpProtoTable is not sorted"); |
| |
| return Stringify::Lookup(aIpProto, kIpProtoTable, "Unknown"); |
| } |
| |
| const char *Ip6::EcnToString(Ecn aEcn) |
| { |
| #define EcnMapList(_) \ |
| _(kEcnNotCapable, "no") \ |
| _(kEcnCapable1, "e1") /* ECT1*/ \ |
| _(kEcnCapable0, "e0") /* ECT0 */ \ |
| _(kEcnMarked, "ce") /* Congestion Encountered */ |
| |
| DefineEnumStringArray(EcnMapList); |
| |
| return kStrings[aEcn]; |
| } |
| |
| // LCOV_EXCL_STOP |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // Headers |
| |
| Error Headers::ParseFrom(const Message &aMessage) |
| { |
| Error error = kErrorParse; |
| |
| Clear(); |
| |
| SuccessOrExit(mIp6Header.ParseFrom(aMessage)); |
| |
| switch (mIp6Header.GetNextHeader()) |
| { |
| case kProtoUdp: |
| SuccessOrExit(aMessage.Read(sizeof(Header), mHeader.mUdp)); |
| break; |
| case kProtoTcp: |
| SuccessOrExit(aMessage.Read(sizeof(Header), mHeader.mTcp)); |
| break; |
| case kProtoIcmp6: |
| SuccessOrExit(aMessage.Read(sizeof(Header), mHeader.mIcmp)); |
| break; |
| default: |
| break; |
| } |
| |
| error = kErrorNone; |
| |
| exit: |
| return error; |
| } |
| |
| Error Headers::DecompressFrom(const Message &aMessage, uint16_t aOffset, const Mac::Addresses &aMacAddrs) |
| { |
| static constexpr uint16_t kReadLength = sizeof(Lowpan::FragmentHeader::NextFrag) + sizeof(Headers); |
| |
| uint8_t frameBuffer[kReadLength]; |
| uint16_t frameLength; |
| FrameData frameData; |
| |
| frameLength = aMessage.ReadBytes(aOffset, frameBuffer, sizeof(frameBuffer)); |
| frameData.Init(frameBuffer, frameLength); |
| |
| return DecompressFrom(frameData, aMacAddrs, aMessage.GetInstance()); |
| } |
| |
| Error Headers::DecompressFrom(const FrameData &aFrameData, const Mac::Addresses &aMacAddrs, Instance &aInstance) |
| { |
| Error error = kErrorNone; |
| FrameData frameData = aFrameData; |
| Lowpan::FragmentHeader fragmentHeader; |
| bool nextHeaderCompressed; |
| |
| if (fragmentHeader.ParseFrom(frameData) == kErrorNone) |
| { |
| // Only the first fragment header is followed by a LOWPAN_IPHC header |
| VerifyOrExit(fragmentHeader.GetDatagramOffset() == 0, error = kErrorNotFound); |
| } |
| |
| VerifyOrExit(Lowpan::Lowpan::IsLowpanHc(frameData), error = kErrorNotFound); |
| |
| SuccessOrExit(error = aInstance.Get<Lowpan::Lowpan>().DecompressBaseHeader(mIp6Header, nextHeaderCompressed, |
| aMacAddrs, frameData)); |
| |
| switch (mIp6Header.GetNextHeader()) |
| { |
| case kProtoUdp: |
| if (nextHeaderCompressed) |
| { |
| SuccessOrExit(error = aInstance.Get<Lowpan::Lowpan>().DecompressUdpHeader(mHeader.mUdp, frameData)); |
| } |
| else |
| { |
| SuccessOrExit(error = frameData.Read(mHeader.mUdp)); |
| } |
| break; |
| |
| case kProtoTcp: |
| SuccessOrExit(error = frameData.Read(mHeader.mTcp)); |
| break; |
| |
| case kProtoIcmp6: |
| SuccessOrExit(error = frameData.Read(mHeader.mIcmp)); |
| break; |
| |
| default: |
| break; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| uint16_t Headers::GetSourcePort(void) const |
| { |
| uint16_t port = 0; |
| |
| switch (GetIpProto()) |
| { |
| case kProtoUdp: |
| port = mHeader.mUdp.GetSourcePort(); |
| break; |
| |
| case kProtoTcp: |
| port = mHeader.mTcp.GetSourcePort(); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return port; |
| } |
| |
| void Headers::SetSourcePort(uint16_t aSrcPort) |
| { |
| switch (GetIpProto()) |
| { |
| case kProtoUdp: |
| mHeader.mUdp.SetSourcePort(aSrcPort); |
| break; |
| |
| case kProtoTcp: |
| mHeader.mTcp.SetSourcePort(aSrcPort); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| uint16_t Headers::GetDestinationPort(void) const |
| { |
| uint16_t port = 0; |
| |
| switch (GetIpProto()) |
| { |
| case kProtoUdp: |
| port = mHeader.mUdp.GetDestinationPort(); |
| break; |
| |
| case kProtoTcp: |
| port = mHeader.mTcp.GetDestinationPort(); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return port; |
| } |
| |
| uint16_t Headers::GetChecksum(void) const |
| { |
| uint16_t checksum = 0; |
| |
| switch (GetIpProto()) |
| { |
| case kProtoUdp: |
| checksum = mHeader.mUdp.GetChecksum(); |
| break; |
| |
| case kProtoTcp: |
| checksum = mHeader.mTcp.GetChecksum(); |
| break; |
| |
| case kProtoIcmp6: |
| checksum = mHeader.mIcmp.GetChecksum(); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return checksum; |
| } |
| |
| } // namespace Ip6 |
| } // namespace ot |