blob: cabd8a2aa5117915d6aad67992b6cc5cfe0c1789 [file]
/*
* Copyright (c) 2025, 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.
*/
#include "nexus_infra_if.hpp"
#include "nexus_core.hpp"
#include "nexus_node.hpp"
namespace ot {
namespace Nexus {
InfraIf::InfraIf(void)
: mNode(nullptr)
, mNodeId(0)
, mIfIndex(0)
{
}
void InfraIf::Init(Node &aNode)
{
Ip6::Address address;
LinkLayerAddress mac;
Ip6::InterfaceIdentifier iid;
mIfIndex = 1;
mNode = &aNode;
mNodeId = aNode.GetId();
GetLinkLayerAddress(mac);
iid.mFields.m8[0] = mac.mAddress[0] ^ 0x02;
iid.mFields.m8[1] = mac.mAddress[1];
iid.mFields.m8[2] = mac.mAddress[2];
iid.mFields.m8[3] = 0xff;
iid.mFields.m8[4] = 0xfe;
iid.mFields.m8[5] = mac.mAddress[3];
iid.mFields.m8[6] = mac.mAddress[4];
iid.mFields.m8[7] = mac.mAddress[5];
address.SetToLinkLocalAddress(iid);
AddAddress(address);
}
bool InfraIf::HasAddress(const Ip6::Address &aAddress) const { return mAddresses.Contains(aAddress); }
void InfraIf::AddAddress(const Ip6::Address &aAddress)
{
VerifyOrExit(!HasAddress(aAddress));
SuccessOrQuit(mAddresses.PushBack(aAddress));
mNode->mMdns.HandleHostAddressEvent(aAddress, /* aAdded */ true);
exit:
return;
}
void InfraIf::RemoveAddress(const Ip6::Address &aAddress)
{
for (uint16_t index = 0; index < mAddresses.GetLength(); index++)
{
if (mAddresses[index] == aAddress)
{
mAddresses[index] = *mAddresses.Back();
mAddresses.PopBack();
mNode->mMdns.HandleHostAddressEvent(aAddress, /* aAdded */ false);
break;
}
}
}
void InfraIf::RemoveAllAddresses(void)
{
mAddresses.Clear();
mNode->mMdns.HandleHostAddressRemoveAll();
}
const Ip6::Address *InfraIf::FindAddress(const char *aPrefix) const
{
Ip6::Prefix prefix;
const Ip6::Address *matchedAddress = nullptr;
SuccessOrQuit(prefix.FromString(aPrefix));
for (const Ip6::Address &address : mAddresses)
{
if (address.MatchesPrefix(prefix))
{
matchedAddress = &address;
break;
}
}
return matchedAddress;
}
const Ip6::Address &InfraIf::FindMatchingAddress(const char *aPrefix) const
{
const Ip6::Address *matchedAddress = FindAddress(aPrefix);
VerifyOrQuit(matchedAddress != nullptr, "no matching address found on infrastructure interface");
return *matchedAddress;
}
void InfraIf::SendIcmp6Nd(const Ip6::Address &aDestAddress, const uint8_t *aBuffer, uint16_t aBufferLength)
{
Message *message = GetNode().Get<MessagePool>().Allocate(Message::kTypeIp6);
Ip6::Header ip6Header;
VerifyOrQuit(message != nullptr);
ip6Header.InitVersionTrafficClassFlow();
ip6Header.SetPayloadLength(aBufferLength);
ip6Header.SetNextHeader(Ip6::kProtoIcmp6);
ip6Header.SetHopLimit(255);
ip6Header.SetSource(GetLinkLocalAddress());
ip6Header.SetDestination(aDestAddress);
SuccessOrQuit(message->Append(ip6Header));
SuccessOrQuit(message->AppendBytes(aBuffer, aBufferLength));
// Calculate ICMPv6 checksum
message->SetOffset(sizeof(Ip6::Header));
Checksum::UpdateMessageChecksum(*message, ip6Header.GetSource(), ip6Header.GetDestination(), Ip6::kProtoIcmp6);
mPendingTxQueue.Enqueue(*message);
}
void InfraIf::ProcessIcmp6Nd(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength)
{
Ip6::Nd::Icmp6Packet packet;
OT_UNUSED_VARIABLE(aSrcAddress);
packet.Init(aBuffer, aBufferLength);
VerifyOrExit(packet.GetLength() >= sizeof(Ip6::Icmp::Header));
switch (reinterpret_cast<const Ip6::Icmp::Header *>(packet.GetBytes())->GetType())
{
case Ip6::Icmp::Header::kTypeRouterAdvert:
{
Ip6::Nd::RouterAdvert::RxMessage raMessage(packet);
VerifyOrExit(raMessage.IsValid());
for (const Ip6::Nd::Option &option : raMessage)
{
if (option.GetType() == Ip6::Nd::Option::kTypePrefixInfo)
{
HandlePrefixInfoOption(static_cast<const Ip6::Nd::PrefixInfoOption &>(option));
}
}
break;
}
case Ip6::Icmp::Header::kTypeRouterSolicit:
case Ip6::Icmp::Header::kTypeNeighborAdvert:
case Ip6::Icmp::Header::kTypeNeighborSolicit:
// TODO: Handle other ND messages as needed for the simulation.
break;
default:
break;
}
exit:
return;
}
void InfraIf::HandlePrefixInfoOption(const Ip6::Nd::PrefixInfoOption &aPio)
{
Ip6::Prefix prefix;
Ip6::Address address;
LinkLayerAddress mac;
VerifyOrExit(aPio.IsAutoAddrConfigFlagSet());
aPio.GetPrefix(prefix);
// Generate address using SLAAC (EUI-64 style for simplicity in test)
address = AsCoreType(&prefix.mPrefix);
GetLinkLayerAddress(mac);
address.mFields.m8[8] = mac.mAddress[0] ^ 0x02;
address.mFields.m8[9] = mac.mAddress[1];
address.mFields.m8[10] = mac.mAddress[2];
address.mFields.m8[11] = 0xff;
address.mFields.m8[12] = 0xfe;
address.mFields.m8[13] = mac.mAddress[3];
address.mFields.m8[14] = mac.mAddress[4];
address.mFields.m8[15] = mac.mAddress[5];
if (aPio.GetValidLifetime() == 0)
{
if (HasAddress(address))
{
RemoveAddress(address);
Log("Node %lu (%s) removed address %s from RA (lifetime 0)", ToUlong(GetNode().GetId()),
GetNode().GetName(), address.ToString().AsCString());
}
ExitNow();
}
AddAddress(address);
Log("Node %lu (%s) auto-configured address %s from RA", ToUlong(GetNode().GetId()), GetNode().GetName(),
address.ToString().AsCString());
exit:
return;
}
void InfraIf::SendIp6(const Ip6::Address &aSrcAddress,
const Ip6::Address &aDestAddress,
const uint8_t *aBuffer,
uint16_t aBufferLength)
{
Message *message = GetNode().Get<MessagePool>().Allocate(Message::kTypeIp6);
VerifyOrQuit(message != nullptr);
Log("InfraIf::SendIp6 from %s to %s (len:%u)", aSrcAddress.ToString().AsCString(),
aDestAddress.ToString().AsCString(), aBufferLength);
SuccessOrQuit(message->AppendBytes(aBuffer, aBufferLength));
mPendingTxQueue.Enqueue(*message);
}
void InfraIf::SendEchoRequest(const Ip6::Address &aSrcAddress,
const Ip6::Address &aDestAddress,
uint16_t aIdentifier,
uint16_t aPayloadSize,
uint8_t aHopLimit)
{
Message *message;
Ip6::Header ip6Header;
Ip6::Icmp::Header icmpHeader;
message = GetNode().Get<Ip6::Ip6>().NewMessage();
VerifyOrQuit(message != nullptr);
ip6Header.Clear();
ip6Header.InitVersionTrafficClassFlow();
ip6Header.SetPayloadLength(sizeof(Ip6::Icmp::Header) + aPayloadSize);
ip6Header.SetNextHeader(Ip6::kProtoIcmp6);
ip6Header.SetHopLimit(aHopLimit);
ip6Header.SetSource(aSrcAddress);
ip6Header.SetDestination(aDestAddress);
icmpHeader.Clear();
icmpHeader.SetType(Ip6::Icmp::Header::kTypeEchoRequest);
icmpHeader.SetCode(static_cast<Ip6::Icmp::Header::Code>(0));
icmpHeader.SetId(aIdentifier);
icmpHeader.SetSequence(0);
SuccessOrQuit(message->Append(icmpHeader));
SuccessOrQuit(message->IncreaseLength(aPayloadSize));
Checksum::UpdateMessageChecksum(*message, aSrcAddress, aDestAddress, Ip6::kProtoIcmp6);
SuccessOrQuit(message->Prepend(ip6Header));
mPendingTxQueue.Enqueue(*message);
}
void InfraIf::SendUdp(const Ip6::Address &aSrcAddress,
const Ip6::Address &aDestAddress,
uint16_t aSourcePort,
uint16_t aDestPort,
uint16_t aPayloadSize)
{
Message *message = GetNode().Get<Ip6::Ip6>().NewMessage();
VerifyOrQuit(message != nullptr);
SuccessOrQuit(message->IncreaseLength(aPayloadSize));
SendUdp(aSrcAddress, aDestAddress, aSourcePort, aDestPort, *message);
}
void InfraIf::SendUdp(const Ip6::Address &aSrcAddress,
const Ip6::Address &aDestAddress,
uint16_t aSourcePort,
uint16_t aDestPort,
Message &aPayload)
{
Ip6::Header ip6Header;
Ip6::Udp::Header udpHeader;
ip6Header.Clear();
ip6Header.InitVersionTrafficClassFlow();
ip6Header.SetPayloadLength(sizeof(Ip6::Udp::Header) + aPayload.GetLength());
ip6Header.SetNextHeader(Ip6::kProtoUdp);
ip6Header.SetHopLimit(64);
ip6Header.SetSource(aSrcAddress);
ip6Header.SetDestination(aDestAddress);
udpHeader.SetSourcePort(aSourcePort);
udpHeader.SetDestinationPort(aDestPort);
udpHeader.SetLength(sizeof(Ip6::Udp::Header) + aPayload.GetLength());
udpHeader.SetChecksum(0);
SuccessOrQuit(aPayload.Prepend(udpHeader));
aPayload.SetOffset(0);
Checksum::UpdateMessageChecksum(aPayload, aSrcAddress, aDestAddress, Ip6::kProtoUdp);
SuccessOrQuit(aPayload.Prepend(ip6Header));
aPayload.SetOffset(0);
if (aDestAddress.IsMulticast())
{
Message *loopbackMessage = aPayload.Clone<kNoReservedHeader>();
VerifyOrQuit(loopbackMessage != nullptr);
Receive(GetNode(), *loopbackMessage);
loopbackMessage->Free();
}
mPendingTxQueue.Enqueue(aPayload);
}
void InfraIf::Receive(Node &aSrcNode, Message &aMessage)
{
Node &node = GetNode();
Ip6::Headers headers;
Core::Get().SetActiveNode(&node);
aMessage.SetOffset(0);
SuccessOrExit(headers.ParseFrom(aMessage));
if (headers.IsIcmp6() && (headers.GetDestinationAddress() == Ip6::Address::GetLinkLocalAllNodesMulticast() ||
headers.GetDestinationAddress() == Ip6::Address::GetLinkLocalAllRoutersMulticast() ||
node.mInfraIf.HasAddress(headers.GetDestinationAddress())))
{
switch (headers.GetIcmpHeader().GetType())
{
case Ip6::Icmp::Header::kTypeRouterAdvert:
case Ip6::Icmp::Header::kTypeRouterSolicit:
case Ip6::Icmp::Header::kTypeNeighborAdvert:
case Ip6::Icmp::Header::kTypeNeighborSolicit:
{
Heap::Data payload;
uint16_t offset = sizeof(Ip6::Header);
SuccessOrQuit(payload.SetFrom(aMessage, offset, aMessage.GetLength() - offset));
otPlatInfraIfRecvIcmp6Nd(&node.GetInstance(), mIfIndex,
reinterpret_cast<const otIp6Address *>(&headers.GetSourceAddress()),
payload.GetBytes(), payload.GetLength());
node.mInfraIf.ProcessIcmp6Nd(headers.GetSourceAddress(), payload.GetBytes(), payload.GetLength());
ExitNow();
}
case Ip6::Icmp::Header::kTypeEchoRequest:
HandleEchoRequest(headers.GetIp6Header(), aMessage);
ExitNow();
case Ip6::Icmp::Header::kTypeEchoReply:
HandleEchoReply(headers.GetIp6Header(), aMessage);
ExitNow();
default:
break;
}
}
if (headers.IsUdp() && headers.GetDestinationPort() == Mdns::kUdpPort)
{
if (headers.GetDestinationAddress().IsMulticast() || node.mInfraIf.HasAddress(headers.GetDestinationAddress()))
{
Mdns::AddressInfo senderAddress;
Message *payload = aMessage.Clone<kNoReservedHeader>();
VerifyOrQuit(payload != nullptr);
payload->RemoveHeader(sizeof(Ip6::Header) + sizeof(Ip6::Udp::Header));
senderAddress.mAddress = headers.GetSourceAddress();
senderAddress.mPort = headers.GetSourcePort();
senderAddress.mInfraIfIndex = Mdns::kInfraIfIndex;
node.mMdns.Receive(node.GetInstance(), *payload, !headers.GetDestinationAddress().IsMulticast(),
senderAddress);
payload->Free();
}
ExitNow();
}
{
// We also deliver generic IPv6 packets to the stack if they are NOT ICMPv6 ND packets.
// (ND packets were already delivered via otPlatInfraIfRecvIcmp6Nd above).
OwnedPtr<Message> messagePtr;
Ip6::Header updatedHeader = headers.GetIp6Header();
VerifyOrExit(!node.mInfraIf.HasAddress(headers.GetSourceAddress()));
VerifyOrExit(!node.Get<NetworkData::Leader>().IsOnMesh(headers.GetSourceAddress()));
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
if (headers.GetDestinationAddress().IsMulticastLargerThanRealmLocal())
{
VerifyOrExit(node.Get<BackboneRouter::Local>().IsPrimary());
VerifyOrExit(node.Get<BackboneRouter::MulticastListenersTable>().Has(headers.GetDestinationAddress()));
}
#endif
VerifyOrExit(updatedHeader.GetHopLimit() > 1);
updatedHeader.SetHopLimit(updatedHeader.GetHopLimit() - 1);
messagePtr.Reset(aMessage.Clone<kNoReservedHeader>());
VerifyOrQuit(messagePtr != nullptr);
messagePtr->Write(0, updatedHeader);
messagePtr->SetOrigin(Message::kOriginHostUntrusted);
messagePtr->SetLoopbackToHostAllowed(false);
IgnoreError(node.Get<Ip6::Ip6>().SendRaw(messagePtr.PassOwnership()));
}
exit:
Core::Get().SetActiveNode(&aSrcNode);
}
void InfraIf::HandleEchoRequest(const Ip6::Header &aHeader, Message &aMessage)
{
Node &node = GetNode();
Message *replyMessage;
Ip6::Header replyHeader;
Ip6::Icmp::Header replyIcmp;
uint16_t payloadLen = aMessage.GetLength() - sizeof(Ip6::Header);
replyMessage = node.Get<MessagePool>().Allocate(Message::kTypeIp6);
VerifyOrQuit(replyMessage != nullptr);
SuccessOrQuit(replyMessage->SetLength(payloadLen));
replyMessage->WriteBytesFromMessage(0, aMessage, sizeof(Ip6::Header), payloadLen);
SuccessOrQuit(replyMessage->Read(0, replyIcmp));
replyHeader.InitVersionTrafficClassFlow();
replyHeader.SetPayloadLength(payloadLen);
replyHeader.SetNextHeader(Ip6::kProtoIcmp6);
replyHeader.SetHopLimit(64);
replyHeader.SetSource(aHeader.GetDestination());
replyHeader.SetDestination(aHeader.GetSource());
replyIcmp.SetType(Ip6::Icmp::Header::kTypeEchoReply);
replyMessage->Write(0, replyIcmp);
// Recalculate ICMPv6 checksum
replyMessage->SetOffset(0);
Checksum::UpdateMessageChecksum(*replyMessage, replyHeader.GetSource(), replyHeader.GetDestination(),
Ip6::kProtoIcmp6);
SuccessOrQuit(replyMessage->Prepend(replyHeader));
mPendingTxQueue.Enqueue(*replyMessage);
}
void InfraIf::HandleEchoReply(const Ip6::Header &aHeader, Message &aMessage)
{
Ip6::Icmp::Header icmpHeader;
SuccessOrQuit(aMessage.Read(sizeof(Ip6::Header), icmpHeader));
mEchoReplyCallback.InvokeIfSet(aHeader.GetSource(), icmpHeader.GetId(), icmpHeader.GetSequence());
}
void InfraIf::GetLinkLayerAddress(LinkLayerAddress &aLinkLayerAddress) const
{
// Use a unique MAC address based on Node ID
ClearAllBytes(aLinkLayerAddress);
aLinkLayerAddress.mLength = 6;
aLinkLayerAddress.mAddress[0] = 0x02;
aLinkLayerAddress.mAddress[5] = static_cast<uint8_t>(mNodeId);
}
Node &InfraIf::GetNode(void)
{
OT_ASSERT(mNode != nullptr);
return *mNode;
}
const Node &InfraIf::GetNode(void) const
{
OT_ASSERT(mNode != nullptr);
return *mNode;
}
extern "C" {
bool otPlatInfraIfHasAddress(otInstance *aInstance, uint32_t aInfraIfIndex, const otIp6Address *aAddress)
{
OT_UNUSED_VARIABLE(aInfraIfIndex);
return AsNode(aInstance).mInfraIf.HasAddress(AsCoreType(aAddress));
}
otError otPlatInfraIfSendIcmp6Nd(otInstance *aInstance,
uint32_t aInfraIfIndex,
const otIp6Address *aDestAddress,
const uint8_t *aBuffer,
uint16_t aBufferLength)
{
OT_UNUSED_VARIABLE(aInfraIfIndex);
AsNode(aInstance).mInfraIf.SendIcmp6Nd(AsCoreType(aDestAddress), aBuffer, aBufferLength);
return OT_ERROR_NONE;
}
otError otPlatInfraIfDiscoverNat64Prefix(otInstance *aInstance, uint32_t aInfraIfIndex)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aInfraIfIndex);
return OT_ERROR_NOT_IMPLEMENTED;
}
otError otPlatGetInfraIfLinkLayerAddress(otInstance *aInstance,
uint32_t aInfraIfIndex,
otPlatInfraIfLinkLayerAddress *aLinkLayerAddress)
{
OT_UNUSED_VARIABLE(aInfraIfIndex);
AsNode(aInstance).mInfraIf.GetLinkLayerAddress(*aLinkLayerAddress);
return OT_ERROR_NONE;
}
} // extern "C"
} // namespace Nexus
} // namespace ot