Files
Abtin Keshavarzian cb4b28313b [logging] add support for per-instance log levels (#12740)
This commit introduces the ability to set and manage log levels on a
per-instance basis when dynamic logging is enabled, while maintaining
backward compatibility with existing logging behaviors.

The existing `otLoggingGetLevel()` and `otLoggingSetLevel()` APIs are
repurposed to manage the "global" log level. They continue to behave
exactly as before in both single-instance and multi-instance
configurations, ensuring that existing users of these APIs do not need
to change their implementations. To provide more granular control, new
APIs `otGetLogLevel()` and `otSetLogLevel()` are added to handle
per-instance log levels.

Specifically, this commit makes the following changes:
- Adds `mLogLevel` to `Instance` to track the instance-specific log
  level.
- Renames the global log level static variable to `sGlobalLogLevel` and
  introduces `GetGlobalLogLevel()` and `SetGlobalLogLevel()` to manage
  it in a multi-instance configuration.
- Updates `otGetLogLevel()` and `otSetLogLevel()` APIs to handle
  per-instance log level retrieval and configuration. If a specific
  level is not set for an instance, it falls back to the global
  log level.
- Adds `mIsLogLevelSet` to distinguish between an explicitly set
  instance log level and the global fallback in multi-instance builds.
- Introduces `otPlatLogHandleLogLevelChanged()` platform callback to
  notify the platform when an instance-specific log level is updated.
- Updates Nexus tests to use `SetGlobalLogLevel()` instead of the
  deprecated instance `SetLogLevel()` method.
2026-03-27 00:24:49 -05:00

878 lines
36 KiB
C++

/*
* 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 <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "platform/nexus_core.hpp"
#include "platform/nexus_node.hpp"
namespace ot {
namespace Nexus {
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
static constexpr uint32_t kInfraIfIndex = 1;
static constexpr uint16_t kMaxTxtDataSize = 128;
static constexpr ot::Trel::Peer::DnssdState kDnssdResolved = ot::Trel::Peer::kDnssdResolved;
static constexpr ot::Trel::Peer::DnssdState kDnssdRemoved = ot::Trel::Peer::kDnssdRemoved;
void TestTrelBasic(void)
{
// Validate basic operations, forming a network and
// joining as router, FED, MED, and SED using TREL.
// Also check TREL peer table on all devices.
Core nexus;
Node &leader = nexus.CreateNode();
Node &fed = nexus.CreateNode();
Node &sed = nexus.CreateNode();
Node &med = nexus.CreateNode();
Node &router1 = nexus.CreateNode();
Node &router2 = nexus.CreateNode();
Log("---------------------------------------------------------------------------------------");
Log("TestTrelBasic()");
nexus.AdvanceTime(0);
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelWarn));
for (Node &node : nexus.GetNodes())
{
SuccessOrQuit(node.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Form network");
leader.Form();
nexus.AdvanceTime(13 * 1000);
VerifyOrQuit(leader.Get<Mle::Mle>().IsLeader());
fed.Join(leader, Node::kAsFed);
nexus.AdvanceTime(10 * 1000);
VerifyOrQuit(fed.Get<Mle::Mle>().IsChild());
sed.Join(leader, Node::kAsSed);
nexus.AdvanceTime(2 * 1000);
VerifyOrQuit(sed.Get<Mle::Mle>().IsChild());
med.Join(leader, Node::kAsMed);
nexus.AdvanceTime(2 * 1000);
VerifyOrQuit(med.Get<Mle::Mle>().IsChild());
router1.Join(leader);
router2.Join(leader);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check all nodes roles and device modes");
nexus.AdvanceTime(300 * 1000);
VerifyOrQuit(leader.Get<Mle::Mle>().IsLeader());
VerifyOrQuit(fed.Get<Mle::Mle>().IsChild());
VerifyOrQuit(sed.Get<Mle::Mle>().IsChild());
VerifyOrQuit(router1.Get<Mle::Mle>().IsRouter());
VerifyOrQuit(router2.Get<Mle::Mle>().IsRouter());
VerifyOrQuit(fed.Get<Mle::Mle>().IsRxOnWhenIdle());
VerifyOrQuit(fed.Get<Mle::Mle>().IsFullThreadDevice());
VerifyOrQuit(med.Get<Mle::Mle>().IsRxOnWhenIdle());
VerifyOrQuit(!med.Get<Mle::Mle>().IsFullThreadDevice());
VerifyOrQuit(med.Get<Mle::Mle>().IsMinimalEndDevice());
VerifyOrQuit(!sed.Get<Mle::Mle>().IsRxOnWhenIdle());
VerifyOrQuit(!sed.Get<Mle::Mle>().IsFullThreadDevice());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check TREL peer table on all nodes");
for (Node &node : nexus.GetNodes())
{
VerifyOrQuit(node.Get<ot::Trel::PeerTable>().GetNumberOfPeers() == 5);
for (const ot::Trel::Peer &peer : node.Get<ot::Trel::PeerTable>())
{
bool found = false;
VerifyOrQuit(peer.GetDnssdState() == ot::Trel::Peer::kDnssdResolved);
VerifyOrQuit(peer.GetExtPanId() == node.Get<MeshCoP::NetworkIdentity>().GetExtPanId());
for (Node &otherNode : nexus.GetNodes())
{
if (&otherNode == &node)
{
continue;
}
if (peer.GetExtAddress() == otherNode.Get<Mac::Mac>().GetExtAddress())
{
Ip6::SockAddr otherSockAddr;
found = true;
otherNode.GetTrelSockAddr(otherSockAddr);
VerifyOrQuit(peer.GetSockAddr() == otherSockAddr);
VerifyOrQuit(peer.GetServiceName() != nullptr);
VerifyOrQuit(
StringMatch(peer.GetServiceName(), otherNode.Get<ot::Trel::PeerDiscoverer>().GetServiceName()));
VerifyOrQuit(peer.GetHostName() != nullptr);
VerifyOrQuit(StringStartsWith(peer.GetHostName(), "ot"));
VerifyOrQuit(StringEndsWith(peer.GetHostName(),
otherNode.Get<Mac::Mac>().GetExtAddress().ToString().AsCString()));
VerifyOrQuit(peer.GetHostAddresses().GetLength() == 1);
VerifyOrQuit(peer.GetHostAddresses()[0] == otherSockAddr.GetAddress());
break;
}
}
VerifyOrQuit(found);
// Check the format of the TREL service name
VerifyOrQuit(StringStartsWith(node.Get<ot::Trel::PeerDiscoverer>().GetServiceName(), "otTREL"));
VerifyOrQuit(StringEndsWith(node.Get<ot::Trel::PeerDiscoverer>().GetServiceName(),
node.Get<Mac::Mac>().GetExtAddress().ToString().AsCString()));
}
}
}
void TestTrelUserDisableReenable(void)
{
// Validates user disabling of TREL interface persists after
// OpenThread state changes such as Thread netif down and up.
Core nexus;
Node &node = nexus.CreateNode();
Log("---------------------------------------------------------------------------------------");
Log("TestTrelUserDisableReenable()");
nexus.AdvanceTime(0);
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelWarn));
SuccessOrQuit(node.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Form network");
node.Form();
nexus.AdvanceTime(13 * 1000);
VerifyOrQuit(node.Get<Mle::Mle>().IsLeader());
VerifyOrQuit(node.Get<ot::Trel::Interface>().IsEnabled());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable TREL interface by `kRequesterUser`");
node.Get<ot::Trel::Interface>().SetEnabled(false, ot::Trel::Interface::kRequesterUser);
VerifyOrQuit(!node.Get<ot::Trel::Interface>().IsEnabled());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Bring down Thread netif and stop MAC and all radio links");
node.Get<ThreadNetif>().Down();
node.Get<Mac::Mac>().SetEnabled(false);
VerifyOrQuit(node.Get<Mle::Mle>().IsDisabled());
VerifyOrQuit(!node.Get<ot::Trel::Interface>().IsEnabled());
nexus.AdvanceTime(1000);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Bring up the Thread netif down and restart MLE and Thread operation");
node.Get<ThreadNetif>().Up();
SuccessOrQuit(node.Get<Mle::Mle>().Start());
nexus.AdvanceTime(60 * 1000);
VerifyOrQuit(node.Get<Mle::Mle>().IsLeader());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Ensure TREL interface remains disabled since it was explicitly disabled by `kRequesterUser`");
VerifyOrQuit(!node.Get<ot::Trel::Interface>().IsEnabled());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable TREL interface by `kRequesterUser` ");
node.Get<ot::Trel::Interface>().SetEnabled(true, ot::Trel::Interface::kRequesterUser);
VerifyOrQuit(node.Get<ot::Trel::Interface>().IsEnabled());
}
void TestTrelDelayedMdnsStartAndPeerRemovalDelay(void)
{
Core nexus;
Node &node1 = nexus.CreateNode();
Node &node2 = nexus.CreateNode();
const ot::Trel::Peer *peer;
uint32_t inactiveDuration;
Log("---------------------------------------------------------------------------------------");
Log("TestTrelDelayedMdnsStartAndPeerRemovalDelay()");
nexus.AdvanceTime(0);
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelWarn));
for (Node &node : nexus.GetNodes())
{
VerifyOrQuit(!node.Get<Dns::Multicast::Core>().IsEnabled());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Start first network on node1");
SuccessOrQuit(node1.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
node1.Form();
nexus.AdvanceTime(45 * 1000);
VerifyOrQuit(node1.Get<Mle::Mle>().IsLeader());
// Check that `node1` did not discover any `TREL` peer,
// as `mDNS` is not yet enabled on other nodes.
// Additionally, the `TREL` peer table must exclude the device itself.
VerifyOrQuit(node1.Get<ot::Trel::PeerTable>().IsEmpty());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable mDNS on `node2`, form a new network");
SuccessOrQuit(node2.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
node2.Form();
nexus.AdvanceTime(45 * 1000);
VerifyOrQuit(node1.Get<Mle::Mle>().IsLeader());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that peer tables are correctly updated on both `node1` and `node2`");
VerifyOrQuit(node1.Get<ot::Trel::PeerTable>().GetNumberOfPeers() == 1);
VerifyOrQuit(node2.Get<ot::Trel::PeerTable>().GetNumberOfPeers() == 1);
// Check peer on `node1` to match `node2` info.
peer = node1.Get<ot::Trel::PeerTable>().GetHead();
VerifyOrQuit(peer != nullptr);
VerifyOrQuit(peer->GetDnssdState() == ot::Trel::Peer::kDnssdResolved);
VerifyOrQuit(peer->GetExtPanId() == node2.Get<MeshCoP::NetworkIdentity>().GetExtPanId());
VerifyOrQuit(peer->GetExtAddress() == node2.Get<Mac::Mac>().GetExtAddress());
VerifyOrQuit(peer->GetServiceName() != nullptr);
VerifyOrQuit(StringMatch(peer->GetServiceName(), node2.Get<ot::Trel::PeerDiscoverer>().GetServiceName()));
VerifyOrQuit(peer->GetHostName() != nullptr);
VerifyOrQuit(StringStartsWith(peer->GetHostName(), "ot"));
VerifyOrQuit(StringEndsWith(peer->GetHostName(), node2.Get<Mac::Mac>().GetExtAddress().ToString().AsCString()));
VerifyOrQuit(peer->GetSockAddr().GetPort() == node2.mTrel.mUdpPort);
VerifyOrQuit(peer->GetSockAddr().GetAddress() == node2.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peer->GetHostAddresses().GetLength() == 1);
VerifyOrQuit(peer->GetHostAddresses()[0] == node2.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peer->GetNext() == nullptr);
// Check peer on `node2` to match `node1` info.
peer = node2.Get<ot::Trel::PeerTable>().GetHead();
VerifyOrQuit(peer != nullptr);
VerifyOrQuit(peer->GetDnssdState() == ot::Trel::Peer::kDnssdResolved);
VerifyOrQuit(peer->GetExtPanId() == node1.Get<MeshCoP::NetworkIdentity>().GetExtPanId());
VerifyOrQuit(peer->GetExtAddress() == node1.Get<Mac::Mac>().GetExtAddress());
VerifyOrQuit(peer->GetServiceName() != nullptr);
VerifyOrQuit(StringMatch(peer->GetServiceName(), node1.Get<ot::Trel::PeerDiscoverer>().GetServiceName()));
VerifyOrQuit(peer->GetHostName() != nullptr);
VerifyOrQuit(StringStartsWith(peer->GetHostName(), "ot"));
VerifyOrQuit(StringEndsWith(peer->GetHostName(), node1.Get<Mac::Mac>().GetExtAddress().ToString().AsCString()));
VerifyOrQuit(peer->GetSockAddr().GetPort() == node1.mTrel.mUdpPort);
VerifyOrQuit(peer->GetSockAddr().GetAddress() == node1.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peer->GetHostAddresses().GetLength() == 1);
VerifyOrQuit(peer->GetHostAddresses()[0] == node1.mInfraIf.GetLinkLocalAddress());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable TREL Interface (and `PeerDiscoverer`) on `node2`");
node2.Get<ot::Trel::Interface>().SetEnabled(false, ot::Trel::Interface::kRequesterUser);
nexus.AdvanceTime(2 * 1000);
VerifyOrQuit(node2.Get<ot::Trel::PeerTable>().GetNumberOfPeers() == 0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `PeerTable` is properly updated on `node1`");
peer = node1.Get<ot::Trel::PeerTable>().GetHead();
VerifyOrQuit(peer != nullptr);
VerifyOrQuit(peer->GetDnssdState() == kDnssdRemoved);
VerifyOrQuit(peer->GetExtPanId() == node2.Get<MeshCoP::NetworkIdentity>().GetExtPanId());
VerifyOrQuit(peer->GetExtAddress() == node2.Get<Mac::Mac>().GetExtAddress());
VerifyOrQuit(peer->GetServiceName() != nullptr);
VerifyOrQuit(StringMatch(peer->GetServiceName(), node2.Get<ot::Trel::PeerDiscoverer>().GetServiceName()));
VerifyOrQuit(peer->GetHostName() == nullptr);
VerifyOrQuit(peer->GetHostAddresses().GetLength() == 0);
VerifyOrQuit(peer->GetNext() == nullptr);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Re-enable TREL Interface (and `PeerDiscoverer`) on `node2`");
node2.Get<ot::Trel::Interface>().SetEnabled(true, ot::Trel::Interface::kRequesterUser);
nexus.AdvanceTime(15 * 1000);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that peer tables again updated on both nodes");
VerifyOrQuit(node1.Get<ot::Trel::PeerTable>().GetNumberOfPeers() == 1);
VerifyOrQuit(node2.Get<ot::Trel::PeerTable>().GetNumberOfPeers() == 1);
// Check peer on `node1` to match `node2` info.
peer = node1.Get<ot::Trel::PeerTable>().GetHead();
VerifyOrQuit(peer != nullptr);
VerifyOrQuit(peer->GetDnssdState() == ot::Trel::Peer::kDnssdResolved);
VerifyOrQuit(peer->GetExtPanId() == node2.Get<MeshCoP::NetworkIdentity>().GetExtPanId());
VerifyOrQuit(peer->GetExtAddress() == node2.Get<Mac::Mac>().GetExtAddress());
VerifyOrQuit(peer->GetServiceName() != nullptr);
VerifyOrQuit(StringMatch(peer->GetServiceName(), node2.Get<ot::Trel::PeerDiscoverer>().GetServiceName()));
VerifyOrQuit(peer->GetHostName() != nullptr);
VerifyOrQuit(StringStartsWith(peer->GetHostName(), "ot"));
VerifyOrQuit(StringEndsWith(peer->GetHostName(), node2.Get<Mac::Mac>().GetExtAddress().ToString().AsCString()));
VerifyOrQuit(peer->GetSockAddr().GetPort() == node2.mTrel.mUdpPort);
VerifyOrQuit(peer->GetSockAddr().GetAddress() == node2.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peer->GetHostAddresses().GetLength() == 1);
VerifyOrQuit(peer->GetHostAddresses()[0] == node2.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peer->GetNext() == nullptr);
// Check peer on `node2` to match `node1` info.
peer = node2.Get<ot::Trel::PeerTable>().GetHead();
VerifyOrQuit(peer != nullptr);
VerifyOrQuit(peer->GetDnssdState() == ot::Trel::Peer::kDnssdResolved);
VerifyOrQuit(peer->GetExtPanId() == node1.Get<MeshCoP::NetworkIdentity>().GetExtPanId());
VerifyOrQuit(peer->GetExtAddress() == node1.Get<Mac::Mac>().GetExtAddress());
VerifyOrQuit(peer->GetServiceName() != nullptr);
VerifyOrQuit(StringMatch(peer->GetServiceName(), node1.Get<ot::Trel::PeerDiscoverer>().GetServiceName()));
VerifyOrQuit(peer->GetHostName() != nullptr);
VerifyOrQuit(StringStartsWith(peer->GetHostName(), "ot"));
VerifyOrQuit(StringEndsWith(peer->GetHostName(), node1.Get<Mac::Mac>().GetExtAddress().ToString().AsCString()));
VerifyOrQuit(peer->GetSockAddr().GetPort() == node1.mTrel.mUdpPort);
VerifyOrQuit(peer->GetSockAddr().GetAddress() == node1.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peer->GetHostAddresses().GetLength() == 1);
VerifyOrQuit(peer->GetHostAddresses()[0] == node1.mInfraIf.GetLinkLocalAddress());
peer = node1.Get<ot::Trel::PeerTable>().GetHead();
VerifyOrQuit(peer != nullptr);
inactiveDuration = peer->DetermineSecondsSinceLastInteraction();
VerifyOrQuit(inactiveDuration > 0);
Log("- peer has been inactive for %lu seconds", ToUlong(inactiveDuration));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable TREL Interface (and `PeerDiscoverer`) on `node2` again and signal its removal on mDNS");
node2.Get<ot::Trel::Interface>().SetEnabled(false, ot::Trel::Interface::kRequesterUser);
VerifyOrQuit(node2.Get<ot::Trel::PeerTable>().IsEmpty());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check that peer entry for `node2` is properly switched to `kDnssdRemoved` state");
nexus.AdvanceTime(10 * 1000 + 500);
peer = node1.Get<ot::Trel::PeerTable>().GetHead();
VerifyOrQuit(peer != nullptr);
VerifyOrQuit(peer->GetDnssdState() == kDnssdRemoved);
VerifyOrQuit(peer->GetExtPanId() == node2.Get<MeshCoP::NetworkIdentity>().GetExtPanId());
VerifyOrQuit(peer->GetExtAddress() == node2.Get<Mac::Mac>().GetExtAddress());
VerifyOrQuit(peer->GetSockAddr().GetAddress() == node2.mInfraIf.GetLinkLocalAddress());
Log("Validate the `DetermineSecondsSinceLastInteraction()` is properly tracked");
VerifyOrQuit(peer->DetermineSecondsSinceLastInteraction() - inactiveDuration >= 10);
inactiveDuration = peer->DetermineSecondsSinceLastInteraction();
VerifyOrQuit(inactiveDuration > 0);
Log("- peer has been inactive for %lu seconds", ToUlong(inactiveDuration));
Log("Validate that peer is deleted from list after 450 second inactivity");
nexus.AdvanceTime((451 - inactiveDuration) * 1000);
VerifyOrQuit(node1.Get<ot::Trel::PeerTable>().IsEmpty());
peer = node1.Get<ot::Trel::PeerTable>().GetHead();
VerifyOrQuit(peer == nullptr);
}
void TestServiceNameConflict(void)
{
Core nexus;
Node &node1 = nexus.CreateNode();
Node &node2 = nexus.CreateNode();
Node &conflictNode = nexus.CreateNode();
Dns::Multicast::Core::Service service;
Dns::Name::Buffer conflictName;
bool peersValidated;
Log("---------------------------------------------------------------------------------------");
Log("TestServiceNameConflict()");
nexus.AdvanceTime(0);
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelWarn));
for (Node &node : nexus.GetNodes())
{
VerifyOrQuit(!node.Get<Dns::Multicast::Core>().IsEnabled());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable TREL interface but enable mDNS on `conflictNode`");
conflictNode.Get<ot::Trel::Interface>().SetEnabled(false, ot::Trel::Interface::kRequesterUser);
SuccessOrQuit(conflictNode.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
Log("Register a service on `conflictNode` with same name that `node1` would use");
SuccessOrQuit(StringCopy(conflictName, node1.Get<ot::Trel::PeerDiscoverer>().GetServiceName()));
ClearAllBytes(service);
service.mServiceType = "_trel._udp";
service.mServiceInstance = conflictName;
service.mPort = 12345;
SuccessOrQuit(conflictNode.Get<Dns::Multicast::Core>().RegisterService(service, /* aRequestId */ 0, nullptr));
nexus.AdvanceTime(15 * 1000);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable mDNS on `node1` and `node2` and form a new network");
SuccessOrQuit(node1.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
SuccessOrQuit(node2.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
node1.Form();
nexus.AdvanceTime(45 * 1000);
VerifyOrQuit(node1.Get<Mle::Mle>().IsLeader());
node2.Join(node1, Node::kAsFed);
nexus.AdvanceTime(15 * 1000);
VerifyOrQuit(node2.Get<Mle::Mle>().IsChild());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `node1` correctly detected the name conflict and appended a (1) to its service name");
VerifyOrQuit(StringStartsWith(node1.Get<ot::Trel::PeerDiscoverer>().GetServiceName(), conflictName));
VerifyOrQuit(StringEndsWith(node1.Get<ot::Trel::PeerDiscoverer>().GetServiceName(), "(1)"));
Log("Check peer table on `node2` to match `node1` info");
peersValidated = false;
for (const ot::Trel::Peer &peer : node2.Get<ot::Trel::PeerTable>())
{
if (peer.GetDnssdState() == ot::Trel::Peer::kDnssdResolved)
{
VerifyOrQuit(peer.GetExtPanId() == node1.Get<MeshCoP::NetworkIdentity>().GetExtPanId());
VerifyOrQuit(peer.GetExtAddress() == node1.Get<Mac::Mac>().GetExtAddress());
VerifyOrQuit(peer.GetServiceName() != nullptr);
VerifyOrQuit(StringMatch(peer.GetServiceName(), node1.Get<ot::Trel::PeerDiscoverer>().GetServiceName()));
peersValidated = true;
break;
}
}
VerifyOrQuit(peersValidated);
}
void TestHostAddressChange(void)
{
Core nexus;
Node &node1 = nexus.CreateNode();
Node &node2 = nexus.CreateNode();
const ot::Trel::Peer *peer;
Dns::Multicast::Core::Service service;
uint8_t txtData[kMaxTxtDataSize];
Dns::TxtDataEncoder encoder(txtData, sizeof(txtData));
Ip6::Address linkLocalAddr;
Ip6::Address guaAddr;
Ip6::Address ulaAddr;
Log("---------------------------------------------------------------------------------------");
Log("TestHostAddressChange()");
nexus.AdvanceTime(0);
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelWarn));
for (Node &node : nexus.GetNodes())
{
VerifyOrQuit(!node.Get<Dns::Multicast::Core>().IsEnabled());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable TREL interface but enable mDNS on `node2`");
node2.Get<ot::Trel::Interface>().SetEnabled(false, ot::Trel::Interface::kRequesterUser);
SuccessOrQuit(node2.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
Log("Manually register a TREL service on `node2` with proper TXT data");
SuccessOrQuit(encoder.AppendEntry("xa", node2.Get<Mac::Mac>().GetExtAddress()));
SuccessOrQuit(encoder.AppendEntry("xp", node2.Get<MeshCoP::NetworkIdentity>().GetExtPanId()));
ClearAllBytes(service);
service.mServiceType = "_trel._udp";
service.mServiceInstance = "node2";
service.mTxtData = txtData;
service.mTxtDataLength = encoder.GetLength();
service.mPort = 3333;
SuccessOrQuit(node2.Get<Dns::Multicast::Core>().RegisterService(service, /* aRequestId */ 0, nullptr));
nexus.AdvanceTime(15 * 1000);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Form a new network on `node1`");
SuccessOrQuit(node1.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
node1.Form();
nexus.AdvanceTime(45 * 1000);
VerifyOrQuit(node1.Get<Mle::Mle>().IsLeader());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate peer table on `node1` and `node2` is discovered properly");
peer = node1.Get<ot::Trel::PeerTable>().GetHead();
VerifyOrQuit(peer != nullptr);
VerifyOrQuit(peer->GetDnssdState() == kDnssdResolved);
VerifyOrQuit(peer->GetExtPanId() == node2.Get<MeshCoP::NetworkIdentity>().GetExtPanId());
VerifyOrQuit(peer->GetExtAddress() == node2.Get<Mac::Mac>().GetExtAddress());
VerifyOrQuit(peer->GetServiceName() != nullptr);
VerifyOrQuit(StringMatch(peer->GetServiceName(), service.mServiceInstance));
VerifyOrQuit(peer->GetHostName() != nullptr);
VerifyOrQuit(StringStartsWith(peer->GetHostName(), "ot"));
VerifyOrQuit(StringEndsWith(peer->GetHostName(), node2.Get<Mac::Mac>().GetExtAddress().ToString().AsCString()));
VerifyOrQuit(peer->GetSockAddr().GetPort() == service.mPort);
VerifyOrQuit(peer->GetSockAddr().GetAddress() == node2.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peer->GetHostAddresses().GetLength() == 1);
VerifyOrQuit(peer->GetHostAddresses()[0] == node2.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peer->GetNext() == nullptr);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Update the advertised local host addresses on `node2`");
node2.mInfraIf.RemoveAllAddresses();
SuccessOrQuit(guaAddr.FromString("2001:cafe::4567"));
node2.mInfraIf.AddAddress(guaAddr);
SuccessOrQuit(ulaAddr.FromString("fd00:abba::1234"));
node2.mInfraIf.AddAddress(ulaAddr);
SuccessOrQuit(linkLocalAddr.FromString("fe80::bd2c:a124"));
node2.mInfraIf.AddAddress(linkLocalAddr);
nexus.AdvanceTime(3 * 1000);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate peer table on `node1` discovers all the new addresses");
peer = node1.Get<ot::Trel::PeerTable>().GetHead();
VerifyOrQuit(peer != nullptr);
VerifyOrQuit(peer->GetDnssdState() == kDnssdResolved);
VerifyOrQuit(peer->GetExtPanId() == node2.Get<MeshCoP::NetworkIdentity>().GetExtPanId());
VerifyOrQuit(peer->GetExtAddress() == node2.Get<Mac::Mac>().GetExtAddress());
VerifyOrQuit(peer->GetServiceName() != nullptr);
VerifyOrQuit(StringMatch(peer->GetServiceName(), service.mServiceInstance));
VerifyOrQuit(peer->GetHostName() != nullptr);
VerifyOrQuit(StringStartsWith(peer->GetHostName(), "ot"));
VerifyOrQuit(StringEndsWith(peer->GetHostName(), node2.Get<Mac::Mac>().GetExtAddress().ToString().AsCString()));
VerifyOrQuit(peer->GetSockAddr().GetPort() == service.mPort);
VerifyOrQuit(peer->GetHostAddresses().GetLength() == 3);
for (const Ip6::Address &hostAddress : peer->GetHostAddresses())
{
VerifyOrQuit(node2.mInfraIf.HasAddress(hostAddress));
}
for (const Ip6::Address &ifAddress : node2.mInfraIf.GetAddresses())
{
VerifyOrQuit(peer->GetHostAddresses().Contains(ifAddress));
}
// Check the order of addresses in list:
VerifyOrQuit(peer->GetHostAddresses()[0] == linkLocalAddr);
VerifyOrQuit(peer->GetHostAddresses()[1] == guaAddr);
VerifyOrQuit(peer->GetHostAddresses()[2] == ulaAddr);
Log("Validate the peer `SockAddr` is correctly updated based on new discovered host addresses");
VerifyOrQuit(peer->GetSockAddr().GetAddress() == linkLocalAddr);
VerifyOrQuit(peer->GetSockAddr().GetPort() == service.mPort);
VerifyOrQuit(peer->GetNext() == nullptr);
}
void TestMultiServiceSameHost(void)
{
Core nexus;
Node &node = nexus.CreateNode();
Node &multiServiceNode = nexus.CreateNode();
Dns::Multicast::Core::Service services[3];
uint8_t txtData[kMaxTxtDataSize];
Ip6::Address address;
Log("---------------------------------------------------------------------------------------");
Log("TestMultiServiceSameHost()");
nexus.AdvanceTime(0);
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelInfo));
for (Node &nodeEntry : nexus.GetNodes())
{
VerifyOrQuit(!nodeEntry.Get<Dns::Multicast::Core>().IsEnabled());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable TREL interface but enable mDNS on `multiServiceNode`");
multiServiceNode.Get<ot::Trel::Interface>().SetEnabled(false, ot::Trel::Interface::kRequesterUser);
SuccessOrQuit(multiServiceNode.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
Log("Manually register three TREL services on the `multiServiceNode`");
{
Dns::TxtDataEncoder encoder(txtData, sizeof(txtData));
SuccessOrQuit(encoder.AppendEntry("xa", "0102030405060708"));
SuccessOrQuit(encoder.AppendEntry("xp", "0102030405060708"));
ClearAllBytes(services[0]);
services[0].mServiceType = "_trel._udp";
services[0].mServiceInstance = "service0";
services[0].mTxtData = txtData;
services[0].mTxtDataLength = encoder.GetLength();
services[0].mPort = 11111;
}
SuccessOrQuit(
multiServiceNode.Get<Dns::Multicast::Core>().RegisterService(services[0], /* aRequestId */ 0, nullptr));
{
Dns::TxtDataEncoder encoder(txtData, sizeof(txtData));
SuccessOrQuit(encoder.AppendEntry("xa", "1122334455667788"));
SuccessOrQuit(encoder.AppendEntry("xp", "1122334455667788"));
ClearAllBytes(services[1]);
services[1].mServiceType = "_trel._udp";
services[1].mServiceInstance = "service1";
services[1].mTxtData = txtData;
services[1].mTxtDataLength = encoder.GetLength();
services[1].mPort = 2222;
}
SuccessOrQuit(
multiServiceNode.Get<Dns::Multicast::Core>().RegisterService(services[1], /* aRequestId */ 0, nullptr));
{
Dns::TxtDataEncoder encoder(txtData, sizeof(txtData));
SuccessOrQuit(encoder.AppendEntry("xa", "1020304050607080"));
SuccessOrQuit(encoder.AppendEntry("xp", "1020304050607080"));
ClearAllBytes(services[2]);
services[2].mServiceType = "_trel._udp";
services[2].mServiceInstance = "service2";
services[2].mTxtData = txtData;
services[2].mTxtDataLength = encoder.GetLength();
services[2].mPort = 3333;
}
SuccessOrQuit(
multiServiceNode.Get<Dns::Multicast::Core>().RegisterService(services[2], /* aRequestId */ 0, nullptr));
nexus.AdvanceTime(15 * 1000);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Form a new network on `node`");
SuccessOrQuit(node.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
node.Form();
nexus.AdvanceTime(45 * 1000);
VerifyOrQuit(node.Get<Mle::Mle>().IsLeader());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate peer table on `node` and all services are discovered from the same host");
VerifyOrQuit(node.Get<ot::Trel::PeerTable>().GetNumberOfPeers() == 3);
for (const ot::Trel::Peer &peerEntry : node.Get<ot::Trel::PeerTable>())
{
bool found = false;
VerifyOrQuit(peerEntry.GetDnssdState() == kDnssdResolved);
VerifyOrQuit(peerEntry.GetServiceName() != nullptr);
VerifyOrQuit(peerEntry.GetHostName() != nullptr);
VerifyOrQuit(StringStartsWith(peerEntry.GetHostName(), "ot"));
VerifyOrQuit(StringEndsWith(peerEntry.GetHostName(),
multiServiceNode.Get<Mac::Mac>().GetExtAddress().ToString().AsCString()));
VerifyOrQuit(peerEntry.GetSockAddr().GetAddress() == multiServiceNode.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peerEntry.GetHostAddresses().GetLength() == 1);
VerifyOrQuit(peerEntry.GetHostAddresses()[0] == multiServiceNode.mInfraIf.GetLinkLocalAddress());
for (const Dns::Multicast::Core::Service &service : services)
{
if (StringMatch(peerEntry.GetServiceName(), service.mServiceInstance))
{
found = true;
}
}
VerifyOrQuit(found);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Unregister the third service on `multiServiceNode`");
SuccessOrQuit(multiServiceNode.Get<Dns::Multicast::Core>().UnregisterService(services[2]));
nexus.AdvanceTime(15 * 1000);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate peer table on `node`");
VerifyOrQuit(node.Get<ot::Trel::PeerTable>().GetNumberOfPeers() == 3);
for (const ot::Trel::Peer &peerEntry : node.Get<ot::Trel::PeerTable>())
{
bool found = false;
if (peerEntry.GetDnssdState() != kDnssdResolved)
{
continue;
}
VerifyOrQuit(peerEntry.GetServiceName() != nullptr);
VerifyOrQuit(peerEntry.GetHostName() != nullptr);
VerifyOrQuit(StringStartsWith(peerEntry.GetHostName(), "ot"));
VerifyOrQuit(StringEndsWith(peerEntry.GetHostName(),
multiServiceNode.Get<Mac::Mac>().GetExtAddress().ToString().AsCString()));
VerifyOrQuit(peerEntry.GetSockAddr().GetAddress() == multiServiceNode.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peerEntry.GetHostAddresses().GetLength() == 1);
VerifyOrQuit(peerEntry.GetHostAddresses()[0] == multiServiceNode.mInfraIf.GetLinkLocalAddress());
for (uint16_t index = 0; index < 2; index++)
{
if (StringMatch(peerEntry.GetServiceName(), services[index].mServiceInstance))
{
found = true;
}
}
VerifyOrQuit(found);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Update the local host addresses on `multiServiceNode`");
SuccessOrQuit(address.FromString("fd00:abba::1234"));
multiServiceNode.mInfraIf.AddAddress(address);
nexus.AdvanceTime(5 * 1000);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate all peers get the updated list");
VerifyOrQuit(node.Get<ot::Trel::PeerTable>().GetNumberOfPeers() == 3);
for (const ot::Trel::Peer &peerEntry : node.Get<ot::Trel::PeerTable>())
{
bool found = false;
if (peerEntry.GetDnssdState() != kDnssdResolved)
{
continue;
}
VerifyOrQuit(peerEntry.GetServiceName() != nullptr);
VerifyOrQuit(peerEntry.GetHostName() != nullptr);
VerifyOrQuit(StringStartsWith(peerEntry.GetHostName(), "ot"));
VerifyOrQuit(StringEndsWith(peerEntry.GetHostName(),
multiServiceNode.Get<Mac::Mac>().GetExtAddress().ToString().AsCString()));
VerifyOrQuit(peerEntry.GetSockAddr().GetAddress() == multiServiceNode.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peerEntry.GetHostAddresses().GetLength() == 2);
VerifyOrQuit(peerEntry.GetHostAddresses()[0] == multiServiceNode.mInfraIf.GetLinkLocalAddress());
VerifyOrQuit(peerEntry.GetHostAddresses()[1] == multiServiceNode.mInfraIf.GetAddresses()[1]);
for (uint16_t index = 0; index < 2; index++)
{
if (StringMatch(peerEntry.GetServiceName(), services[index].mServiceInstance))
{
found = true;
}
}
VerifyOrQuit(found);
}
}
#endif // OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
} // namespace Nexus
} // namespace ot
int main(void)
{
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
ot::Nexus::TestTrelBasic();
ot::Nexus::TestTrelUserDisableReenable();
ot::Nexus::TestTrelDelayedMdnsStartAndPeerRemovalDelay();
ot::Nexus::TestServiceNameConflict();
ot::Nexus::TestHostAddressChange();
ot::Nexus::TestMultiServiceSameHost();
printf("All tests passed\n");
#else
printf("TREL is not enabled - test skipped\n");
#endif
return 0;
}