1500 lines
45 KiB
C++
1500 lines
45 KiB
C++
/*
|
|
* 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 "backbone_router/bbr_leader.hpp"
|
|
#include "backbone_router/bbr_local.hpp"
|
|
#include "backbone_router/ndproxy_table.hpp"
|
|
#include "common/code_utils.hpp"
|
|
#include "common/debug.hpp"
|
|
#include "common/instance.hpp"
|
|
#include "common/locator_getters.hpp"
|
|
#include "common/log.hpp"
|
|
#include "common/message.hpp"
|
|
#include "common/random.hpp"
|
|
#include "net/checksum.hpp"
|
|
#include "net/icmp6.hpp"
|
|
#include "net/ip6_address.hpp"
|
|
#include "net/ip6_filter.hpp"
|
|
#include "net/netif.hpp"
|
|
#include "net/udp6.hpp"
|
|
#include "openthread/ip6.h"
|
|
#include "thread/mle.hpp"
|
|
|
|
using IcmpType = ot::Ip6::Icmp::Header::Type;
|
|
|
|
static const IcmpType sForwardICMPTypes[] = {
|
|
IcmpType::kTypeDstUnreach, IcmpType::kTypePacketToBig, IcmpType::kTypeTimeExceeded,
|
|
IcmpType::kTypeParameterProblem, IcmpType::kTypeEchoRequest, IcmpType::kTypeEchoReply,
|
|
};
|
|
|
|
namespace ot {
|
|
namespace Ip6 {
|
|
|
|
RegisterLogModule("Ip6");
|
|
|
|
Ip6::Ip6(Instance &aInstance)
|
|
: InstanceLocator(aInstance)
|
|
, mForwardingEnabled(false)
|
|
, mIsReceiveIp6FilterEnabled(false)
|
|
, mReceiveIp6DatagramCallback(nullptr)
|
|
, mReceiveIp6DatagramCallbackContext(nullptr)
|
|
, mSendQueueTask(aInstance, Ip6::HandleSendQueue)
|
|
, mIcmp(aInstance)
|
|
, mUdp(aInstance)
|
|
, mMpl(aInstance)
|
|
#if OPENTHREAD_CONFIG_TCP_ENABLE
|
|
, mTcp(aInstance)
|
|
#endif
|
|
{
|
|
}
|
|
|
|
Message *Ip6::NewMessage(uint16_t aReserved, const Message::Settings &aSettings)
|
|
{
|
|
return Get<MessagePool>().Allocate(
|
|
Message::kTypeIp6, sizeof(Header) + sizeof(HopByHopHeader) + sizeof(OptionMpl) + aReserved, aSettings);
|
|
}
|
|
|
|
Message *Ip6::NewMessage(const uint8_t *aData, uint16_t aDataLength, const Message::Settings &aSettings)
|
|
{
|
|
Message *message = Get<MessagePool>().Allocate(Message::kTypeIp6, /* aReserveHeader */ 0, aSettings);
|
|
|
|
VerifyOrExit(message != nullptr);
|
|
|
|
if (message->AppendBytes(aData, aDataLength) != kErrorNone)
|
|
{
|
|
message->Free();
|
|
message = nullptr;
|
|
}
|
|
|
|
exit:
|
|
return message;
|
|
}
|
|
|
|
Message *Ip6::NewMessage(const uint8_t *aData, uint16_t aDataLength)
|
|
{
|
|
Message * message = nullptr;
|
|
Message::Priority priority;
|
|
|
|
SuccessOrExit(GetDatagramPriority(aData, aDataLength, priority));
|
|
message = NewMessage(aData, aDataLength, Message::Settings(Message::kWithLinkSecurity, priority));
|
|
|
|
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::GetDatagramPriority(const uint8_t *aData, uint16_t aDataLen, Message::Priority &aPriority)
|
|
{
|
|
Error error = kErrorNone;
|
|
const Header *header;
|
|
|
|
VerifyOrExit((aData != nullptr) && (aDataLen >= sizeof(Header)), error = kErrorInvalidArgs);
|
|
|
|
header = reinterpret_cast<const Header *>(aData);
|
|
VerifyOrExit(header->IsValid(), error = kErrorParse);
|
|
VerifyOrExit(sizeof(Header) + header->GetPayloadLength() == aDataLen, error = kErrorParse);
|
|
|
|
aPriority = DscpToPriority(header->GetDscp());
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
void Ip6::SetReceiveDatagramCallback(otIp6ReceiveCallback aCallback, void *aCallbackContext)
|
|
{
|
|
mReceiveIp6DatagramCallback = aCallback;
|
|
mReceiveIp6DatagramCallbackContext = aCallbackContext;
|
|
}
|
|
|
|
Error Ip6::AddMplOption(Message &aMessage, Header &aHeader)
|
|
{
|
|
Error error = kErrorNone;
|
|
HopByHopHeader hbhHeader;
|
|
OptionMpl mplOption;
|
|
OptionPadN padOption;
|
|
|
|
hbhHeader.SetNextHeader(aHeader.GetNextHeader());
|
|
hbhHeader.SetLength(0);
|
|
mMpl.InitOption(mplOption, aHeader.GetSource());
|
|
|
|
// Mpl option may require two bytes padding.
|
|
if ((mplOption.GetTotalLength() + sizeof(hbhHeader)) % 8)
|
|
{
|
|
padOption.Init(2);
|
|
SuccessOrExit(error = aMessage.PrependBytes(&padOption, padOption.GetTotalLength()));
|
|
}
|
|
|
|
SuccessOrExit(error = aMessage.PrependBytes(&mplOption, mplOption.GetTotalLength()));
|
|
SuccessOrExit(error = aMessage.Prepend(hbhHeader));
|
|
aHeader.SetPayloadLength(aHeader.GetPayloadLength() + sizeof(hbhHeader) + sizeof(mplOption));
|
|
aHeader.SetNextHeader(kProtoHopOpts);
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
Error Ip6::AddTunneledMplOption(Message &aMessage, Header &aHeader, MessageInfo &aMessageInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
Header tunnelHeader;
|
|
const Netif::UnicastAddress *source;
|
|
MessageInfo messageInfo(aMessageInfo);
|
|
|
|
// Use IP-in-IP encapsulation (RFC2473) and ALL_MPL_FORWARDERS address.
|
|
messageInfo.GetPeerAddr().SetToRealmLocalAllMplForwarders();
|
|
|
|
tunnelHeader.InitVersionTrafficClassFlow();
|
|
tunnelHeader.SetHopLimit(static_cast<uint8_t>(kDefaultHopLimit));
|
|
tunnelHeader.SetPayloadLength(aHeader.GetPayloadLength() + sizeof(tunnelHeader));
|
|
tunnelHeader.SetDestination(messageInfo.GetPeerAddr());
|
|
tunnelHeader.SetNextHeader(kProtoIp6);
|
|
|
|
VerifyOrExit((source = SelectSourceAddress(messageInfo)) != nullptr, error = kErrorInvalidSourceAddress);
|
|
|
|
tunnelHeader.SetSource(source->GetAddress());
|
|
|
|
SuccessOrExit(error = AddMplOption(aMessage, tunnelHeader));
|
|
SuccessOrExit(error = aMessage.Prepend(tunnelHeader));
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
Error Ip6::InsertMplOption(Message &aMessage, Header &aHeader, MessageInfo &aMessageInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
|
|
VerifyOrExit(aHeader.GetDestination().IsMulticast() &&
|
|
aHeader.GetDestination().GetScope() >= Address::kRealmLocalScope);
|
|
|
|
if (aHeader.GetDestination().IsRealmLocalMulticast())
|
|
{
|
|
aMessage.RemoveHeader(sizeof(aHeader));
|
|
|
|
if (aHeader.GetNextHeader() == kProtoHopOpts)
|
|
{
|
|
HopByHopHeader hbh;
|
|
uint16_t hbhLength = 0;
|
|
OptionMpl mplOption;
|
|
|
|
// read existing hop-by-hop option header
|
|
SuccessOrExit(error = aMessage.Read(0, hbh));
|
|
hbhLength = (hbh.GetLength() + 1) * 8;
|
|
|
|
VerifyOrExit(hbhLength <= aHeader.GetPayloadLength(), error = kErrorParse);
|
|
|
|
// increase existing hop-by-hop option header length by 8 bytes
|
|
hbh.SetLength(hbh.GetLength() + 1);
|
|
aMessage.Write(0, hbh);
|
|
|
|
// make space for MPL Option + padding by shifting hop-by-hop option header
|
|
SuccessOrExit(error = aMessage.PrependBytes(nullptr, 8));
|
|
aMessage.CopyTo(8, 0, hbhLength, aMessage);
|
|
|
|
// insert MPL Option
|
|
mMpl.InitOption(mplOption, aHeader.GetSource());
|
|
aMessage.WriteBytes(hbhLength, &mplOption, mplOption.GetTotalLength());
|
|
|
|
// insert Pad Option (if needed)
|
|
if (mplOption.GetTotalLength() % 8)
|
|
{
|
|
OptionPadN padOption;
|
|
padOption.Init(8 - (mplOption.GetTotalLength() % 8));
|
|
aMessage.WriteBytes(hbhLength + mplOption.GetTotalLength(), &padOption, padOption.GetTotalLength());
|
|
}
|
|
|
|
// increase IPv6 Payload Length
|
|
aHeader.SetPayloadLength(aHeader.GetPayloadLength() + 8);
|
|
}
|
|
else
|
|
{
|
|
SuccessOrExit(error = AddMplOption(aMessage, aHeader));
|
|
}
|
|
|
|
SuccessOrExit(error = aMessage.Prepend(aHeader));
|
|
}
|
|
else
|
|
{
|
|
#if OPENTHREAD_FTD
|
|
if (aHeader.GetDestination().IsMulticastLargerThanRealmLocal() &&
|
|
Get<ChildTable>().HasSleepyChildWithAddress(aHeader.GetDestination()))
|
|
{
|
|
Message *messageCopy = nullptr;
|
|
|
|
if ((messageCopy = aMessage.Clone()) != nullptr)
|
|
{
|
|
IgnoreError(HandleDatagram(*messageCopy, nullptr, nullptr, /* aFromHost */ true));
|
|
LogInfo("Message copy for indirect transmission to sleepy children");
|
|
}
|
|
else
|
|
{
|
|
LogWarn("No enough buffer for message copy for indirect transmission to sleepy children");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SuccessOrExit(error = AddTunneledMplOption(aMessage, aHeader, aMessageInfo));
|
|
}
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
Error Ip6::RemoveMplOption(Message &aMessage)
|
|
{
|
|
Error error = kErrorNone;
|
|
Header ip6Header;
|
|
HopByHopHeader hbh;
|
|
uint16_t offset;
|
|
uint16_t endOffset;
|
|
uint16_t mplOffset = 0;
|
|
uint8_t mplLength = 0;
|
|
bool remove = false;
|
|
|
|
offset = 0;
|
|
IgnoreError(aMessage.Read(offset, ip6Header));
|
|
offset += sizeof(ip6Header);
|
|
VerifyOrExit(ip6Header.GetNextHeader() == kProtoHopOpts);
|
|
|
|
IgnoreError(aMessage.Read(offset, hbh));
|
|
endOffset = offset + (hbh.GetLength() + 1) * 8;
|
|
VerifyOrExit(aMessage.GetLength() >= endOffset, error = kErrorParse);
|
|
|
|
offset += sizeof(hbh);
|
|
|
|
while (offset < endOffset)
|
|
{
|
|
OptionHeader option;
|
|
|
|
IgnoreError(aMessage.Read(offset, option));
|
|
|
|
switch (option.GetType())
|
|
{
|
|
case OptionMpl::kType:
|
|
// if multiple MPL options exist, discard packet
|
|
VerifyOrExit(mplOffset == 0, error = kErrorParse);
|
|
|
|
mplOffset = offset;
|
|
mplLength = option.GetLength();
|
|
|
|
VerifyOrExit(mplLength <= sizeof(OptionMpl) - sizeof(OptionHeader), error = kErrorParse);
|
|
|
|
if (mplOffset == sizeof(ip6Header) + sizeof(hbh) && hbh.GetLength() == 0)
|
|
{
|
|
// first and only IPv6 Option, remove IPv6 HBH Option header
|
|
remove = true;
|
|
}
|
|
else if (mplOffset + 8 == endOffset)
|
|
{
|
|
// last IPv6 Option, remove last 8 bytes
|
|
remove = true;
|
|
}
|
|
|
|
offset += sizeof(option) + option.GetLength();
|
|
break;
|
|
|
|
case OptionPad1::kType:
|
|
offset += sizeof(OptionPad1);
|
|
break;
|
|
|
|
case OptionPadN::kType:
|
|
offset += sizeof(option) + option.GetLength();
|
|
break;
|
|
|
|
default:
|
|
// encountered another option, now just replace MPL Option with PadN
|
|
remove = false;
|
|
offset += sizeof(option) + option.GetLength();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// verify that IPv6 Options header is properly formed
|
|
VerifyOrExit(offset == endOffset, error = kErrorParse);
|
|
|
|
if (remove)
|
|
{
|
|
// last IPv6 Option, shrink HBH Option header
|
|
uint8_t buf[8];
|
|
|
|
offset = endOffset - sizeof(buf);
|
|
|
|
while (offset >= sizeof(buf))
|
|
{
|
|
IgnoreError(aMessage.Read(offset - sizeof(buf), buf));
|
|
aMessage.Write(offset, buf);
|
|
offset -= sizeof(buf);
|
|
}
|
|
|
|
aMessage.RemoveHeader(sizeof(buf));
|
|
|
|
if (mplOffset == sizeof(ip6Header) + sizeof(hbh))
|
|
{
|
|
// remove entire HBH header
|
|
ip6Header.SetNextHeader(hbh.GetNextHeader());
|
|
}
|
|
else
|
|
{
|
|
// update HBH header length
|
|
hbh.SetLength(hbh.GetLength() - 1);
|
|
aMessage.Write(sizeof(ip6Header), hbh);
|
|
}
|
|
|
|
ip6Header.SetPayloadLength(ip6Header.GetPayloadLength() - sizeof(buf));
|
|
aMessage.Write(0, ip6Header);
|
|
}
|
|
else if (mplOffset != 0)
|
|
{
|
|
// replace MPL Option with PadN Option
|
|
OptionPadN padOption;
|
|
|
|
padOption.Init(sizeof(OptionHeader) + mplLength);
|
|
aMessage.WriteBytes(mplOffset, &padOption, padOption.GetTotalLength());
|
|
}
|
|
|
|
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;
|
|
uint16_t payloadLength = aMessage.GetLength();
|
|
|
|
header.InitVersionTrafficClassFlow();
|
|
header.SetDscp(PriorityToDscp(aMessage.GetPriority()));
|
|
header.SetEcn(aMessageInfo.GetEcn());
|
|
header.SetPayloadLength(payloadLength);
|
|
header.SetNextHeader(aIpProto);
|
|
|
|
if (aMessageInfo.GetHopLimit() != 0 || aMessageInfo.ShouldAllowZeroHopLimit())
|
|
{
|
|
header.SetHopLimit(aMessageInfo.GetHopLimit());
|
|
}
|
|
else
|
|
{
|
|
header.SetHopLimit(static_cast<uint8_t>(kDefaultHopLimit));
|
|
}
|
|
|
|
if (aMessageInfo.GetSockAddr().IsUnspecified() || aMessageInfo.GetSockAddr().IsMulticast())
|
|
{
|
|
const Netif::UnicastAddress *source = SelectSourceAddress(aMessageInfo);
|
|
|
|
VerifyOrExit(source != nullptr, error = kErrorInvalidSourceAddress);
|
|
header.SetSource(source->GetAddress());
|
|
}
|
|
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())
|
|
{
|
|
#if OPENTHREAD_FTD
|
|
if (Get<ChildTable>().HasSleepyChildWithAddress(header.GetDestination()))
|
|
{
|
|
Message *messageCopy = aMessage.Clone();
|
|
|
|
if (messageCopy != nullptr)
|
|
{
|
|
LogInfo("Message copy for indirect transmission to sleepy children");
|
|
EnqueueDatagram(*messageCopy);
|
|
}
|
|
else
|
|
{
|
|
LogWarn("No enough buffer for message copy for indirect transmission to sleepy children");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SuccessOrExit(error = AddTunneledMplOption(aMessage, header, aMessageInfo));
|
|
}
|
|
|
|
aMessage.SetMulticastLoop(aMessageInfo.GetMulticastLoop());
|
|
|
|
if (aMessage.GetLength() > kMaxDatagramLength)
|
|
{
|
|
error = FragmentDatagram(aMessage, aIpProto);
|
|
}
|
|
else
|
|
{
|
|
EnqueueDatagram(aMessage);
|
|
}
|
|
|
|
exit:
|
|
|
|
return error;
|
|
}
|
|
|
|
void Ip6::HandleSendQueue(Tasklet &aTasklet)
|
|
{
|
|
aTasklet.Get<Ip6>().HandleSendQueue();
|
|
}
|
|
|
|
void Ip6::HandleSendQueue(void)
|
|
{
|
|
Message *message;
|
|
|
|
while ((message = mSendQueue.GetHead()) != nullptr)
|
|
{
|
|
mSendQueue.Dequeue(*message);
|
|
IgnoreError(HandleDatagram(*message, nullptr, nullptr, /* aFromHost */ false));
|
|
}
|
|
}
|
|
|
|
Error Ip6::HandleOptions(Message &aMessage, Header &aHeader, bool aIsOutbound, bool &aReceive)
|
|
{
|
|
Error error = kErrorNone;
|
|
HopByHopHeader hbhHeader;
|
|
OptionHeader optionHeader;
|
|
uint16_t endOffset;
|
|
|
|
SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), hbhHeader));
|
|
endOffset = aMessage.GetOffset() + (hbhHeader.GetLength() + 1) * 8;
|
|
|
|
VerifyOrExit(endOffset <= aMessage.GetLength(), error = kErrorParse);
|
|
|
|
aMessage.MoveOffset(sizeof(optionHeader));
|
|
|
|
while (aMessage.GetOffset() < endOffset)
|
|
{
|
|
SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), optionHeader));
|
|
|
|
if (optionHeader.GetType() == OptionPad1::kType)
|
|
{
|
|
aMessage.MoveOffset(sizeof(OptionPad1));
|
|
continue;
|
|
}
|
|
|
|
VerifyOrExit(aMessage.GetOffset() + sizeof(optionHeader) + optionHeader.GetLength() <= endOffset,
|
|
error = kErrorParse);
|
|
|
|
switch (optionHeader.GetType())
|
|
{
|
|
case OptionMpl::kType:
|
|
SuccessOrExit(error = mMpl.ProcessOption(aMessage, aHeader.GetSource(), aIsOutbound, aReceive));
|
|
break;
|
|
|
|
default:
|
|
switch (optionHeader.GetAction())
|
|
{
|
|
case OptionHeader::kActionSkip:
|
|
break;
|
|
|
|
case OptionHeader::kActionDiscard:
|
|
ExitNow(error = kErrorDrop);
|
|
|
|
case OptionHeader::kActionForceIcmp:
|
|
// TODO: send icmp error
|
|
ExitNow(error = kErrorDrop);
|
|
|
|
case OptionHeader::kActionIcmp:
|
|
// TODO: send icmp error
|
|
ExitNow(error = kErrorDrop);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
aMessage.MoveOffset(sizeof(optionHeader) + optionHeader.GetLength());
|
|
}
|
|
|
|
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.GetLength() - aMessage.GetOffset();
|
|
|
|
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(0)) != 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);
|
|
|
|
VerifyOrExit(aMessage.CopyTo(aMessage.GetOffset() + FragmentHeader::FragmentOffsetToBytes(offset),
|
|
aMessage.GetOffset() + sizeof(fragmentHeader), payloadFragment,
|
|
*fragment) == static_cast<int>(payloadFragment),
|
|
error = kErrorNoBufs);
|
|
|
|
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, Netif *aNetif, MessageInfo &aMessageInfo, bool aFromHost)
|
|
{
|
|
Error error = kErrorNone;
|
|
Header header, headerBuffer;
|
|
FragmentHeader fragmentHeader;
|
|
Message * message = nullptr;
|
|
uint16_t offset = 0;
|
|
uint16_t payloadFragment = 0;
|
|
int assertValue = 0;
|
|
bool isFragmented = true;
|
|
|
|
OT_UNUSED_VARIABLE(assertValue);
|
|
|
|
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 %d received > %d bytes, offset %d", 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(0)) != nullptr, error = kErrorNoBufs);
|
|
mReassemblyList.Enqueue(*message);
|
|
SuccessOrExit(error = message->SetLength(aMessage.GetOffset()));
|
|
|
|
message->SetTimestampToNow();
|
|
message->SetOffset(0);
|
|
message->SetDatagramTag(fragmentHeader.GetIdentification());
|
|
|
|
// copying the non-fragmentable header to the fragmentation buffer
|
|
assertValue = aMessage.CopyTo(0, 0, aMessage.GetOffset(), *message);
|
|
OT_ASSERT(assertValue == 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
|
|
assertValue = aMessage.CopyTo(aMessage.GetOffset() + sizeof(fragmentHeader), aMessage.GetOffset() + offset,
|
|
payloadFragment, *message);
|
|
OT_ASSERT(assertValue == static_cast<int>(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(*message, aNetif, aMessageInfo.mLinkInfo, aFromHost));
|
|
}
|
|
|
|
exit:
|
|
if (error != kErrorDrop && error != kErrorNone && isFragmented)
|
|
{
|
|
if (message != nullptr)
|
|
{
|
|
mReassemblyList.DequeueAndFree(*message);
|
|
}
|
|
|
|
LogWarn("Reassembly failed: %s", ErrorToString(error));
|
|
}
|
|
|
|
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(kIp6ReassemblyTimeout))
|
|
{
|
|
LogNote("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());
|
|
messageInfo.SetLinkInfo(nullptr);
|
|
|
|
error = mIcmp.SendError(aIcmpType, aIcmpCode, messageInfo, aMessage);
|
|
|
|
exit:
|
|
|
|
if (error != kErrorNone)
|
|
{
|
|
LogWarn("Failed to send ICMP error: %s", ErrorToString(error));
|
|
}
|
|
}
|
|
|
|
#else
|
|
Error Ip6::FragmentDatagram(Message &aMessage, uint8_t aIpProto)
|
|
{
|
|
OT_UNUSED_VARIABLE(aIpProto);
|
|
|
|
EnqueueDatagram(aMessage);
|
|
|
|
return kErrorNone;
|
|
}
|
|
|
|
Error Ip6::HandleFragment(Message &aMessage, Netif *aNetif, MessageInfo &aMessageInfo, bool aFromHost)
|
|
{
|
|
OT_UNUSED_VARIABLE(aNetif);
|
|
OT_UNUSED_VARIABLE(aMessageInfo);
|
|
OT_UNUSED_VARIABLE(aFromHost);
|
|
|
|
Error error = kErrorNone;
|
|
FragmentHeader fragmentHeader;
|
|
|
|
SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), fragmentHeader));
|
|
|
|
VerifyOrExit(fragmentHeader.GetOffset() == 0 && !fragmentHeader.IsMoreFlagSet(), error = kErrorDrop);
|
|
|
|
aMessage.MoveOffset(sizeof(fragmentHeader));
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
#endif // OPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE
|
|
|
|
Error Ip6::HandleExtensionHeaders(Message & aMessage,
|
|
Netif * aNetif,
|
|
MessageInfo &aMessageInfo,
|
|
Header & aHeader,
|
|
uint8_t & aNextHeader,
|
|
bool aIsOutbound,
|
|
bool aFromHost,
|
|
bool & aReceive)
|
|
{
|
|
Error error = kErrorNone;
|
|
ExtensionHeader extHeader;
|
|
|
|
while (aReceive || aNextHeader == kProtoHopOpts)
|
|
{
|
|
SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), extHeader));
|
|
|
|
switch (aNextHeader)
|
|
{
|
|
case kProtoHopOpts:
|
|
SuccessOrExit(error = HandleOptions(aMessage, aHeader, aIsOutbound, aReceive));
|
|
break;
|
|
|
|
case kProtoFragment:
|
|
#if !OPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE
|
|
IgnoreError(ProcessReceiveCallback(aMessage, aMessageInfo, aNextHeader, aFromHost,
|
|
/* aAllowReceiveFilter */ false, Message::kCopyToUse));
|
|
#endif
|
|
SuccessOrExit(error = HandleFragment(aMessage, aNetif, aMessageInfo, aFromHost));
|
|
break;
|
|
|
|
case kProtoDstOpts:
|
|
SuccessOrExit(error = HandleOptions(aMessage, aHeader, aIsOutbound, aReceive));
|
|
break;
|
|
|
|
case kProtoIp6:
|
|
ExitNow();
|
|
|
|
case kProtoRouting:
|
|
case kProtoNone:
|
|
ExitNow(error = kErrorDrop);
|
|
|
|
default:
|
|
ExitNow();
|
|
}
|
|
|
|
aNextHeader = static_cast<uint8_t>(extHeader.GetNextHeader());
|
|
}
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
Error Ip6::HandlePayload(Header & aIp6Header,
|
|
Message & aMessage,
|
|
MessageInfo & aMessageInfo,
|
|
uint8_t aIpProto,
|
|
Message::Ownership aMessageOwnership)
|
|
{
|
|
#if !OPENTHREAD_CONFIG_TCP_ENABLE
|
|
OT_UNUSED_VARIABLE(aIp6Header);
|
|
#endif
|
|
|
|
Error error = kErrorNone;
|
|
Message *message = (aMessageOwnership == Message::kTakeCustody) ? &aMessage : nullptr;
|
|
|
|
VerifyOrExit(aIpProto == kProtoTcp || aIpProto == kProtoUdp || aIpProto == kProtoIcmp6);
|
|
|
|
if (aMessageOwnership == Message::kCopyToUse)
|
|
{
|
|
VerifyOrExit((message = aMessage.Clone()) != nullptr, error = kErrorNoBufs);
|
|
}
|
|
|
|
switch (aIpProto)
|
|
{
|
|
#if OPENTHREAD_CONFIG_TCP_ENABLE
|
|
case kProtoTcp:
|
|
error = mTcp.HandleMessage(aIp6Header, *message, aMessageInfo);
|
|
if (error == kErrorDrop)
|
|
{
|
|
LogNote("Error TCP Checksum");
|
|
}
|
|
break;
|
|
#endif
|
|
case kProtoUdp:
|
|
error = mUdp.HandleMessage(*message, aMessageInfo);
|
|
if (error == kErrorDrop)
|
|
{
|
|
LogNote("Error UDP Checksum");
|
|
}
|
|
break;
|
|
|
|
case kProtoIcmp6:
|
|
error = mIcmp.HandleMessage(*message, aMessageInfo);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
exit:
|
|
if (error != kErrorNone)
|
|
{
|
|
LogNote("Failed to handle payload: %s", ErrorToString(error));
|
|
}
|
|
|
|
FreeMessage(message);
|
|
|
|
return error;
|
|
}
|
|
|
|
Error Ip6::ProcessReceiveCallback(Message & aMessage,
|
|
const MessageInfo &aMessageInfo,
|
|
uint8_t aIpProto,
|
|
bool aFromHost,
|
|
bool aAllowReceiveFilter,
|
|
Message::Ownership aMessageOwnership)
|
|
{
|
|
Error error = kErrorNone;
|
|
Message *message = &aMessage;
|
|
|
|
VerifyOrExit(!aFromHost, error = kErrorNoRoute);
|
|
VerifyOrExit(mReceiveIp6DatagramCallback != nullptr, error = kErrorNoRoute);
|
|
|
|
// Do not forward reassembled IPv6 packets.
|
|
VerifyOrExit(aMessage.GetLength() <= kMinimalMtu, error = kErrorDrop);
|
|
|
|
if (mIsReceiveIp6FilterEnabled && aAllowReceiveFilter)
|
|
{
|
|
#if !OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
|
|
// do not pass messages sent to an RLOC/ALOC, except Service Locator
|
|
bool isLocator = Get<Mle::Mle>().IsMeshLocalAddress(aMessageInfo.GetSockAddr()) &&
|
|
aMessageInfo.GetSockAddr().GetIid().IsLocator();
|
|
|
|
VerifyOrExit(!isLocator || aMessageInfo.GetSockAddr().GetIid().IsAnycastServiceLocator(),
|
|
error = kErrorNoRoute);
|
|
#endif
|
|
|
|
switch (aIpProto)
|
|
{
|
|
case kProtoIcmp6:
|
|
if (mIcmp.ShouldHandleEchoRequest(aMessageInfo))
|
|
{
|
|
Icmp::Header icmp;
|
|
IgnoreError(aMessage.Read(aMessage.GetOffset(), icmp));
|
|
|
|
// do not pass ICMP Echo Request messages
|
|
VerifyOrExit(icmp.GetType() != Icmp::Header::kTypeEchoRequest, error = kErrorDrop);
|
|
}
|
|
|
|
break;
|
|
|
|
case kProtoUdp:
|
|
{
|
|
Udp::Header udp;
|
|
|
|
IgnoreError(aMessage.Read(aMessage.GetOffset(), udp));
|
|
VerifyOrExit(Get<Udp>().ShouldUsePlatformUdp(udp.GetDestinationPort()) &&
|
|
!Get<Udp>().IsPortInUse(udp.GetDestinationPort()),
|
|
error = kErrorNoRoute);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (aMessageOwnership)
|
|
{
|
|
case Message::kTakeCustody:
|
|
break;
|
|
|
|
case Message::kCopyToUse:
|
|
message = aMessage.Clone();
|
|
|
|
if (message == nullptr)
|
|
{
|
|
LogWarn("No buff to clone msg (len: %d) to pass to host", aMessage.GetLength());
|
|
ExitNow(error = kErrorNoBufs);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
IgnoreError(RemoveMplOption(*message));
|
|
mReceiveIp6DatagramCallback(message, mReceiveIp6DatagramCallbackContext);
|
|
|
|
exit:
|
|
|
|
if ((error != kErrorNone) && (aMessageOwnership == Message::kTakeCustody))
|
|
{
|
|
aMessage.Free();
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
Error Ip6::SendRaw(Message &aMessage, bool aFromHost)
|
|
{
|
|
Error error = kErrorNone;
|
|
Header header;
|
|
MessageInfo messageInfo;
|
|
bool freed = false;
|
|
|
|
SuccessOrExit(error = header.ParseFrom(aMessage));
|
|
VerifyOrExit(!header.GetSource().IsMulticast(), error = kErrorInvalidSourceAddress);
|
|
|
|
messageInfo.SetPeerAddr(header.GetSource());
|
|
messageInfo.SetSockAddr(header.GetDestination());
|
|
messageInfo.SetHopLimit(header.GetHopLimit());
|
|
|
|
if (header.GetDestination().IsMulticast())
|
|
{
|
|
SuccessOrExit(error = InsertMplOption(aMessage, header, messageInfo));
|
|
}
|
|
|
|
error = HandleDatagram(aMessage, nullptr, nullptr, aFromHost);
|
|
freed = true;
|
|
|
|
exit:
|
|
|
|
if (!freed)
|
|
{
|
|
aMessage.Free();
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
Error Ip6::HandleDatagram(Message &aMessage, Netif *aNetif, const void *aLinkMessageInfo, bool aFromHost)
|
|
{
|
|
Error error;
|
|
MessageInfo messageInfo;
|
|
Header header;
|
|
bool receive;
|
|
bool forwardThread;
|
|
bool forwardHost;
|
|
bool shouldFreeMessage;
|
|
uint8_t nextHeader;
|
|
|
|
start:
|
|
receive = false;
|
|
forwardThread = false;
|
|
forwardHost = false;
|
|
shouldFreeMessage = true;
|
|
|
|
SuccessOrExit(error = header.ParseFrom(aMessage));
|
|
|
|
messageInfo.Clear();
|
|
messageInfo.SetPeerAddr(header.GetSource());
|
|
messageInfo.SetSockAddr(header.GetDestination());
|
|
messageInfo.SetHopLimit(header.GetHopLimit());
|
|
messageInfo.SetEcn(header.GetEcn());
|
|
messageInfo.SetLinkInfo(aLinkMessageInfo);
|
|
|
|
// determine destination of packet
|
|
if (header.GetDestination().IsMulticast())
|
|
{
|
|
Netif *netif;
|
|
|
|
if (aNetif != nullptr)
|
|
{
|
|
#if OPENTHREAD_FTD
|
|
if (header.GetDestination().IsMulticastLargerThanRealmLocal() &&
|
|
Get<ChildTable>().HasSleepyChildWithAddress(header.GetDestination()))
|
|
{
|
|
forwardThread = true;
|
|
}
|
|
#endif
|
|
|
|
netif = aNetif;
|
|
}
|
|
else
|
|
{
|
|
forwardThread = true;
|
|
|
|
netif = &Get<ThreadNetif>();
|
|
}
|
|
|
|
forwardHost = header.GetDestination().IsMulticastLargerThanRealmLocal();
|
|
|
|
if ((aNetif != nullptr || aMessage.GetMulticastLoop()) && netif->IsMulticastSubscribed(header.GetDestination()))
|
|
{
|
|
receive = true;
|
|
}
|
|
else if (netif->IsMulticastPromiscuousEnabled())
|
|
{
|
|
forwardHost = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// unicast
|
|
if (Get<ThreadNetif>().HasUnicastAddress(header.GetDestination()))
|
|
{
|
|
receive = true;
|
|
}
|
|
else if (!header.GetDestination().IsLinkLocal())
|
|
{
|
|
forwardThread = true;
|
|
}
|
|
else if (aNetif == nullptr)
|
|
{
|
|
forwardThread = true;
|
|
}
|
|
|
|
if (forwardThread && !ShouldForwardToThread(messageInfo, aFromHost))
|
|
{
|
|
forwardThread = false;
|
|
forwardHost = true;
|
|
}
|
|
}
|
|
|
|
aMessage.SetOffset(sizeof(header));
|
|
|
|
// process IPv6 Extension Headers
|
|
nextHeader = static_cast<uint8_t>(header.GetNextHeader());
|
|
SuccessOrExit(error = HandleExtensionHeaders(aMessage, aNetif, messageInfo, header, nextHeader, aNetif == nullptr,
|
|
aFromHost, receive));
|
|
|
|
// process IPv6 Payload
|
|
if (receive)
|
|
{
|
|
if (nextHeader == kProtoIp6)
|
|
{
|
|
// Remove encapsulating header and start over.
|
|
aMessage.RemoveHeader(aMessage.GetOffset());
|
|
Get<MeshForwarder>().LogMessage(MeshForwarder::kMessageReceive, aMessage);
|
|
goto start;
|
|
}
|
|
|
|
error = ProcessReceiveCallback(aMessage, messageInfo, nextHeader, aFromHost,
|
|
/* aAllowReceiveFilter */ !forwardHost, Message::kCopyToUse);
|
|
|
|
if ((error == kErrorNone || error == kErrorNoRoute) && forwardHost)
|
|
{
|
|
forwardHost = false;
|
|
}
|
|
|
|
error = HandlePayload(header, aMessage, messageInfo, nextHeader,
|
|
(forwardThread || forwardHost ? Message::kCopyToUse : Message::kTakeCustody));
|
|
shouldFreeMessage = forwardThread || forwardHost;
|
|
}
|
|
|
|
if (forwardHost)
|
|
{
|
|
// try passing to host
|
|
error = ProcessReceiveCallback(aMessage, messageInfo, nextHeader, aFromHost, /* aAllowReceiveFilter */ false,
|
|
forwardThread ? Message::kCopyToUse : Message::kTakeCustody);
|
|
shouldFreeMessage = forwardThread;
|
|
}
|
|
|
|
if (forwardThread)
|
|
{
|
|
uint8_t hopLimit;
|
|
|
|
if (aNetif != nullptr)
|
|
{
|
|
VerifyOrExit(mForwardingEnabled);
|
|
header.SetHopLimit(header.GetHopLimit() - 1);
|
|
}
|
|
|
|
VerifyOrExit(header.GetHopLimit() > 0, error = kErrorDrop);
|
|
|
|
hopLimit = header.GetHopLimit();
|
|
aMessage.Write(Header::kHopLimitFieldOffset, hopLimit);
|
|
|
|
if (nextHeader == kProtoIcmp6)
|
|
{
|
|
uint8_t icmpType;
|
|
bool isAllowedType = false;
|
|
|
|
SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), icmpType));
|
|
for (IcmpType type : sForwardICMPTypes)
|
|
{
|
|
if (icmpType == type)
|
|
{
|
|
isAllowedType = true;
|
|
break;
|
|
}
|
|
}
|
|
VerifyOrExit(isAllowedType, error = kErrorDrop);
|
|
}
|
|
|
|
#if !OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
|
|
if (aFromHost && (nextHeader == kProtoUdp))
|
|
{
|
|
uint16_t destPort;
|
|
|
|
SuccessOrExit(error = aMessage.Read(aMessage.GetOffset() + Udp::Header::kDestPortFieldOffset, destPort));
|
|
destPort = HostSwap16(destPort);
|
|
|
|
if (nextHeader == kProtoUdp)
|
|
{
|
|
VerifyOrExit(Get<Udp>().ShouldUsePlatformUdp(destPort), error = kErrorDrop);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#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).
|
|
aMessage.ClearRadioType();
|
|
#endif
|
|
|
|
// `SendMessage()` takes custody of message in the success case
|
|
SuccessOrExit(error = Get<ThreadNetif>().SendMessage(aMessage));
|
|
shouldFreeMessage = false;
|
|
}
|
|
|
|
exit:
|
|
|
|
if (shouldFreeMessage)
|
|
{
|
|
aMessage.Free();
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
bool Ip6::ShouldForwardToThread(const MessageInfo &aMessageInfo, bool aFromHost) const
|
|
{
|
|
bool shouldForward = false;
|
|
|
|
if (aMessageInfo.GetSockAddr().IsMulticast() || aMessageInfo.GetSockAddr().IsLinkLocal())
|
|
{
|
|
shouldForward = true;
|
|
}
|
|
else if (IsOnLink(aMessageInfo.GetSockAddr()))
|
|
{
|
|
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE
|
|
shouldForward =
|
|
(aFromHost || !Get<BackboneRouter::Manager>().ShouldForwardDuaToBackbone(aMessageInfo.GetSockAddr()));
|
|
#else
|
|
OT_UNUSED_VARIABLE(aFromHost);
|
|
shouldForward = true;
|
|
#endif
|
|
}
|
|
else if (Get<ThreadNetif>().RouteLookup(aMessageInfo.GetPeerAddr(), aMessageInfo.GetSockAddr(), nullptr) ==
|
|
kErrorNone)
|
|
{
|
|
shouldForward = true;
|
|
}
|
|
|
|
return shouldForward;
|
|
}
|
|
|
|
const Netif::UnicastAddress *Ip6::SelectSourceAddress(MessageInfo &aMessageInfo)
|
|
{
|
|
Address * destination = &aMessageInfo.GetPeerAddr();
|
|
uint8_t destinationScope = destination->GetScope();
|
|
const bool destinationIsRoutingLocator = Get<Mle::Mle>().IsRoutingLocator(*destination);
|
|
const Netif::UnicastAddress *rvalAddr = nullptr;
|
|
uint8_t rvalPrefixMatched = 0;
|
|
|
|
for (const Netif::UnicastAddress &addr : Get<ThreadNetif>().GetUnicastAddresses())
|
|
{
|
|
const Address *candidateAddr = &addr.GetAddress();
|
|
uint8_t candidatePrefixMatched;
|
|
uint8_t overrideScope;
|
|
|
|
if (Get<Mle::Mle>().IsAnycastLocator(*candidateAddr))
|
|
{
|
|
// Don't use anycast address as source address.
|
|
continue;
|
|
}
|
|
|
|
candidatePrefixMatched = destination->PrefixMatch(*candidateAddr);
|
|
|
|
if (candidatePrefixMatched >= addr.mPrefixLength)
|
|
{
|
|
candidatePrefixMatched = addr.mPrefixLength;
|
|
overrideScope = addr.GetScope();
|
|
}
|
|
else
|
|
{
|
|
overrideScope = destinationScope;
|
|
}
|
|
|
|
if (rvalAddr == nullptr)
|
|
{
|
|
// Rule 0: Prefer any address
|
|
rvalAddr = &addr;
|
|
rvalPrefixMatched = candidatePrefixMatched;
|
|
}
|
|
else if (*candidateAddr == *destination)
|
|
{
|
|
// Rule 1: Prefer same address
|
|
rvalAddr = &addr;
|
|
ExitNow();
|
|
}
|
|
else if (addr.GetScope() < rvalAddr->GetScope())
|
|
{
|
|
// Rule 2: Prefer appropriate scope
|
|
if (addr.GetScope() >= overrideScope)
|
|
{
|
|
rvalAddr = &addr;
|
|
rvalPrefixMatched = candidatePrefixMatched;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else if (addr.GetScope() > rvalAddr->GetScope())
|
|
{
|
|
if (rvalAddr->GetScope() < overrideScope)
|
|
{
|
|
rvalAddr = &addr;
|
|
rvalPrefixMatched = candidatePrefixMatched;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else if (addr.mPreferred && !rvalAddr->mPreferred)
|
|
{
|
|
// Rule 3: Avoid deprecated addresses
|
|
rvalAddr = &addr;
|
|
rvalPrefixMatched = candidatePrefixMatched;
|
|
}
|
|
else if (candidatePrefixMatched > rvalPrefixMatched)
|
|
{
|
|
// Rule 6: Prefer matching label
|
|
// Rule 7: Prefer public address
|
|
// Rule 8: Use longest prefix matching
|
|
rvalAddr = &addr;
|
|
rvalPrefixMatched = candidatePrefixMatched;
|
|
}
|
|
else if ((candidatePrefixMatched == rvalPrefixMatched) &&
|
|
(destinationIsRoutingLocator == Get<Mle::Mle>().IsRoutingLocator(*candidateAddr)))
|
|
{
|
|
// Additional rule: Prefer RLOC source for RLOC destination, EID source for anything else
|
|
rvalAddr = &addr;
|
|
rvalPrefixMatched = candidatePrefixMatched;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// infer destination scope based on prefix match
|
|
if (rvalPrefixMatched >= rvalAddr->mPrefixLength)
|
|
{
|
|
destinationScope = rvalAddr->GetScope();
|
|
}
|
|
}
|
|
|
|
exit:
|
|
return rvalAddr;
|
|
}
|
|
|
|
bool Ip6::IsOnLink(const Address &aAddress) const
|
|
{
|
|
bool rval = false;
|
|
|
|
if (Get<ThreadNetif>().IsOnMesh(aAddress))
|
|
{
|
|
ExitNow(rval = true);
|
|
}
|
|
|
|
for (const Netif::UnicastAddress &cur : Get<ThreadNetif>().GetUnicastAddresses())
|
|
{
|
|
if (cur.GetAddress().PrefixMatch(aAddress) >= cur.mPrefixLength)
|
|
{
|
|
ExitNow(rval = true);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
return rval;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
static const char *const kEcnStrings[] = {
|
|
"no", // (0) kEcnNotCapable
|
|
"e1", // (1) kEcnCapable1 (ECT1)
|
|
"e0", // (2) kEcnCapable0 (ECT0)
|
|
"ce", // (3) kEcnMarked (Congestion Encountered)
|
|
};
|
|
|
|
static_assert(0 == kEcnNotCapable, "kEcnNotCapable value is incorrect");
|
|
static_assert(1 == kEcnCapable1, "kEcnCapable1 value is incorrect");
|
|
static_assert(2 == kEcnCapable0, "kEcnCapable0 value is incorrect");
|
|
static_assert(3 == kEcnMarked, "kEcnMarked value is incorrect");
|
|
|
|
return kEcnStrings[aEcn];
|
|
}
|
|
|
|
// LCOV_EXCL_STOP
|
|
|
|
} // namespace Ip6
|
|
} // namespace ot
|