From a40b42e29932104e2960e9cebb6ebec95c67bc4c Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 11 May 2021 13:44:29 -0700 Subject: [PATCH] [network-data] add new DNS/SRP anycast/unicast service definitions (#6501) This commit adds new network data service entry definitions to indicate presence of DNS/SRP servers within the Thread mesh and provide info about them. Two service TLV formats are added: `NetworkData::Service::DnsSrpAnycast` indicates that DNS/SRP client on a device can use the associated anycast address with this service TLV to reach the DNS/SRP server. The use of anycast address ensures that the messages are routed to the nearest node which has added such a service entry in the network data. The service TLV data in this model contains a one byte sequence number which is used to notify the SRP clients if they need to re-register with the server(s) (e.g., due server reset/reboot and/or loss of previous registrations). `NetworkData::Service::DnsSrpUnicast` directly provides the IPv6 address and port info for a DNS/SRP server which can be included as part of the service TLV data and/or the server TLV data. Using service TLV data allows the info about a common infrastructure SRP/DNS server to be added by multiple BRs. In the case of server TLV, the IPv6 address info can be optionally omitted (i.e. just a port number is provided) which then causes the associated RLOC/ALOC address of the node who added the service entry to be used as the server's IPv6 address. This commit updates the `Srp::Client` to use the new service entries to discover/select the SRP sever when auto-start mode is enabled. The client prefers and uses a `DnsSrpAnycast` over `DnsSrpUnicast` entries. The `Srp::Server` is also updated to publish its info as `DnsSrpAnycast` entry (using mesh-local address). This commit also updates the `test_network_data` unit test to cover the behavior of newly methods which iterate and parse different service entry formats. --- .../config/openthread-core-config-check.h | 5 + src/core/config/srp_server.h | 10 - src/core/net/socket.hpp | 8 + src/core/net/srp_client.cpp | 89 +++-- src/core/net/srp_client.hpp | 7 + src/core/net/srp_server.cpp | 9 +- src/core/thread/network_data_service.cpp | 168 +++++++-- src/core/thread/network_data_service.hpp | 347 +++++++++++++++--- tests/scripts/thread-cert/node.py | 5 +- tests/unit/test_network_data.cpp | 137 +++++++ 10 files changed, 659 insertions(+), 126 deletions(-) diff --git a/src/core/config/openthread-core-config-check.h b/src/core/config/openthread-core-config-check.h index 627d166ff..f0ede5a56 100644 --- a/src/core/config/openthread-core-config-check.h +++ b/src/core/config/openthread-core-config-check.h @@ -504,4 +504,9 @@ #error "OPENTHREAD_CONFIG_ANNOUNCE_SENDER_INTERVAL_REED was replaced by OPENTHREAD_CONFIG_ANNOUNCE_SENDER_INTERVAL" #endif +#ifdef OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_NUMBER +#error "OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_NUMBER was removed. "\ + "Service numbers are defined in `network_data_servcie.hpp` per spec" +#endif + #endif // OPENTHREAD_CORE_CONFIG_CHECK_H_ diff --git a/src/core/config/srp_server.h b/src/core/config/srp_server.h index 42fe08d84..fd72a868c 100644 --- a/src/core/config/srp_server.h +++ b/src/core/config/srp_server.h @@ -55,16 +55,6 @@ #define OPENTHREAD_CONFIG_SRP_SERVER_UDP_PORT 0 #endif -/** - * @def OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_NUMBER - * - * Specifies the Thread Network Data Service number for SRP Server. - * - */ -#ifndef OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_NUMBER -#define OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_NUMBER 0x5du -#endif - /** * @def OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_UPDATE_TIMEOUT * diff --git a/src/core/net/socket.hpp b/src/core/net/socket.hpp index 0b59dedf9..a7a259fa2 100644 --- a/src/core/net/socket.hpp +++ b/src/core/net/socket.hpp @@ -295,6 +295,14 @@ public: */ const Address &GetAddress(void) const { return *static_cast(&mAddress); } + /** + * This method sets the IPv6 address. + * + * @param[in] aAddress The IPv6 address. + * + */ + void SetAddress(const Address &aAddress) { mAddress = aAddress; } + /** * This method returns the socket address port number. * diff --git a/src/core/net/srp_client.cpp b/src/core/net/srp_client.cpp index c44c45c58..d4874a0eb 100644 --- a/src/core/net/srp_client.cpp +++ b/src/core/net/srp_client.cpp @@ -150,6 +150,7 @@ Client::Client(Instance &aInstance) #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE , mAutoStartModeEnabled(kAutoStartDefaultMode) , mAutoStartDidSelectServer(false) + , mAutoStartIsUsingAnycastAddress(false) #endif #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE , mServiceKeyRecordEnabled(false) @@ -165,6 +166,7 @@ Client::Client(Instance &aInstance) #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE , mAutoStartCallback(nullptr) , mAutoStartContext(nullptr) + , mServerSequenceNumber(0) #endif , mDomainName(kDefaultDomainName) , mTimer(aInstance, Client::HandleTimer) @@ -311,7 +313,7 @@ void Client::HandleNotifierEvents(Events aEvents) } #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE - if (aEvents.Contains(kEventThreadNetdataChanged)) + if (aEvents.ContainsAny(kEventThreadNetdataChanged | kEventThreadMeshLocalAddrChanged)) { ProcessAutoStart(); } @@ -1510,51 +1512,84 @@ exit: void Client::ProcessAutoStart(void) { - uint16_t numServers = 0; - NetworkData::Service::SrpServer::Info selectedServer; - NetworkData::Service::SrpServer::Info server; - NetworkData::Service::Manager::Iterator iterator; + Ip6::SockAddr serverSockAddr; + bool serverIsAnycast = false; + NetworkData::Service::DnsSrpAnycast::Info anycastInfo; VerifyOrExit(mAutoStartModeEnabled); - // If the client is not running we check if there is any SRP sever - // info in Network Data and select one randomly and then start the - // client. If the client is already running with a server that was - // selected by the auto-start feature, we verify that the selected - // server is still present in the Network Data. + serverSockAddr.Clear(); + + // If the SRP client is not running and auto start mode is + // enabled, we check if we can find any SRP server info in the + // Thread Network Data. If it is already running and the server + // was chosen by the auto-start feature, then we ensure that the + // selected server is still present in the Network Data. + // + // Two types of "DNS/SRP Service" entries can be present in + // Network Data, "DNS/SRP Service Anycast Address" model and + // "DNS/SRP Service Unicast" model. The Anycast entries are + // preferred over the Unicast entries. VerifyOrExit(!IsRunning() || mAutoStartDidSelectServer); - while (Get().GetNextSrpServerInfo(iterator, server) == kErrorNone) + // Now `IsRunning()` implies `mAutoStartDidSelectServer`. + + if (Get().FindPreferredDnsSrpAnycastInfo(anycastInfo) == kErrorNone) { - numServers++; - - // Choose a server randomly (with uniform distribution) from - // the list of servers. As we iterate through server entries, - // with probability `1/numServers`, we choose to switch the - // current selected server with the new entry. This approach - // results in a uniform/same probability of selection among - // all server entries. - - if ((numServers == 1) || (Random::NonCrypto::GetUint16InRange(0, numServers) == 0)) + if (IsRunning() && mAutoStartIsUsingAnycastAddress && (mServerSequenceNumber == anycastInfo.mSequenceNumber) && + (GetServerAddress().GetAddress() == anycastInfo.mAnycastAddress)) { - selectedServer = server; + // Client is already using the same anycast address. + ExitNow(); } - if (IsRunning() && mAutoStartDidSelectServer && (GetServerAddress() == server.mSockAddr)) + otLogInfoSrp("[client] Found anycast server %d", anycastInfo.mSequenceNumber); + + serverSockAddr.SetAddress(anycastInfo.mAnycastAddress); + serverSockAddr.SetPort(kAnycastServerPort); + mServerSequenceNumber = anycastInfo.mSequenceNumber; + serverIsAnycast = true; + } + else + { + uint16_t numServers = 0; + NetworkData::Service::DnsSrpUnicast::Info unicastInfo; + NetworkData::Service::Manager::Iterator iterator; + + while (Get().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone) { - ExitNow(); + if (IsRunning() && !mAutoStartIsUsingAnycastAddress && (GetServerAddress() == unicastInfo.mSockAddr)) + { + ExitNow(); + } + + numServers++; + + // Choose a server randomly (with uniform distribution) from + // the list of servers. As we iterate through server entries, + // with probability `1/numServers`, we choose to switch the + // current selected server with the new entry. This approach + // results in a uniform/same probability of selection among + // all server entries. + + if ((numServers == 1) || (Random::NonCrypto::GetUint16InRange(0, numServers) == 0)) + { + serverSockAddr = unicastInfo.mSockAddr; + serverIsAnycast = false; + } } } if (IsRunning()) { - otLogInfoSrp("[client] Server %s is no longer present in net data", GetServerAddress().ToString().AsCString()); Stop(kRequesterAuto); } - VerifyOrExit(numServers > 0); - IgnoreError(Start(selectedServer.mSockAddr, kRequesterAuto)); + VerifyOrExit(!serverSockAddr.GetAddress().IsUnspecified()); + + mAutoStartIsUsingAnycastAddress = serverIsAnycast; + IgnoreError(Start(serverSockAddr, kRequesterAuto)); exit: return; diff --git a/src/core/net/srp_client.hpp b/src/core/net/srp_client.hpp index 2feb835e6..cf67d2deb 100644 --- a/src/core/net/srp_client.hpp +++ b/src/core/net/srp_client.hpp @@ -710,6 +710,11 @@ private: kAutoStartDefaultMode = OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE, }; + enum : uint16_t + { + kAnycastServerPort = 53, // Port number to use when server is discovered using "network data anycast service". + }; + // This enumeration type is used by the private `Start()` and // `Stop()` methods to indicate whether it is being requested by the // user or by the auto-start feature. @@ -794,6 +799,7 @@ private: #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE bool mAutoStartModeEnabled : 1; bool mAutoStartDidSelectServer : 1; + bool mAutoStartIsUsingAnycastAddress : 1; #endif #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE bool mServiceKeyRecordEnabled : 1; @@ -815,6 +821,7 @@ private: #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE AutoStartCallback mAutoStartCallback; void * mAutoStartContext; + uint8_t mServerSequenceNumber; #endif const char * mDomainName; diff --git a/src/core/net/srp_server.cpp b/src/core/net/srp_server.cpp index 875d088d7..c78ae87f5 100644 --- a/src/core/net/srp_server.cpp +++ b/src/core/net/srp_server.cpp @@ -506,18 +506,17 @@ exit: Error Server::PublishServerData(void) { - NetworkData::Service::SrpServer::ServerData serverData; + NetworkData::Service::DnsSrpUnicast::ServerData serverData(Get().GetMeshLocal64(), + mSocket.GetSockName().GetPort()); OT_ASSERT(mSocket.IsBound()); - serverData.SetPort(mSocket.GetSockName().GetPort()); - - return Get().Add(serverData); + return Get().Add(serverData); } void Server::UnpublishServerData(void) { - Error error = Get().Remove(); + Error error = Get().Remove(); if (error != kErrorNone) { diff --git a/src/core/thread/network_data_service.cpp b/src/core/thread/network_data_service.cpp index 0d56702bb..37c8d1187 100644 --- a/src/core/thread/network_data_service.cpp +++ b/src/core/thread/network_data_service.cpp @@ -43,18 +43,27 @@ namespace ot { namespace NetworkData { namespace Service { +// Definitions of static const class member variables to allow ODR-use +// (One Definition Rule), e.g., to get address of `kServiceData`. + +#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) +const uint8_t BackboneRouter::kServiceData; +#endif +const uint8_t DnsSrpUnicast::kServiceData; + #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE -Error Manager::AddService(uint8_t aServiceNumber, +Error Manager::AddService(const void *aServiceData, + uint8_t aServiceDataLength, bool aServerStable, const void *aServerData, uint8_t aServerDataLength) { Error error; - SuccessOrExit(error = Get().AddService(kThreadEnterpriseNumber, &aServiceNumber, sizeof(aServiceNumber), - aServerStable, reinterpret_cast(aServerData), - aServerDataLength)); + SuccessOrExit(error = Get().AddService( + kThreadEnterpriseNumber, reinterpret_cast(aServiceData), aServiceDataLength, + aServerStable, reinterpret_cast(aServerData), aServerDataLength)); Get().HandleServerDataUpdated(); @@ -62,11 +71,12 @@ exit: return error; } -Error Manager::RemoveService(uint8_t aServiceNumber) +Error Manager::RemoveService(const void *aServiceData, uint8_t aServiceDataLength) { Error error; - SuccessOrExit(error = Get().RemoveService(kThreadEnterpriseNumber, &aServiceNumber, sizeof(aServiceNumber))); + SuccessOrExit(error = Get().RemoveService( + kThreadEnterpriseNumber, reinterpret_cast(aServiceData), aServiceDataLength)); Get().HandleServerDataUpdated(); exit: @@ -75,26 +85,27 @@ exit: #endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE -Error Manager::GetServiceId(uint8_t aServiceNumber, bool aServerStable, uint8_t &aServiceId) const +Error Manager::GetServiceId(const void *aServiceData, + uint8_t aServiceDataLength, + bool aServerStable, + uint8_t & aServiceId) const { - return Get().GetServiceId(kThreadEnterpriseNumber, &aServiceNumber, sizeof(aServiceNumber), aServerStable, - aServiceId); + return Get().GetServiceId(kThreadEnterpriseNumber, reinterpret_cast(aServiceData), + aServiceDataLength, aServerStable, aServiceId); } #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) void Manager::GetBackboneRouterPrimary(ot::BackboneRouter::BackboneRouterConfig &aConfig) const { - const uint8_t serviceData = BackboneRouter::kServiceNumber; const ServerTlv * rvalServerTlv = nullptr; const BackboneRouter::ServerData *rvalServerData = nullptr; Iterator iterator; aConfig.mServer16 = Mac::kShortAddrInvalid; - iterator.mServiceTlv = Get().FindService(kThreadEnterpriseNumber, &serviceData, sizeof(serviceData)); - - VerifyOrExit(iterator.mServiceTlv != nullptr); + iterator.mServiceTlv = Get().FindService(kThreadEnterpriseNumber, &BackboneRouter::kServiceData, + sizeof(BackboneRouter::kServiceData)); while (IterateToNextServer(iterator) == kErrorNone) { @@ -132,38 +143,106 @@ exit: #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) -Error Manager::GetNextSrpServerInfo(Iterator &aIterator, SrpServer::Info &aInfo) const +Error Manager::GetNextDnsSrpAnycastInfo(Iterator &aIterator, DnsSrpAnycast::Info &aInfo) const { - Error error = kErrorNotFound; + Error error = kErrorNone; + uint8_t serviceData = DnsSrpAnycast::kServiceNumber; + const ServiceTlv *tlv = aIterator.mServiceTlv; - if (aIterator.mServiceTlv == nullptr) + do { - const uint8_t serviceData = SrpServer::kServiceNumber; + tlv = Get().FindNextMatchingService(tlv, kThreadEnterpriseNumber, &serviceData, sizeof(serviceData)); + VerifyOrExit(tlv != nullptr, error = kErrorNotFound); - aIterator.mServiceTlv = Get().FindService(kThreadEnterpriseNumber, &serviceData, sizeof(serviceData)); - VerifyOrExit(aIterator.mServiceTlv != nullptr); - } - else + } while (tlv->GetServiceDataLength() < sizeof(DnsSrpAnycast::ServiceData)); + + aInfo.mAnycastAddress.SetToAnycastLocator(Get().GetMeshLocalPrefix(), + Mle::Mle::ServiceAlocFromId(tlv->GetServiceId())); + aInfo.mSequenceNumber = + reinterpret_cast(tlv->GetServiceData())->GetSequenceNumber(); + + aIterator.mServiceTlv = tlv; + +exit: + return error; +} + +Error Manager::FindPreferredDnsSrpAnycastInfo(DnsSrpAnycast::Info &aInfo) const +{ + Iterator iterator; + DnsSrpAnycast::Info info; + bool found = false; + + while (GetNextDnsSrpAnycastInfo(iterator, info) == kErrorNone) { - VerifyOrExit(aIterator.mServerSubTlv != nullptr); - } - - while ((error = IterateToNextServer(aIterator)) == kErrorNone) - { - const SrpServer::ServerData *serverData; - - if (aIterator.mServerSubTlv->GetServerDataLength() < sizeof(SrpServer::ServerData)) + if (!found || info.IsSequenceNumberAheadOf(aInfo)) { - continue; + aInfo = info; + found = true; + } + } + + return found ? kErrorNone : kErrorNotFound; +} + +Error Manager::GetNextDnsSrpUnicastInfo(Iterator &aIterator, DnsSrpUnicast::Info &aInfo) const +{ + Error error = kErrorNone; + + while (true) + { + // Process Server sub-TLVs in the current Service TLV. + + while (IterateToNextServer(aIterator) == kErrorNone) + { + // Server sub-TLV can contain address and port info + // (then we parse and return the info), or it can be + // empty (then we skip over it). + + if (aIterator.mServerSubTlv->GetServerDataLength() >= sizeof(DnsSrpUnicast::ServerData)) + { + const DnsSrpUnicast::ServerData *serverData = + reinterpret_cast(aIterator.mServerSubTlv->GetServerData()); + + aInfo.mSockAddr.SetAddress(serverData->GetAddress()); + aInfo.mSockAddr.SetPort(serverData->GetPort()); + ExitNow(); + } + + if (aIterator.mServerSubTlv->GetServerDataLength() == sizeof(uint16_t)) + { + // Handle the case where the server TLV data only + // contains a port number and use the RLOC as the + // IPv6 address. + aInfo.mSockAddr.GetAddress().SetToRoutingLocator(Get().GetMeshLocalPrefix(), + aIterator.mServerSubTlv->GetServer16()); + aInfo.mSockAddr.SetPort(Encoding::BigEndian::ReadUint16(aIterator.mServerSubTlv->GetServerData())); + ExitNow(); + } } - serverData = reinterpret_cast(aIterator.mServerSubTlv->GetServerData()); + // Find the next matching Service TLV. - aInfo.mRloc16 = aIterator.mServerSubTlv->GetServer16(); - aInfo.mSockAddr.GetAddress().SetToRoutingLocator(Get().GetMeshLocalPrefix(), aInfo.mRloc16); - aInfo.mSockAddr.SetPort(serverData->GetPort()); + aIterator.mServiceTlv = + Get().FindNextMatchingService(aIterator.mServiceTlv, kThreadEnterpriseNumber, + &DnsSrpUnicast::kServiceData, sizeof(DnsSrpUnicast::kServiceData)); - break; + VerifyOrExit(aIterator.mServiceTlv != nullptr, error = kErrorNotFound); + + if (aIterator.mServiceTlv->GetServiceDataLength() >= sizeof(DnsSrpUnicast::ServiceData)) + { + // The Service TLV data contains the address and port info. + + const DnsSrpUnicast::ServiceData *serviceData; + + serviceData = reinterpret_cast(aIterator.mServiceTlv->GetServiceData()); + aInfo.mSockAddr.SetAddress(serviceData->GetAddress()); + aInfo.mSockAddr.SetPort(serviceData->GetPort()); + ExitNow(); + } + + // Go back to the start of `while (true)` loop to + // process the Server sub-TLVs in the new Service TLV. } exit: @@ -172,13 +251,22 @@ exit: Error Manager::IterateToNextServer(Iterator &aIterator) const { - const NetworkDataTlv *start; + Error error = kErrorNotFound; - start = - (aIterator.mServerSubTlv != nullptr) ? aIterator.mServerSubTlv->GetNext() : aIterator.mServiceTlv->GetSubTlvs(); - aIterator.mServerSubTlv = NetworkData::FindTlv(start, aIterator.mServiceTlv->GetNext()); + VerifyOrExit(aIterator.mServiceTlv != nullptr); - return (aIterator.mServerSubTlv != nullptr) ? kErrorNone : kErrorNotFound; + aIterator.mServerSubTlv = NetworkData::FindTlv( + /* aStart */ (aIterator.mServerSubTlv != nullptr) ? aIterator.mServerSubTlv->GetNext() + : aIterator.mServiceTlv->GetSubTlvs(), + /* aEnd */ aIterator.mServiceTlv->GetNext()); + + if (aIterator.mServerSubTlv != nullptr) + { + error = kErrorNone; + } + +exit: + return error; } } // namespace Service diff --git a/src/core/thread/network_data_service.hpp b/src/core/thread/network_data_service.hpp index 313b2a865..ab372be37 100644 --- a/src/core/thread/network_data_service.hpp +++ b/src/core/thread/network_data_service.hpp @@ -66,10 +66,13 @@ enum : uint32_t class BackboneRouter { public: - enum : uint8_t - { - kServiceNumber = 0x01, ///< Backbone Router service data number (THREAD_SERVICE_DATA_BBR). - }; + /** + * This constant variable represents the Backbone Router service data. + * + * The service data contains only the service number (THREAD_SERVICE_DATA_BBR) as a single byte. + * + */ + static const uint8_t kServiceData = 0x01; /** * This class implements the generation and parsing of "Backbone Router Service" server data. @@ -150,63 +153,224 @@ public: #endif // #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) /** - * This type implements Thread Network Data "SRP Server Service" server data generation and parsing. + * This type implements Thread Network Data "DNS/SRP Service Anycast Address" generation and parsing. * */ -class SrpServer +class DnsSrpAnycast { public: enum : uint8_t { - kServiceNumber = OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_NUMBER, ///< SRP Sever Service number + kServiceNumber = 0x5c, ///< The service number of a `DnsSrpAnycast` entry. }; /** - * This structure represents information about an SRP server (from "SRP Server Service" Server entries). + * This structure represents information about an DNS/SRP server parsed from related Network Data service entries. * */ struct Info { - Ip6::SockAddr mSockAddr; ///< The SRP server address (IPv6 address and port number). - uint16_t mRloc16; ///< The RLOC16 of SRP server. + /** + * This method indicates whether or not the sequence number from the current `Info` is ahead of (more recent + * than) the sequence number from another `Info`. + * + * The sequence numbers comparison follows the Serial Number Arithmetic logic from RFC-1982. It is semantically + * equivalent to `GetSequenceNumber() > aOther.GetSequenceNumber()` while handling roll-over of the `uint8_t` + * values. + * + * @param[in] aOther The other `Info` to compare with. + * + * @retval TRUE The `Info` is ahead of @p aOther. + * @retval FALSE The `Info` is not ahead of @p aOther. + * + */ + bool IsSequenceNumberAheadOf(const Info &aOther) const + { + return (((aOther.mSequenceNumber - mSequenceNumber) & (1U << 7)) != 0); + } + + Ip6::Address mAnycastAddress; ///< The anycast address associated with the DNS/SRP servers. + uint8_t mSequenceNumber; ///< Sequence number used to notify SRP client if they need to re-register. }; /** - * This class implements generation and parsing of "SRP Server Service" server data. + * This class represents the "DNS/SRP Service (Anycast)" service data. + * + */ + OT_TOOL_PACKED_BEGIN + class ServiceData + { + public: + /** + * This constructor initializes the `ServiceData` object. + * + * @param[in] aSequenceNumber The sequence number of "DNS/SRP server" service. + * + */ + explicit ServiceData(uint8_t aSequenceNumber) + : mServiceNumber(kServiceNumber) + , mSequenceNumber(aSequenceNumber) + { + OT_UNUSED_VARIABLE(mServiceNumber); + } + + /** + * This method returns the length (in bytes) of service data. + * + * @returns The data length in bytes. + * + */ + uint8_t GetLength(void) const { return sizeof(ServiceData); } + + /** + * This method returns the sequence number. + * + * @returns The sequence number. + * + */ + uint8_t GetSequenceNumber(void) const { return mSequenceNumber; } + + private: + uint8_t mServiceNumber; + uint8_t mSequenceNumber; + } OT_TOOL_PACKED_END; + + DnsSrpAnycast(void) = delete; +}; + +/** + * This type implements Thread Network Data DNS/SRP Service (Unicast Address) generation and parsing. + * + */ +class DnsSrpUnicast +{ +public: + enum : uint8_t + { + kServiceNumber = 0x5d, ///< The service number of `DnsSrpUnicast` entry. + }; + + /** + * This constant variable represents the short version of service data. + * + * The short version of service data contains only service number as a single byte. + * + */ + static const uint8_t kServiceData = kServiceNumber; + + /** + * This structure represents information about an DNS/SRP server parsed from related Network Data service entries. + * + */ + struct Info + { + Ip6::SockAddr mSockAddr; ///< The socket address (IPv6 address and port) of the DNS/SRP server. + }; + + /** + * This class represents long version of "DNS/SRP Service (Unicast)" service data. + * + */ + OT_TOOL_PACKED_BEGIN + class ServiceData + { + public: + /** + * This constructor initializes the `ServiceData` object. + * + * @param[in] aAddress The IPv6 address of DNS/SRP server. + * @param[in] aPort The port number of DNS/SRP server. + * + */ + explicit ServiceData(const Ip6::Address &aAddress, uint16_t aPort) + : mServiceNumber(kServiceNumber) + , mAddress(aAddress) + , mPort(HostSwap16(aPort)) + { + OT_UNUSED_VARIABLE(mServiceNumber); + } + + /** + * This method returns the length (in bytes) of service data. + * + * @returns The data length in bytes. + * + */ + uint8_t GetLength(void) const { return sizeof(ServiceData); } + + /** + * This method returns the IPv6 address. + * + * @returns The IPv6 address + * + */ + const Ip6::Address &GetAddress(void) const { return mAddress; } + + /** + * This method returns the port number. + * + * @returns The port number. + * + */ + uint16_t GetPort(void) const { return HostSwap16(mPort); } + + private: + uint8_t mServiceNumber; + Ip6::Address mAddress; + uint16_t mPort; + } OT_TOOL_PACKED_END; + + /** + * This class represents long version of "DNS/SRP Service (Unicast)" server data. * */ OT_TOOL_PACKED_BEGIN class ServerData { public: - /** This method returns the length (in bytes) of server data. + /** + * This constructor initializes the `ServerData` object. * - * @returns The server data length in bytes. + * @param[in] aAddress The IPv6 address of DNS/SRP server. + * @param[in] aPort The port number of DNS/SRP server. + * + */ + ServerData(const Ip6::Address &aAddress, uint16_t aPort) + : mAddress(aAddress) + , mPort(HostSwap16(aPort)) + { + } + + /** + * This method returns the length (in bytes) of server data. + * + * @returns The data length in bytes. * */ uint8_t GetLength(void) const { return sizeof(ServerData); } /** - * This method returns the port number being used by the SRP server. + * This method returns the IPv6 address. * - * @return The port number of SPR server. + * @returns The IPv6 address + * + */ + const Ip6::Address &GetAddress(void) const { return mAddress; } + + /** + * This method returns the port number. + * + * @returns The port number. * */ uint16_t GetPort(void) const { return HostSwap16(mPort); } - /** - * This method sets the SRP port number in `ServerData`. - * - * @param[in] aPort The port number of SRP server. - * - */ - void SetPort(uint16_t aPort) { mPort = HostSwap16(aPort); } - private: - uint16_t mPort; + Ip6::Address mAddress; + uint16_t mPort; } OT_TOOL_PACKED_END; - SrpServer(void) = delete; + DnsSrpUnicast(void) = delete; }; /** @@ -258,8 +422,11 @@ public: * When successfully added, this method also invokes `Notifier::HandleServerDataUpdated()` to register the changes * in local Network Data with leader. * + * This version of `Add()` is intended for use with a `ServiceType` that has a constant service data + * format with a non-empty and potentially non-const server data format (provided as input parameter). + * * The template type `ServiceType` has the following requirements: - * - It MUST have a constant `ServiceType::kServiceNumber` specifying the service number. + * - It MUST have a constant variable `ServiceType::kServiceData` specifying the service data. * - It MUST define nested type `ServiceType::ServerData` representing the server data (and its format). * - The `ServiceType::ServerData` MUST provide `GetLength()` method returning the length of server data. * @@ -275,17 +442,49 @@ public: template Error Add(const typename ServiceType::ServerData &aServerData, bool aServerStable = true) { - return AddService(ServiceType::kServiceNumber, aServerStable, &aServerData, aServerData.GetLength()); + return AddService(&ServiceType::kServiceData, sizeof(ServiceType::kServiceData), aServerStable, &aServerData, + aServerData.GetLength()); } /** - * This method removed a Thread Service entry to the local Thread Network Data. + * This method adds a Thread Service entry to the local Thread Network Data. + * + * When successfully added, this method also invokes `Notifier::HandleServerDataUpdated()` to register the changes + * in local Network Data with leader. + * + * This version of `Add()` is intended for use with a `ServiceType` that has a non-const service data + * format (provided as input parameter) with an empty server data. + * + * The template type `ServiceType` has the following requirements: + * - It MUST define nested type `ServiceType::ServiceData` representing the service data (and its format). + * - The `ServiceType::ServiceData` MUST provide `GetLength()` method returning the length of service data. + * + * @tparam ServiceType The service type to be added. + * + * @param[in] aServiceData The service data. + * @param[in] aServerStable The Stable flag value for Server TLV. + * + * @retval kErrorNone Successfully added the Service entry. + * @retval kErrorNoBufs Insufficient space to add the Service entry. + * + */ + template + Error Add(const typename ServiceType::ServiceData &aServiceData, bool aServerStable = true) + { + return AddService(&aServiceData, aServiceData.GetLength(), aServerStable, nullptr, 0); + } + + /** + * This method removes a Thread Service entry from the local Thread Network Data. * * When successfully removed, this method also invokes `Notifier::HandleServerDataUpdated()` to register the * changes in local Network Data with leader. * + * This version of `Remove()` is intended for use with a `ServiceType` that has a constant service data + * format. + * * The template type `ServiceType` has the following requirements: - * - It MUST have a constant `ServiceType::kServiceNumber` specifying the service number. + * - It MUST have a constant variable `ServiceType::kServiceData` specifying the service data. * * @tparam ServiceType The service type to be removed. * @@ -293,9 +492,36 @@ public: * @retval kErrorNotFound Could not find the Service entry. * */ - template Error Remove(void) { return RemoveService(ServiceType::kServiceNumber); } + template Error Remove(void) + { + return RemoveService(&ServiceType::kServiceData, sizeof(ServiceType::kServiceData)); + } -#endif + /** + * This method removes a Thread Service entry from the local Thread Network Data. + * + * When successfully removed, this method also invokes `Notifier::HandleServerDataUpdated()` to register the + * changes in local Network Data with leader. + * + * This version of `Remove()` is intended for use with a `ServiceType` that has a non-const service data + * format (provided as input parameter). + * + * The template type `ServiceType` has the following requirements: + * - It MUST define nested type `ServiceType::ServiceData` representing the service data (and its format). + * - The `ServiceType::ServiceData` MUST provide `GetLength()` method returning the length of service data. + * + * @tparam ServiceType The service type to be removed. + * + * @retval kErrorNone Successfully removed the Service entry. + * @retval kErrorNotFound Could not find the Service entry. + * + */ + template Error Remove(const typename ServiceType::ServiceData &aServiceData) + { + return RemoveService(&aServiceData, aServiceData.GetLength()); + } + +#endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE /** * This method gets the Service ID for the specified service from Thread Network Data. @@ -314,7 +540,7 @@ public: */ template Error GetServiceId(bool aServerStable, uint8_t &aServiceId) const { - return GetServiceId(ServiceType::kServiceNumber, aServerStable, aServiceId); + return GetServiceId(&ServiceType::kServiceData, sizeof(ServiceType::kServiceData), aServerStable, aServiceId); } #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) @@ -328,27 +554,64 @@ public: #endif /** - * This method gets the next SRP server info from the Thread Network Data "SRP Server Service" entries. + * This method gets the next DNS/SRP info from the Thread Network Data "DNS/SRP Service Anycast Address" entries. * - * This method allows caller to iterate through all server entries for Network Data "SRP Server Service". To get - * the first entry @p aIterator should be cleared (e.g., a new instance of `Iterator` or calling `Clear()` method). + * To get the first entry, @p aIterator should be cleared (e.g., a new instance of `Iterator` or calling `Clear()` + * method). * * @param[inout] aIterator A reference to an iterator. - * @param[out] aInfo A reference to `SrpServer::Info` to return the next SRP server info. + * @param[out] aInfo A reference to `DnsSrpAnycast::Info` to return the info. * - * @retval kErrorNone Successfully got the next SRP server info. @p aInfo and @p aIterator are updated. - * @retval kErrorNotFound No more SRP server entries in Network Data. + * @retval kErrorNone Successfully got the next info. @p aInfo and @p aIterator are updated. + * @retval kErrorNotFound No more matching entries in the Network Data. * */ - Error GetNextSrpServerInfo(Iterator &aIterator, SrpServer::Info &aInfo) const; + Error GetNextDnsSrpAnycastInfo(Iterator &aIterator, DnsSrpAnycast::Info &aInfo) const; + + /** + * This method finds the preferred DNS/SRP info among all the Thread Network Data "DNS/SRP Service Anycast Address" + * entries. + * + * The preferred entry is determined based on the sequence number value where a larger value (in the sense + * specified by Serial Number Arithmetic logic in RFC-1982) is considered more recent and therefore preferred. + * + * @param[out] aInfo A reference to `DnsSrpAnycast::Info` to return the info. + * + * @retval kErrorNone Successfully found the preferred info. @p aInfo is updated. + * @retval kErrorNotFound No "DNS/SRP Service Anycast" entry in Network Data. + * + */ + Error FindPreferredDnsSrpAnycastInfo(DnsSrpAnycast::Info &aInfo) const; + + /** + * This method gets the next DNS/SRP info from the Thread Network Data "DNS/SRP Service Unicast Address" entries. + * + * To get the first entry @p aIterator should be cleared (e.g., a new instance of `Iterator` or calling `Clear()` + * method). + * + * @param[inout] aIterator A reference to an iterator. + * @param[out] aInfo A reference to `DnsSrpUnicast::Info` to return the info. + * + * @retval kErrorNone Successfully got the next info. @p aInfo and @p aIterator are updated. + * @retval kErrorNotFound No more matching entries in the Network Data. + * + */ + Error GetNextDnsSrpUnicastInfo(Iterator &aIterator, DnsSrpUnicast::Info &aInfo) const; private: #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE - Error AddService(uint8_t aServiceNumber, bool aServerStable, const void *aServerData, uint8_t aServerDataLength); - Error RemoveService(uint8_t aServiceNumber); + Error AddService(const void *aServiceData, + uint8_t aServiceDataLength, + bool aServerStable, + const void *aServerData, + uint8_t aServerDataLength); + Error RemoveService(const void *aServiceData, uint8_t aServiceDataLength); #endif - Error GetServiceId(uint8_t aServiceNumber, bool aServerStable, uint8_t &aServiceId) const; + Error GetServiceId(const void *aServiceData, + uint8_t aServiceDataLength, + bool aServerStable, + uint8_t & aServiceId) const; Error IterateToNextServer(Iterator &aIterator) const; }; diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index a2879e863..0bc1af0ff 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -931,8 +931,9 @@ class NodeImpl: # TODO: for now, we are using 0xfd as the SRP service data. # May use a dedicated bit flag for SRP server. if int(service[1], 16) == 0x5d: - # The SRP server data are 2-bytes UDP port number. - return int(service[2], 16) + # The SRP server data contains IPv6 address (16 bytes) + # followed by UDP port number. + return int(service[2][2 * 16:], 16) def srp_client_start(self, server_address, server_port): self.send_command(f'srp client start {server_address} {server_port}') diff --git a/tests/unit/test_network_data.cpp b/tests/unit/test_network_data.cpp index c0c15577a..73a650de8 100644 --- a/tests/unit/test_network_data.cpp +++ b/tests/unit/test_network_data.cpp @@ -30,7 +30,9 @@ #include "common/code_utils.hpp" #include "common/instance.hpp" +#include "thread/network_data_leader.hpp" #include "thread/network_data_local.hpp" +#include "thread/network_data_service.hpp" #include "test_platform.h" #include "test_util.hpp" @@ -312,6 +314,140 @@ void TestNetworkDataFindNextService(void) #endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE +void TestNetworkDataDsnSrpServices(void) +{ + class TestLeader : public Leader + { + public: + void Populate(const uint8_t *aTlvs, uint8_t aTlvsLength) + { + memcpy(mTlvs, aTlvs, aTlvsLength); + mLength = aTlvsLength; + } + }; + + ot::Instance *instance; + + printf("\n\n-------------------------------------------------"); + printf("\nTestNetworkDataDsnSrpServices()\n"); + + instance = testInitInstance(); + VerifyOrQuit(instance != nullptr, "Null OpenThread instance\n"); + + { + struct AnycastEntry + { + uint16_t mAloc16; + uint8_t mSequenceNumber; + + bool Matches(Service::DnsSrpAnycast::Info aInfo) const + { + VerifyOrQuit(aInfo.mAnycastAddress.GetIid().IsAnycastServiceLocator(), "Anycast address is invalid"); + + return (aInfo.mAnycastAddress.GetIid().GetLocator() == mAloc16) && + (aInfo.mSequenceNumber == mSequenceNumber); + } + }; + + struct UnicastEntry + { + const char *mAddress; + uint16_t mPort; + + bool Matches(Service::DnsSrpUnicast::Info aInfo) const + { + Ip6::SockAddr sockAddr; + + SuccessOrQuit(sockAddr.GetAddress().FromString(mAddress), "Ip6::Address::FromString() failed"); + sockAddr.SetPort(mPort); + + return (aInfo.mSockAddr == sockAddr); + } + }; + + const uint8_t kNetworkData[] = { + 0x0b, 0x08, 0x80, 0x02, 0x5c, 0x02, 0x0d, 0x02, 0x28, 0x00, 0x0b, 0x08, 0x81, 0x02, 0x5c, 0xff, 0x0d, 0x02, + 0x6c, 0x00, 0x0b, 0x09, 0x82, 0x02, 0x5c, 0x03, 0x0d, 0x03, 0x4c, 0x00, 0xaa, 0x0b, 0x35, 0x83, 0x13, 0x5d, + 0xfd, 0xde, 0xad, 0x00, 0xbe, 0xef, 0x00, 0x00, 0x2d, 0x0e, 0xc6, 0x27, 0x55, 0x56, 0x18, 0xd9, 0x12, 0x34, + 0x0d, 0x02, 0x00, 0x00, 0x0d, 0x14, 0x6c, 0x00, 0xfd, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0xab, 0xcd, 0x0d, 0x04, 0x28, 0x00, 0x56, 0x78, 0x0b, 0x23, 0x84, 0x01, + 0x5d, 0x0d, 0x02, 0x00, 0x00, 0x0d, 0x14, 0x4c, 0x00, 0xfd, 0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, + 0xf0, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0x00, 0x0e, 0x0d, 0x04, 0x6c, 0x00, 0xcd, 0x12, + }; + + const AnycastEntry kAnycastEntries[] = { + {0xfc10, 0x02}, + {0xfc11, 0xff}, + {0xfc12, 0x03}, + }; + + const UnicastEntry kUnicastEntries[] = { + {"fdde:ad00:beef:0:2d0e:c627:5556:18d9", 0x1234}, {"fd00:aabb:ccdd:eeff:11:2233:4455:6677", 0xabcd}, + {"fdde:ad00:beef:0:0:ff:fe00:2800", 0x5678}, {"fd00:1234:5678:9abc:def0:123:4567:89ab", 0x0e}, + {"fdde:ad00:beef:0:0:ff:fe00:6c00", 0xcd12}, + }; + + const uint8_t kPreferredAnycastEntryIndex = 2; + + Service::Manager & manager = instance->Get(); + Service::Manager::Iterator iterator; + Service::DnsSrpAnycast::Info anycastInfo; + Service::DnsSrpUnicast::Info unicastInfo; + + reinterpret_cast(instance->Get()).Populate(kNetworkData, sizeof(kNetworkData)); + + DumpBuffer("netdata", kNetworkData, sizeof(kNetworkData)); + + // Verify all the "DNS/SRP Anycast Service" entries in Network Data + + printf("\n- - - - - - - - - - - - - - - - - - - -"); + printf("\nDNS/SRP Anycast Service entries\n"); + + for (const AnycastEntry &entry : kAnycastEntries) + { + SuccessOrQuit(manager.GetNextDnsSrpAnycastInfo(iterator, anycastInfo), "GetNextDnsSrpAnycastInfo() failed"); + + printf("\nanycastInfo { %s, seq:%d }", anycastInfo.mAnycastAddress.ToString().AsCString(), + anycastInfo.mSequenceNumber); + + VerifyOrQuit(entry.Matches(anycastInfo), "GetNextDnsSrpAnycastInfo() returned incorrect info"); + } + + VerifyOrQuit(manager.GetNextDnsSrpAnycastInfo(iterator, anycastInfo) == kErrorNotFound, + "GetNextDnsSrpAnycastInfo() returned unexpected extra entry"); + + // Find the preferred "DNS/SRP Anycast Service" entries in Network Data + + SuccessOrQuit(manager.FindPreferredDnsSrpAnycastInfo(anycastInfo), "FindPreferredDnsSrpAnycastInfo() failed"); + + printf("\n\nPreferred anycastInfo { %s, seq:%d }", anycastInfo.mAnycastAddress.ToString().AsCString(), + anycastInfo.mSequenceNumber); + + VerifyOrQuit(kAnycastEntries[kPreferredAnycastEntryIndex].Matches(anycastInfo), + "FindPreferredDnsSrpAnycastInfo() returned invalid info"); + + printf("\n\n- - - - - - - - - - - - - - - - - - - -"); + printf("\nDNS/SRP Unicast Service entries\n"); + + iterator.Clear(); + + for (const UnicastEntry &entry : kUnicastEntries) + { + SuccessOrQuit(manager.GetNextDnsSrpUnicastInfo(iterator, unicastInfo), "GetNextDnsSrpUnicastInfo() failed"); + printf("\nunicastInfo %s", unicastInfo.mSockAddr.ToString().AsCString()); + + VerifyOrQuit(entry.Matches(unicastInfo), "GetNextDnsSrpUnicastInfo() returned incorrect info"); + } + + VerifyOrQuit(manager.GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNotFound, + "GetNextDnsSrpUnicastInfo() returned unexpected extra entry"); + + printf("\n"); + } + + testFreeInstance(instance); +} + } // namespace NetworkData } // namespace ot @@ -321,6 +457,7 @@ int main(void) #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE ot::NetworkData::TestNetworkDataFindNextService(); #endif + ot::NetworkData::TestNetworkDataDsnSrpServices(); printf("\nAll tests passed\n"); return 0;