mirror of
https://github.com/espressif/openthread.git
synced 2026-10-05 07:17:39 +00:00
[ip6] resolve extension headers before forward-path protocol checks (#13412)
On the forward path, extension headers are intentionally left unprocessed, so the forwarded-ICMPv6 type allowlist and the untrusted-origin TMF port filter matched on the first Next Header value rather than the final upper-layer protocol. A Destination Options (or atomic Fragment) header inserted before the upper-layer header caused both checks to be skipped. Add a `ResolveUpperLayerProtocol()` helper that walks the extension header chain (shared with `HasIp6InIpTunnel()`), failing closed on malformed or overlong chains, and apply the forward-path checks to the resolved protocol. A continuation (non-first) fragment does not contain the upper-layer header and resolves to `kProtoFragment`; it is forwarded as-is, since it cannot complete reassembly anywhere unless its first fragment also passed through this filter. Add a nexus test covering the forward-path checks with extension header chains. Co-authored-by: aussinfosec <[email protected]>
This commit is contained in:
co-authored by
aussinfosec
parent
b035ecbc07
commit
83e79fe9bd
+58
-20
@@ -854,37 +854,50 @@ exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
bool Ip6::HasIp6InIpTunnel(const Message &aMessage, uint8_t aNextHeader) const
|
||||
Error Ip6::ResolveUpperLayerProtocol(const Message &aMessage, uint8_t &aNextHeader, OffsetRange &aOffsetRange) const
|
||||
{
|
||||
bool hasTunnel = false;
|
||||
OffsetRange offsetRange;
|
||||
// Walks the extension header chain starting at the message offset,
|
||||
// updating `aNextHeader` in place to the final upper-layer
|
||||
// protocol carried in `aMessage`, with `aOffsetRange` starting at
|
||||
// the corresponding upper-layer header.
|
||||
//
|
||||
// A continuation (non-first) fragment does not contain the
|
||||
// upper-layer header; in this case `aNextHeader` is left as
|
||||
// `kProtoFragment`.
|
||||
//
|
||||
// Returns `kErrorParse` if the chain is malformed (an extension
|
||||
// header does not fit within the message) or cannot be resolved
|
||||
// within `kMaxExtHeaderChain` headers.
|
||||
|
||||
offsetRange.InitFromMessageOffsetToEnd(aMessage);
|
||||
static constexpr uint8_t kMaxExtHeaderChain = 8;
|
||||
|
||||
while (!offsetRange.IsEmpty())
|
||||
Error error = kErrorNone;
|
||||
|
||||
aOffsetRange.InitFromMessageOffsetToEnd(aMessage);
|
||||
|
||||
for (uint8_t numHeaders = 0;; numHeaders++)
|
||||
{
|
||||
if (aNextHeader == kProtoIp6)
|
||||
{
|
||||
hasTunnel = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (aNextHeader == kProtoHopOpts || aNextHeader == kProtoDstOpts || aNextHeader == kProtoRouting)
|
||||
if ((aNextHeader == kProtoHopOpts) || (aNextHeader == kProtoDstOpts) || (aNextHeader == kProtoRouting))
|
||||
{
|
||||
ExtensionHeader extHeader;
|
||||
uint16_t size;
|
||||
|
||||
SuccessOrExit(aMessage.Read(offsetRange, extHeader));
|
||||
size = extHeader.GetSize();
|
||||
VerifyOrExit(offsetRange.Contains(size));
|
||||
offsetRange.AdvanceOffset(size);
|
||||
VerifyOrExit(numHeaders < kMaxExtHeaderChain, error = kErrorParse);
|
||||
SuccessOrExit(error = aMessage.Read(aOffsetRange, extHeader));
|
||||
VerifyOrExit(aOffsetRange.Contains(extHeader.GetSize()), error = kErrorParse);
|
||||
aOffsetRange.AdvanceOffset(extHeader.GetSize());
|
||||
aNextHeader = extHeader.GetNextHeader();
|
||||
}
|
||||
else if (aNextHeader == kProtoFragment)
|
||||
{
|
||||
FragmentHeader fragHeader;
|
||||
|
||||
SuccessOrExit(aMessage.ReadAndAdvance(offsetRange, fragHeader));
|
||||
VerifyOrExit(numHeaders < kMaxExtHeaderChain, error = kErrorParse);
|
||||
SuccessOrExit(error = aMessage.ReadAndAdvance(aOffsetRange, fragHeader));
|
||||
|
||||
// A continuation fragment does not contain the upper-layer
|
||||
// header; leave `aNextHeader` as `kProtoFragment`.
|
||||
VerifyOrExit(fragHeader.GetOffset() == 0);
|
||||
|
||||
aNextHeader = fragHeader.GetNextHeader();
|
||||
}
|
||||
else
|
||||
@@ -893,6 +906,23 @@ bool Ip6::HasIp6InIpTunnel(const Message &aMessage, uint8_t aNextHeader) const
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
bool Ip6::HasIp6InIpTunnel(const Message &aMessage, uint8_t aNextHeader) const
|
||||
{
|
||||
// Fails closed: a chain that cannot be resolved is treated the
|
||||
// same as a tunnel, since it cannot be verified not to contain
|
||||
// one. This filter is only applied to host-untrusted messages
|
||||
// (see `SendRaw()`).
|
||||
|
||||
bool hasTunnel = true;
|
||||
OffsetRange offsetRange;
|
||||
|
||||
SuccessOrExit(ResolveUpperLayerProtocol(aMessage, aNextHeader, offsetRange));
|
||||
hasTunnel = (aNextHeader == kProtoIp6);
|
||||
|
||||
exit:
|
||||
return hasTunnel;
|
||||
}
|
||||
@@ -1337,6 +1367,8 @@ Error Ip6::HandleDatagram(OwnedPtr<Message> aMessagePtr, bool aIsReassembled, ui
|
||||
|
||||
if (forwardThread)
|
||||
{
|
||||
OffsetRange upperOffsetRange;
|
||||
|
||||
if (aMessagePtr->IsOriginThreadNetif())
|
||||
{
|
||||
VerifyOrExit(Get<Mle::Mle>().IsRouterOrLeader());
|
||||
@@ -1347,11 +1379,17 @@ Error Ip6::HandleDatagram(OwnedPtr<Message> aMessagePtr, bool aIsReassembled, ui
|
||||
|
||||
aMessagePtr->Write<uint8_t>(Header::kHopLimitFieldOffset, header.GetHopLimit());
|
||||
|
||||
// Resolve any extension headers left unprocessed on the
|
||||
// forward path so the checks below apply to the final
|
||||
// upper-layer protocol.
|
||||
|
||||
SuccessOrExit(error = ResolveUpperLayerProtocol(*aMessagePtr, nextHeader, upperOffsetRange));
|
||||
|
||||
if (nextHeader == kProtoIcmp6)
|
||||
{
|
||||
uint8_t icmpType;
|
||||
|
||||
SuccessOrExit(error = aMessagePtr->Read(aMessagePtr->GetOffset(), icmpType));
|
||||
SuccessOrExit(error = aMessagePtr->Read(upperOffsetRange, icmpType));
|
||||
|
||||
VerifyOrExit(DoesArrayContain(kForwardIcmpTypes, icmpType), error = kErrorDrop);
|
||||
}
|
||||
@@ -1363,7 +1401,7 @@ Error Ip6::HandleDatagram(OwnedPtr<Message> aMessagePtr, bool aIsReassembled, ui
|
||||
{
|
||||
UdpHeader udpHeader;
|
||||
|
||||
SuccessOrExit(error = aMessagePtr->Read(aMessagePtr->GetOffset(), udpHeader));
|
||||
SuccessOrExit(error = aMessagePtr->Read(upperOffsetRange, udpHeader));
|
||||
|
||||
if (udpHeader.GetDestinationPort() == Tmf::kUdpPort)
|
||||
{
|
||||
|
||||
@@ -353,6 +353,7 @@ private:
|
||||
const Header &aHeader,
|
||||
uint8_t &aNextHeader,
|
||||
bool &aReceive);
|
||||
Error ResolveUpperLayerProtocol(const Message &aMessage, uint8_t &aNextHeader, OffsetRange &aOffsetRange) const;
|
||||
bool HasIp6InIpTunnel(const Message &aMessage, uint8_t aNextHeader) const;
|
||||
Error FragmentDatagram(Message &aMessage, uint8_t aIpProto);
|
||||
Error HandleFragment(Message &aMessage);
|
||||
|
||||
@@ -418,6 +418,7 @@ ot_nexus_test(fed_rx_only_link_establishment "core;nexus")
|
||||
ot_nexus_test(form_join "core;nexus")
|
||||
ot_nexus_test(history_tracker "core;nexus")
|
||||
ot_nexus_test(inform_previous_parent_on_reattach "core;nexus")
|
||||
ot_nexus_test(ipv6_forward_ext_headers "core;nexus")
|
||||
ot_nexus_test(ipv6_fragmentation "core;nexus")
|
||||
ot_nexus_test(ipv6_recursion "core;nexus")
|
||||
ot_nexus_test(ipv6_source_selection "core;nexus")
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Copyright (c) 2026, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Verifies that the forward-path protocol checks in `Ip6::HandleDatagram()` (the
|
||||
* forwarded-ICMPv6 type allowlist and the untrusted-origin TMF port filter) are applied
|
||||
* to the final upper-layer protocol after resolving any IPv6 extension headers, rather
|
||||
* than to the first Next Header value of the IPv6 header.
|
||||
*
|
||||
* Each case constructs a raw IPv6 datagram with a host-untrusted origin and a link-local
|
||||
* destination that is not on this node, so that `Ip6::SendRaw()` routes it through the
|
||||
* forward-to-Thread path of `Ip6::HandleDatagram()`, and asserts on the returned error:
|
||||
*
|
||||
* case 1 (control): UDP to the TMF port with no extension header is dropped;
|
||||
* case 2: the same UDP datagram with a Destination Options header preceding the UDP
|
||||
* header is also dropped;
|
||||
* case 3: the same UDP datagram with an atomic Fragment header (offset 0) preceding
|
||||
* the UDP header is also dropped;
|
||||
* case 4: ICMPv6 Echo Request with a Destination Options header is still matched by
|
||||
* the forwarded-ICMPv6 type allowlist and forwarded;
|
||||
* case 5: ICMPv6 Neighbor Solicitation (not in the allowlist) with a Destination
|
||||
* Options header is dropped;
|
||||
* case 6: a chain of extension headers longer than the resolution limit is dropped
|
||||
* (the resolution fails closed);
|
||||
* case 7: a continuation fragment (offset != 0), which does not contain the
|
||||
* upper-layer header, is forwarded as-is;
|
||||
* case 8: UDP to a non-TMF port with no extension header is forwarded;
|
||||
* case 9: UDP to a non-TMF port with a Destination Options header is forwarded;
|
||||
* case 10: TCP with no extension header is forwarded.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "platform/nexus_core.hpp"
|
||||
#include "platform/nexus_node.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace Nexus {
|
||||
|
||||
static constexpr uint16_t kNonTmfPort = 12345;
|
||||
static constexpr uint16_t kSourcePort = 1234;
|
||||
static constexpr uint8_t kHopLimit = 64;
|
||||
static constexpr uint8_t kLongChainLen = 9; // Longer than the resolution limit (8).
|
||||
|
||||
static Node *sNode;
|
||||
static Ip6::Address sSource;
|
||||
static Ip6::Address sDestination;
|
||||
|
||||
static Message &NewForwardedMessage(uint8_t aNextHeader, uint16_t aPayloadLength)
|
||||
{
|
||||
// Allocates a new IPv6 message and appends an IPv6 header with a
|
||||
// link-local destination that is not on this node, so that the
|
||||
// message takes the forward-to-Thread path in `HandleDatagram()`.
|
||||
|
||||
Message *message = sNode->Get<MessagePool>().Allocate(Message::kTypeIp6);
|
||||
Ip6::Header header;
|
||||
|
||||
VerifyOrQuit(message != nullptr);
|
||||
|
||||
header.Clear();
|
||||
header.InitVersionTrafficClassFlow();
|
||||
header.SetSource(sSource);
|
||||
header.SetDestination(sDestination);
|
||||
header.SetNextHeader(aNextHeader);
|
||||
header.SetPayloadLength(aPayloadLength);
|
||||
header.SetHopLimit(kHopLimit);
|
||||
|
||||
SuccessOrQuit(message->Append(header));
|
||||
|
||||
return *message;
|
||||
}
|
||||
|
||||
static void AppendDstOptsHeader(Message &aMessage, uint8_t aNextHeader)
|
||||
{
|
||||
// Appends a minimal (8-byte) Destination Options header carrying a
|
||||
// single PadN option.
|
||||
|
||||
Ip6::ExtensionHeader extHeader;
|
||||
Ip6::PadOption padOption;
|
||||
|
||||
extHeader.SetNextHeader(aNextHeader);
|
||||
extHeader.SetLength(0);
|
||||
padOption.InitForPadSize(6);
|
||||
|
||||
SuccessOrQuit(aMessage.Append(extHeader));
|
||||
SuccessOrQuit(aMessage.AppendBytes(&padOption, padOption.GetSize()));
|
||||
}
|
||||
|
||||
static void AppendFragmentHeader(Message &aMessage, uint8_t aNextHeader, uint16_t aOffset)
|
||||
{
|
||||
Ip6::FragmentHeader fragHeader;
|
||||
|
||||
fragHeader.Init();
|
||||
fragHeader.SetNextHeader(aNextHeader);
|
||||
fragHeader.SetOffset(aOffset);
|
||||
|
||||
SuccessOrQuit(aMessage.Append(fragHeader));
|
||||
}
|
||||
|
||||
static void AppendUdpHeader(Message &aMessage, uint16_t aDestPort)
|
||||
{
|
||||
Ip6::UdpHeader udpHeader;
|
||||
|
||||
udpHeader.Clear();
|
||||
udpHeader.SetSourcePort(kSourcePort);
|
||||
udpHeader.SetDestinationPort(aDestPort);
|
||||
udpHeader.SetLength(sizeof(Ip6::UdpHeader));
|
||||
|
||||
SuccessOrQuit(aMessage.Append(udpHeader));
|
||||
}
|
||||
|
||||
static void AppendIcmp6Header(Message &aMessage, Ip6::Icmp6Header::Type aType)
|
||||
{
|
||||
Ip6::Icmp6Header icmp6Header;
|
||||
|
||||
icmp6Header.Clear();
|
||||
icmp6Header.SetType(aType);
|
||||
|
||||
SuccessOrQuit(aMessage.Append(icmp6Header));
|
||||
}
|
||||
|
||||
static Error SendFromHost(Message &aMessage)
|
||||
{
|
||||
// Marks the message as host-untrusted origin and passes it to
|
||||
// `SendRaw()`, which routes it through `HandleDatagram()`.
|
||||
|
||||
aMessage.SetOrigin(Message::kOriginHostUntrusted);
|
||||
|
||||
return sNode->Get<Ip6::Ip6>().SendRaw(OwnedPtr<Message>(&aMessage));
|
||||
}
|
||||
|
||||
void TestIp6ForwardExtHeaders(void)
|
||||
{
|
||||
Core nexus;
|
||||
Node &node = nexus.CreateNode();
|
||||
Error error;
|
||||
|
||||
node.Form();
|
||||
nexus.AdvanceTime(13 * 1000);
|
||||
VerifyOrQuit(node.Get<Mle::Mle>().IsLeader());
|
||||
|
||||
sNode = &node;
|
||||
sSource = node.Get<Mle::Mle>().GetLinkLocalAddress();
|
||||
|
||||
// A link-local destination that is not on this node.
|
||||
sDestination = node.Get<Mle::Mle>().GetLinkLocalAddress();
|
||||
sDestination.mFields.m8[15] ^= 0x01;
|
||||
|
||||
Log("--- Case 1 (control): UDP to TMF port, no extension header -> dropped");
|
||||
{
|
||||
Message &message = NewForwardedMessage(Ip6::kProtoUdp, sizeof(Ip6::UdpHeader));
|
||||
|
||||
AppendUdpHeader(message, Tmf::kUdpPort);
|
||||
error = SendFromHost(message);
|
||||
Log("SendRaw returned: %s", ErrorToString(error));
|
||||
VerifyOrQuit(error == kErrorDrop);
|
||||
}
|
||||
|
||||
Log("--- Case 2: UDP to TMF port behind Destination Options header -> dropped");
|
||||
{
|
||||
Message &message =
|
||||
NewForwardedMessage(Ip6::kProtoDstOpts, sizeof(Ip6::ExtensionHeader) + 6 + sizeof(Ip6::UdpHeader));
|
||||
|
||||
AppendDstOptsHeader(message, Ip6::kProtoUdp);
|
||||
AppendUdpHeader(message, Tmf::kUdpPort);
|
||||
error = SendFromHost(message);
|
||||
Log("SendRaw returned: %s", ErrorToString(error));
|
||||
VerifyOrQuit(error == kErrorDrop);
|
||||
}
|
||||
|
||||
Log("--- Case 3: UDP to TMF port behind atomic Fragment header -> dropped");
|
||||
{
|
||||
Message &message =
|
||||
NewForwardedMessage(Ip6::kProtoFragment, sizeof(Ip6::FragmentHeader) + sizeof(Ip6::UdpHeader));
|
||||
|
||||
AppendFragmentHeader(message, Ip6::kProtoUdp, /* aOffset */ 0);
|
||||
AppendUdpHeader(message, Tmf::kUdpPort);
|
||||
error = SendFromHost(message);
|
||||
Log("SendRaw returned: %s", ErrorToString(error));
|
||||
VerifyOrQuit(error == kErrorDrop);
|
||||
}
|
||||
|
||||
Log("--- Case 4: ICMPv6 Echo Request behind Destination Options header -> forwarded");
|
||||
{
|
||||
Message &message =
|
||||
NewForwardedMessage(Ip6::kProtoDstOpts, sizeof(Ip6::ExtensionHeader) + 6 + sizeof(Ip6::Icmp6Header));
|
||||
|
||||
AppendDstOptsHeader(message, Ip6::kProtoIcmp6);
|
||||
AppendIcmp6Header(message, Ip6::Icmp6Header::kTypeEchoRequest);
|
||||
error = SendFromHost(message);
|
||||
Log("SendRaw returned: %s", ErrorToString(error));
|
||||
VerifyOrQuit(error == kErrorNone);
|
||||
}
|
||||
|
||||
Log("--- Case 5: ICMPv6 Neighbor Solicit behind Destination Options header -> dropped");
|
||||
{
|
||||
Message &message =
|
||||
NewForwardedMessage(Ip6::kProtoDstOpts, sizeof(Ip6::ExtensionHeader) + 6 + sizeof(Ip6::Icmp6Header));
|
||||
|
||||
AppendDstOptsHeader(message, Ip6::kProtoIcmp6);
|
||||
AppendIcmp6Header(message, Ip6::Icmp6Header::kTypeNeighborSolicit);
|
||||
error = SendFromHost(message);
|
||||
Log("SendRaw returned: %s", ErrorToString(error));
|
||||
VerifyOrQuit(error == kErrorDrop);
|
||||
}
|
||||
|
||||
Log("--- Case 6: extension header chain longer than resolution limit -> dropped");
|
||||
{
|
||||
uint16_t payloadLength = kLongChainLen * (sizeof(Ip6::ExtensionHeader) + 6) + sizeof(Ip6::UdpHeader);
|
||||
Message &message = NewForwardedMessage(Ip6::kProtoDstOpts, payloadLength);
|
||||
|
||||
for (uint8_t i = 0; i < kLongChainLen; i++)
|
||||
{
|
||||
AppendDstOptsHeader(message, (i + 1 < kLongChainLen) ? Ip6::kProtoDstOpts : Ip6::kProtoUdp);
|
||||
}
|
||||
|
||||
AppendUdpHeader(message, kNonTmfPort);
|
||||
error = SendFromHost(message);
|
||||
Log("SendRaw returned: %s", ErrorToString(error));
|
||||
VerifyOrQuit(error != kErrorNone);
|
||||
}
|
||||
|
||||
Log("--- Case 7: continuation fragment (no upper-layer header present) -> forwarded");
|
||||
{
|
||||
static const uint8_t kFragmentData[16] = {0};
|
||||
|
||||
Message &message =
|
||||
NewForwardedMessage(Ip6::kProtoFragment, sizeof(Ip6::FragmentHeader) + sizeof(kFragmentData));
|
||||
|
||||
AppendFragmentHeader(message, Ip6::kProtoUdp, /* aOffset */ 64);
|
||||
SuccessOrQuit(message.AppendBytes(kFragmentData, sizeof(kFragmentData)));
|
||||
error = SendFromHost(message);
|
||||
Log("SendRaw returned: %s", ErrorToString(error));
|
||||
VerifyOrQuit(error == kErrorNone);
|
||||
}
|
||||
|
||||
Log("--- Case 8: UDP to non-TMF port, no extension header -> forwarded");
|
||||
{
|
||||
Message &message = NewForwardedMessage(Ip6::kProtoUdp, sizeof(Ip6::UdpHeader));
|
||||
|
||||
AppendUdpHeader(message, kNonTmfPort);
|
||||
error = SendFromHost(message);
|
||||
Log("SendRaw returned: %s", ErrorToString(error));
|
||||
VerifyOrQuit(error == kErrorNone);
|
||||
}
|
||||
|
||||
Log("--- Case 9: UDP to non-TMF port behind Destination Options header -> forwarded");
|
||||
{
|
||||
Message &message =
|
||||
NewForwardedMessage(Ip6::kProtoDstOpts, sizeof(Ip6::ExtensionHeader) + 6 + sizeof(Ip6::UdpHeader));
|
||||
|
||||
AppendDstOptsHeader(message, Ip6::kProtoUdp);
|
||||
AppendUdpHeader(message, kNonTmfPort);
|
||||
error = SendFromHost(message);
|
||||
Log("SendRaw returned: %s", ErrorToString(error));
|
||||
VerifyOrQuit(error == kErrorNone);
|
||||
}
|
||||
|
||||
Log("--- Case 10: TCP, no extension header -> forwarded");
|
||||
{
|
||||
static const uint8_t kTcpHeader[20] = {0};
|
||||
|
||||
Message &message = NewForwardedMessage(Ip6::kProtoTcp, sizeof(kTcpHeader));
|
||||
|
||||
SuccessOrQuit(message.AppendBytes(kTcpHeader, sizeof(kTcpHeader)));
|
||||
error = SendFromHost(message);
|
||||
Log("SendRaw returned: %s", ErrorToString(error));
|
||||
VerifyOrQuit(error == kErrorNone);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Nexus
|
||||
} // namespace ot
|
||||
|
||||
int main(void)
|
||||
{
|
||||
ot::Nexus::TestIp6ForwardExtHeaders();
|
||||
printf("All tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user