diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 08fb6fcff..cb7d1709e 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (160) +#define OPENTHREAD_API_VERSION (161) /** * @addtogroup api-instance diff --git a/include/openthread/srp_server.h b/include/openthread/srp_server.h index bde0b642b..bd0302dba 100644 --- a/include/openthread/srp_server.h +++ b/include/openthread/srp_server.h @@ -142,6 +142,19 @@ typedef enum OT_SRP_SERVER_STATE_STOPPED = 2, ///< The SRP server is stopped. } otSrpServerState; +/** + * This enumeration represents the address mode used by the SRP server. + * + * Address mode specifies how the address and port number are determined by the SRP server and how this info is + * published in the Thread Network Data. + * + */ +typedef enum otSrpServerAddressMode +{ + OT_SRP_SREVER_ADDRESS_MODE_UNICAST = 0, ///< Unicast address mode. + OT_SRP_SERVER_ADDRESS_MODE_ANYCAST = 1, ///< Anycast address mode. +} otSrpServerAddressMode; + /** * This structure includes SRP server LEASE and KEY-LEASE configurations. * @@ -194,6 +207,52 @@ otError otSrpServerSetDomain(otInstance *aInstance, const char *aDomain); */ otSrpServerState otSrpServerGetState(otInstance *aInstance); +/** + * This function returns the address mode being used by the SRP server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns The SRP server's address mode. + * + */ +otSrpServerAddressMode otSrpServerGetAddressMode(otInstance *aInstance); + +/** + * This function sets the address mode to be used by the SRP server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMode The address mode to use. + * + * @retval OT_ERROR_NONE Successfully set the address mode. + * @retval OT_ERROR_INVALID_STATE The SRP server is enabled and the address mode cannot be changed. + * + */ +otError otSrpServerSetAddressMode(otInstance *aInstance, otSrpServerAddressMode aMode); + +/** + * This function returns the sequence number used with anycast address mode. + * + * The sequence number is included in "DNS/SRP Service Anycast Address" entry published in the Network Data. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns The anycast sequence number. + * + */ +uint8_t otSrpServerGetAnycastModeSequenceNumber(otInstance *aInstance); + +/** + * This function sets the sequence number used with anycast address mode. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aSequenceNumber The sequence number to use. + * + * @retval OT_ERROR_NONE Successfully set the address mode. + * @retval OT_ERROR_INVALID_STATE The SRP server is enabled and the sequence number cannot be changed. + * + */ +otError otSrpServerSetAnycastModeSequenceNumber(otInstance *aInstance, uint8_t aSequenceNumber); + /** * This function enables/disables the SRP server. * diff --git a/src/cli/README_SRP_SERVER.md b/src/cli/README_SRP_SERVER.md index f53a3ff6a..05cb9bc76 100644 --- a/src/cli/README_SRP_SERVER.md +++ b/src/cli/README_SRP_SERVER.md @@ -7,11 +7,13 @@ See [README_SRP.md](README_SRP.md). ## Command List - [help](#help) +- [addrmode](#addrmode) - [disable](#disable) - [domain](#domain) - [enable](#enable) - [host](#host) - [lease](#lease) +- [seqnum](#seqnum) - [service](#service) - [state](#state) @@ -25,17 +27,46 @@ Print SRP server help menu. ```bash > srp server help +addrmode disable domain enable help host lease +seqnum service state Done ``` +### addrmode + +Usage: `srp server addrmode [unicast|anycast]` + +Get or set the address mode used by the SRP server. + +Address mode specifies how the address and port number are determined by the SRP server and this is published in the Thread Network Data. + +Get the address mode. + +```bash +> srp server addrmode +unicast +Done +``` + +Set the address mode. + +```bash +> srp server addrmode anycast +Done + +> srp server addrmode +anycast +Done +``` + ### disable Usage: `srp server disable` @@ -116,6 +147,23 @@ Set LEASE and KEY-LEASE values. Done ``` +### seqnum + +Usage: `srp server seqnum []` + +Get or set the sequence number used with anycast address mode. + +The sequence number is included in "DNS/SRP Service Anycast Address" entry published in the Network Data. + +```bash +> srp server seqnum 20 +Done + +> srp server seqnum +20 +Done +``` + ### service Usage: `srp server service` diff --git a/src/cli/cli_srp_server.cpp b/src/cli/cli_srp_server.cpp index fbfe1a9ea..8809e845d 100644 --- a/src/cli/cli_srp_server.cpp +++ b/src/cli/cli_srp_server.cpp @@ -65,6 +65,37 @@ exit: return error; } +otError SrpServer::ProcessAddrMode(Arg aArgs[]) +{ + otError error = OT_ERROR_INVALID_ARGS; + + if (aArgs[0].IsEmpty()) + { + switch (otSrpServerGetAddressMode(mInterpreter.mInstance)) + { + case OT_SRP_SREVER_ADDRESS_MODE_UNICAST: + mInterpreter.OutputLine("unicast"); + break; + + case OT_SRP_SERVER_ADDRESS_MODE_ANYCAST: + mInterpreter.OutputLine("anycast"); + break; + } + + error = OT_ERROR_NONE; + } + else if (aArgs[0] == "unicast") + { + error = otSrpServerSetAddressMode(mInterpreter.mInstance, OT_SRP_SREVER_ADDRESS_MODE_UNICAST); + } + else if (aArgs[0] == "anycast") + { + error = otSrpServerSetAddressMode(mInterpreter.mInstance, OT_SRP_SERVER_ADDRESS_MODE_ANYCAST); + } + + return error; +} + otError SrpServer::ProcessDomain(Arg aArgs[]) { otError error = OT_ERROR_NONE; @@ -280,6 +311,26 @@ exit: return error; } +otError SrpServer::ProcessSeqNum(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + + if (aArgs[0].IsEmpty()) + { + mInterpreter.OutputLine("%u", otSrpServerGetAnycastModeSequenceNumber(mInterpreter.mInstance)); + } + else + { + uint8_t sequenceNumber; + + SuccessOrExit(error = aArgs[0].ParseAsUint8(sequenceNumber)); + error = otSrpServerSetAnycastModeSequenceNumber(mInterpreter.mInstance, sequenceNumber); + } + +exit: + return error; +} + otError SrpServer::ProcessHelp(Arg aArgs[]) { OT_UNUSED_VARIABLE(aArgs); diff --git a/src/cli/cli_srp_server.hpp b/src/cli/cli_srp_server.hpp index f394fbc7f..11cf5b442 100644 --- a/src/cli/cli_srp_server.hpp +++ b/src/cli/cli_srp_server.hpp @@ -86,6 +86,7 @@ private: otError (SrpServer::*mHandler)(Arg aArgs[]); }; + otError ProcessAddrMode(Arg aArgs[]); otError ProcessDomain(Arg aArgs[]); otError ProcessState(Arg aArgs[]); otError ProcessEnable(Arg aArgs[]); @@ -93,15 +94,17 @@ private: otError ProcessLease(Arg aArgs[]); otError ProcessHost(Arg aArgs[]); otError ProcessService(Arg aArgs[]); + otError ProcessSeqNum(Arg aArgs[]); otError ProcessHelp(Arg aArgs[]); void OutputHostAddresses(const otSrpServerHost *aHost); static constexpr Command sCommands[] = { - {"disable", &SrpServer::ProcessDisable}, {"domain", &SrpServer::ProcessDomain}, - {"enable", &SrpServer::ProcessEnable}, {"help", &SrpServer::ProcessHelp}, - {"host", &SrpServer::ProcessHost}, {"lease", &SrpServer::ProcessLease}, - {"service", &SrpServer::ProcessService}, {"state", &SrpServer::ProcessState}, + {"addrmode", &SrpServer::ProcessAddrMode}, {"disable", &SrpServer::ProcessDisable}, + {"domain", &SrpServer::ProcessDomain}, {"enable", &SrpServer::ProcessEnable}, + {"help", &SrpServer::ProcessHelp}, {"host", &SrpServer::ProcessHost}, + {"lease", &SrpServer::ProcessLease}, {"seqnum", &SrpServer::ProcessSeqNum}, + {"service", &SrpServer::ProcessService}, {"state", &SrpServer::ProcessState}, }; static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); diff --git a/src/core/api/srp_server_api.cpp b/src/core/api/srp_server_api.cpp index 554fc91b7..47e0034fe 100644 --- a/src/core/api/srp_server_api.cpp +++ b/src/core/api/srp_server_api.cpp @@ -63,6 +63,34 @@ otSrpServerState otSrpServerGetState(otInstance *aInstance) return static_cast(instance.Get().GetState()); } +otSrpServerAddressMode otSrpServerGetAddressMode(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return static_cast(instance.Get().GetAddressMode()); +} + +otError otSrpServerSetAddressMode(otInstance *aInstance, otSrpServerAddressMode aMode) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().SetAddressMode(static_cast(aMode)); +} + +uint8_t otSrpServerGetAnycastModeSequenceNumber(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().GetAnycastModeSequenceNumber(); +} + +otError otSrpServerSetAnycastModeSequenceNumber(otInstance *aInstance, uint8_t aSequenceNumber) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().SetAnycastModeSequenceNumber(aSequenceNumber); +} + void otSrpServerSetEnabled(otInstance *aInstance, bool aEnabled) { Instance &instance = *static_cast(aInstance); diff --git a/src/core/config/srp_server.h b/src/core/config/srp_server.h index 124a8ec6f..1b91068cb 100644 --- a/src/core/config/srp_server.h +++ b/src/core/config/srp_server.h @@ -45,6 +45,21 @@ #define OPENTHREAD_CONFIG_SRP_SERVER_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_SRP_SERVER_DEFAULT_ADDDRESS_MODE + * + * Specifies the default address mode used by the SRP server. + * + * The address mode specifies how address and port number are determined by the SRP server and how this info is + * published in the Thread Network Data. + * + * The value of this configuration should be from `otSrpServerAddressMode` enumeration. + * + */ +#ifndef OPENTHREAD_CONFIG_SRP_SERVER_DEFAULT_ADDDRESS_MODE +#define OPENTHREAD_CONFIG_SRP_SERVER_DEFAULT_ADDDRESS_MODE OT_SRP_SREVER_ADDRESS_MODE_UNICAST +#endif + /** * @def OPENTHREAD_CONFIG_SRP_SERVER_UDP_PORT_MIN * diff --git a/src/core/net/dnssd_server.cpp b/src/core/net/dnssd_server.cpp index d361ffe7a..6b044753b 100644 --- a/src/core/net/dnssd_server.cpp +++ b/src/core/net/dnssd_server.cpp @@ -71,6 +71,10 @@ Error Server::Start(void) SuccessOrExit(error = mSocket.Open(&Server::HandleUdpReceive, this)); SuccessOrExit(error = mSocket.Bind(kPort, kBindUnspecifiedNetif ? OT_NETIF_UNSPECIFIED : OT_NETIF_THREAD)); +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + Get().HandleDnssdServerStateChange(); +#endif + exit: otLogInfoDns("[server] started: %s", ErrorToString(error)); @@ -97,6 +101,10 @@ void Server::Stop(void) IgnoreError(mSocket.Close()); otLogInfoDns("[server] stopped"); + +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + Get().HandleDnssdServerStateChange(); +#endif } void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) @@ -107,13 +115,21 @@ void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessa void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { - Error error = kErrorNone; Header requestHeader; - SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), requestHeader)); - VerifyOrExit(requestHeader.GetType() == Header::kTypeQuery, error = kErrorDrop); +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + // We first let the `Srp::Server` process the received message. + // It returns `kErrorNone` to indicate that it successfully + // processed the message. + + VerifyOrExit(Get().HandleDnssdServerUdpReceive(aMessage, aMessageInfo) != kErrorNone); +#endif + + SuccessOrExit(aMessage.Read(aMessage.GetOffset(), requestHeader)); + VerifyOrExit(requestHeader.GetType() == Header::kTypeQuery); ProcessQuery(requestHeader, aMessage, aMessageInfo); + exit: return; } diff --git a/src/core/net/dnssd_server.hpp b/src/core/net/dnssd_server.hpp index 7b1bdcaa6..527a1ca3a 100644 --- a/src/core/net/dnssd_server.hpp +++ b/src/core/net/dnssd_server.hpp @@ -49,6 +49,11 @@ */ namespace ot { + +namespace Srp { +class Server; +} + namespace Dns { namespace ServiceDiscovery { @@ -58,6 +63,8 @@ namespace ServiceDiscovery { */ class Server : public InstanceLocator, private NonCopyable { + friend class Srp::Server; + public: /** * This enumeration specifies a DNS-SD query type. diff --git a/src/core/net/srp_server.cpp b/src/core/net/srp_server.cpp index f937259e4..0c1d6c9a2 100644 --- a/src/core/net/srp_server.cpp +++ b/src/core/net/srp_server.cpp @@ -89,6 +89,8 @@ Server::Server(Instance &aInstance) , mServiceUpdateId(Random::NonCrypto::GetUint32()) , mPort(kUdpPortMin) , mState(kStateDisabled) + , mAddressMode(kDefaultAddressMode) + , mAnycastSequenceNumber(0) , mHasRegisteredAnyService(false) { IgnoreError(SetDomain(kDefaultDomain)); @@ -100,6 +102,32 @@ void Server::SetServiceHandler(otSrpServerServiceUpdateHandler aServiceHandler, mServiceUpdateHandlerContext = aServiceHandlerContext; } +Error Server::SetAddressMode(AddressMode aMode) +{ + Error error = kErrorNone; + + VerifyOrExit(mState == kStateDisabled, error = kErrorInvalidState); + VerifyOrExit(mAddressMode != aMode); + otLogInfoSrp("[server] Address Mode: %s -> %s", AddressModeToString(mAddressMode), AddressModeToString(aMode)); + mAddressMode = aMode; + +exit: + return error; +} + +Error Server::SetAnycastModeSequenceNumber(uint8_t aSequenceNumber) +{ + Error error = kErrorNone; + + VerifyOrExit(mState == kStateDisabled, error = kErrorInvalidState); + mAnycastSequenceNumber = aSequenceNumber; + + otLogInfoSrp("[server] Set Anycast Address Mode Seq Number to %d", aSequenceNumber); + +exit: + return error; +} + void Server::SetEnabled(bool aEnabled) { if (aEnabled) @@ -107,15 +135,23 @@ void Server::SetEnabled(bool aEnabled) VerifyOrExit(mState == kStateDisabled); mState = kStateStopped; - // Select a port and then publish "DNS/SRP Unicast Address - // Service" in Thread Network Data using the device's - // mesh-local EID as the address. Then wait for callback - // `HandleNetDataPublisherEntryChange()` from to `Publisher` to - // start server operation when entry is published (i.e., added - // to the Network Data). + // Request publishing of "DNS/SRP Address Service" entry in the + // Thread Network Data based of `mAddressMode`. Then wait for + // callback `HandleNetDataPublisherEntryChange()` from the + // `Publisher` to start the SRP server. - SelectPort(); - Get().PublishDnsSrpServiceUnicast(mPort); + switch (mAddressMode) + { + case kAddressModeUnicast: + SelectPort(); + Get().PublishDnsSrpServiceUnicast(mPort); + break; + + case kAddressModeAnycast: + mPort = kAnycastAddressModePort; + Get().PublishDnsSrpServiceAnycast(mAnycastSequenceNumber); + break; + } } else { @@ -388,12 +424,12 @@ void Server::CommitSrpUpdate(Error aError, shouldFreeHost = false; #if OPENTHREAD_CONFIG_SRP_SERVER_PORT_SWITCH_ENABLE - if (!mHasRegisteredAnyService) + if (!mHasRegisteredAnyService && (mAddressMode == kAddressModeUnicast)) { Settings::SrpServerInfo info; mHasRegisteredAnyService = true; - info.SetPort(mSocket.mSockName.mPort); + info.SetPort(GetSocket().mSockName.mPort); IgnoreError(Get().Save(info)); } #endif @@ -442,25 +478,96 @@ void Server::SelectPort(void) void Server::Start(void) { - Error error = kErrorNone; - VerifyOrExit(mState == kStateStopped); mState = kStateRunning; - - SuccessOrExit(error = mSocket.Open(HandleUdpReceive, this)); - SuccessOrExit(error = mSocket.Bind(mPort, OT_NETIF_THREAD)); - + PrepareSocket(); otLogInfoSrp("[server] start listening on port %u", mPort); +exit: + return; +} + +void Server::PrepareSocket(void) +{ + Error error = kErrorNone; + +#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE + Ip6::Udp::Socket &dnsSocket = Get().mSocket; + + if (dnsSocket.GetSockName().GetPort() == mPort) + { + // If the DNS-SD socket matches our port number, we use the + // same socket so we close our own socket (in case it was + // open). `GetSocket()` will now return the DNS-SD socket. + + IgnoreError(mSocket.Close()); + ExitNow(); + } +#endif + + VerifyOrExit(!mSocket.IsOpen()); + SuccessOrExit(error = mSocket.Open(HandleUdpReceive, this)); + error = mSocket.Bind(mPort, OT_NETIF_THREAD); + exit: if (error != kErrorNone) { - otLogCritSrp("[server] failed to start: %s", ErrorToString(error)); + otLogCritSrp("[server] failed to prepare socket: %s", ErrorToString(error)); Stop(); } } +Ip6::Udp::Socket &Server::GetSocket(void) +{ + Ip6::Udp::Socket *socket = &mSocket; + +#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE + Ip6::Udp::Socket &dnsSocket = Get().mSocket; + + if (dnsSocket.GetSockName().GetPort() == mPort) + { + socket = &dnsSocket; + } +#endif + + return *socket; +} + +#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE + +void Server::HandleDnssdServerStateChange(void) +{ + // This is called from` Dns::ServiceDiscovery::Server` to notify + // that it has started or stopped. We check whether we need to + // share the socket. + + if (mState == kStateRunning) + { + PrepareSocket(); + } +} + +Error Server::HandleDnssdServerUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + // This is called from` Dns::ServiceDiscovery::Server` when a UDP + // message is received on its socket. We check whether we are + // sharing socket and if so we process the received message. We + // return `kErrorNone` to indicate that message was successfully + // processed by `Srp::Server`, otherwise `kErrorDrop` is returned. + + Error error = kErrorDrop; + + VerifyOrExit((mState == kStateRunning) && !mSocket.IsOpen()); + + error = ProcessMessage(aMessage, aMessageInfo); + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE + void Server::Stop(void) { VerifyOrExit(mState == kStateRunning); @@ -523,10 +630,10 @@ exit: return ret; } -void Server::HandleDnsUpdate(Message & aMessage, - const Ip6::MessageInfo & aMessageInfo, - const Dns::UpdateHeader &aDnsHeader, - uint16_t aOffset) +void Server::ProcessDnsUpdate(Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const Dns::UpdateHeader &aDnsHeader, + uint16_t aOffset) { Error error = kErrorNone; Dns::Zone zone; @@ -1056,7 +1163,7 @@ void Server::SendResponse(const Dns::UpdateHeader & aHeader, Message * response = nullptr; Dns::UpdateHeader header; - response = mSocket.NewMessage(0); + response = GetSocket().NewMessage(0); VerifyOrExit(response != nullptr, error = kErrorNoBufs); header.SetMessageId(aHeader.GetMessageId()); @@ -1065,7 +1172,7 @@ void Server::SendResponse(const Dns::UpdateHeader & aHeader, header.SetResponseCode(aResponseCode); SuccessOrExit(error = response->Append(header)); - SuccessOrExit(error = mSocket.SendTo(*response, aMessageInfo)); + SuccessOrExit(error = GetSocket().SendTo(*response, aMessageInfo)); if (aResponseCode != Dns::UpdateHeader::kResponseSuccess) { @@ -1095,7 +1202,7 @@ void Server::SendResponse(const Dns::UpdateHeader &aHeader, Dns::OptRecord optRecord; Dns::LeaseOption leaseOption; - response = mSocket.NewMessage(0); + response = GetSocket().NewMessage(0); VerifyOrExit(response != nullptr, error = kErrorNoBufs); header.SetMessageId(aHeader.GetMessageId()); @@ -1119,7 +1226,7 @@ void Server::SendResponse(const Dns::UpdateHeader &aHeader, leaseOption.SetKeyLeaseInterval(aKeyLease); SuccessOrExit(error = response->Append(leaseOption)); - SuccessOrExit(error = mSocket.SendTo(*response, aMessageInfo)); + SuccessOrExit(error = GetSocket().SendTo(*response, aMessageInfo)); otLogInfoSrp("[server] send response with granted lease: %u and key lease: %u", aLease, aKeyLease); @@ -1138,6 +1245,16 @@ void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessa } void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + Error error = ProcessMessage(aMessage, aMessageInfo); + + if (error != kErrorNone) + { + otLogInfoSrp("[server] failed to handle DNS message: %s", ErrorToString(error)); + } +} + +Error Server::ProcessMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { Error error; Dns::UpdateHeader dnsHeader; @@ -1152,7 +1269,7 @@ void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessag switch (dnsHeader.GetQueryType()) { case Dns::UpdateHeader::kQueryTypeUpdate: - HandleDnsUpdate(aMessage, aMessageInfo, dnsHeader, offset); + ProcessDnsUpdate(aMessage, aMessageInfo, dnsHeader, offset); break; default: error = kErrorDrop; @@ -1160,10 +1277,7 @@ void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessag } exit: - if (error != kErrorNone) - { - otLogInfoSrp("[server] failed to handle DNS message: %s", ErrorToString(error)); - } + return error; } void Server::HandleLeaseTimer(Timer &aTimer) @@ -1299,6 +1413,19 @@ void Server::HandleOutstandingUpdatesTimer(void) } } +const char *Server::AddressModeToString(AddressMode aMode) +{ + static const char *const kAddressModeStrings[] = { + "unicast", // (0) kAddressModeUnicast + "anycast", // (1) kAddressModeAnycast + }; + + static_assert(kAddressModeUnicast == 0, "kAddressModeUnicast value is incorrect"); + static_assert(kAddressModeAnycast == 1, "kAddressModeAnycast value is incorrect"); + + return kAddressModeStrings[aMode]; +} + //--------------------------------------------------------------------------------------------------------------------- // Server::Service diff --git a/src/core/net/srp_server.hpp b/src/core/net/srp_server.hpp index 357bb614f..caecf5ca4 100644 --- a/src/core/net/srp_server.hpp +++ b/src/core/net/srp_server.hpp @@ -68,6 +68,13 @@ #include "thread/network_data_publisher.hpp" namespace ot { + +namespace Dns { +namespace ServiceDiscovery { +class Server; +} +} // namespace Dns + namespace Srp { /** @@ -80,6 +87,7 @@ class Server : public InstanceLocator, private NonCopyable friend class UpdateMetadata; friend class Service; friend class Host; + friend class Dns::ServiceDiscovery::Server; public: static constexpr uint16_t kUdpPortMin = OPENTHREAD_CONFIG_SRP_SERVER_UDP_PORT_MIN; ///< The reserved min port. @@ -93,6 +101,19 @@ public: */ typedef otSrpServerServiceUpdateId ServiceUpdateId; + /** + * This enumeration represents the address mode used by the SRP server. + * + * Address mode specifies how the address and port number are determined by the SRP server and how this info ins + * published in the Thread Network Data. + * + */ + enum AddressMode : uint8_t + { + kAddressModeUnicast = OT_SRP_SREVER_ADDRESS_MODE_UNICAST, ///< Unicast address mode. + kAddressModeAnycast = OT_SRP_SERVER_ADDRESS_MODE_ANYCAST, ///< Anycast address mode. + }; + class Host; enum State : uint8_t @@ -599,6 +620,46 @@ public: */ Error SetDomain(const char *aDomain); + /** + * This method returns the address mode being used by the SRP server. + * + * @returns The SRP server's address mode. + * + */ + AddressMode GetAddressMode(void) const { return mAddressMode; } + + /** + * This method sets the address mode to be used by the SRP server. + * + * @param[in] aMode The address mode to use. + * + * @retval kErrorNone Successfully set the address mode. + * @retval kErrorInvalidState The SRP server is enabled and the address mode cannot be changed. + * + */ + Error SetAddressMode(AddressMode aMode); + + /** + * This method gets the sequence number used with anycast address mode. + * + * The sequence number is included in "DNS/SRP Service Anycast Address" entry published in the Network Data. + * + * @returns The anycast sequence number. + * + */ + uint8_t GetAnycastModeSequenceNumber(void) const { return mAnycastSequenceNumber; } + + /** + * This method sets the sequence number used with anycast address mode. + * + * @param[in] aSequenceNumber The sequence number to use. + * + * @retval kErrorNone Successfully set the address mode. + * @retval kErrorInvalidState The SRP server is enabled and the sequence number cannot be changed. + * + */ + Error SetAnycastModeSequenceNumber(uint8_t aSequenceNumber); + /** * This method tells whether the SRP server is currently running. * @@ -675,6 +736,11 @@ private: static constexpr uint32_t kDefaultMaxKeyLease = 3600u * 24 * 14; // 14 days (in seconds). static constexpr uint32_t kDefaultEventsHandlerTimeout = OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_UPDATE_TIMEOUT; + static constexpr AddressMode kDefaultAddressMode = + static_cast(OPENTHREAD_CONFIG_SRP_SERVER_DEFAULT_ADDDRESS_MODE); + + static constexpr uint16_t kAnycastAddressModePort = 53; + // This class includes metadata for processing a SRP update (register, deregister) // and sending DNS response to the client. class UpdateMetadata : public InstanceLocator, public LinkedListEntry @@ -708,9 +774,17 @@ private: UpdateMetadata * mNext; }; - void Start(void); - void Stop(void); - void SelectPort(void); + void Start(void); + void Stop(void); + void SelectPort(void); + void PrepareSocket(void); + Ip6::Udp::Socket &GetSocket(void); + +#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE + void HandleDnssdServerStateChange(void); + Error HandleDnssdServerUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); +#endif + void HandleNetDataPublisherEvent(NetworkData::Publisher::Event aEvent); ServiceUpdateId AllocateId(void) { return mServiceUpdateId++; } @@ -719,10 +793,11 @@ private: const Dns::UpdateHeader &aDnsHeader, Host & aHost, const Ip6::MessageInfo & aMessageInfo); - void HandleDnsUpdate(Message & aMessage, - const Ip6::MessageInfo & aMessageInfo, - const Dns::UpdateHeader &aDnsHeader, - uint16_t aOffset); + Error ProcessMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + void ProcessDnsUpdate(Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const Dns::UpdateHeader &aDnsHeader, + uint16_t aOffset); Error ProcessUpdateSection(Host & aHost, const Message & aMessage, const Dns::UpdateHeader &aDnsHeader, @@ -781,6 +856,7 @@ private: void HandleServiceUpdateResult(UpdateMetadata *aUpdate, Error aError); const UpdateMetadata *FindOutstandingUpdate(const Ip6::MessageInfo &aMessageInfo, uint16_t aDnsMessageId); + static const char * AddressModeToString(AddressMode aMode); Ip6::Udp::Socket mSocket; otSrpServerServiceUpdateHandler mServiceUpdateHandler; @@ -799,6 +875,8 @@ private: ServiceUpdateId mServiceUpdateId; uint16_t mPort; State mState; + AddressMode mAddressMode; + uint8_t mAnycastSequenceNumber; bool mHasRegisteredAnyService : 1; }; diff --git a/tests/scripts/thread-cert/Makefile.am b/tests/scripts/thread-cert/Makefile.am index da2e3eef0..9e81c595c 100644 --- a/tests/scripts/thread-cert/Makefile.am +++ b/tests/scripts/thread-cert/Makefile.am @@ -187,6 +187,7 @@ EXTRA_DIST = \ test_srp_lease.py \ test_srp_name_conflicts.py \ test_srp_register_single_service.py \ + test_srp_server_anycast_mode.py \ test_srp_server_reboot_port.py \ test_srp_sub_type.py \ test_zero_len_external_route.py \ @@ -254,6 +255,7 @@ check_SCRIPTS = \ test_srp_lease.py \ test_srp_name_conflicts.py \ test_srp_register_single_service.py \ + test_srp_server_anycast_mode.py \ test_srp_server_reboot_port.py \ test_srp_sub_type.py \ test_zero_len_external_route.py \ diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 58d006b89..533ef68ac 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -800,6 +800,23 @@ class NodeImpl: self.send_command('srp server state') return self._expect_result(states) + def srp_server_get_addr_mode(self): + modes = [r'unicast', r'anycast'] + self.send_command(f'srp server addrmode') + return self._expect_result(modes) + + def srp_server_set_addr_mode(self, mode): + self.send_command(f'srp server addrmode {mode}') + self._expect_done() + + def srp_server_get_anycast_seq_num(self): + self.send_command(f'srp server seqnum') + return int(self._expect_result(r'\d+')) + + def srp_server_set_anycast_seq_num(self, seqnum): + self.send_command(f'srp server seqnum {seqnum}') + self._expect_done() + def srp_server_set_enabled(self, enable): cmd = f'srp server {"enable" if enable else "disable"}' self.send_command(cmd) diff --git a/tests/scripts/thread-cert/test_srp_server_anycast_mode.py b/tests/scripts/thread-cert/test_srp_server_anycast_mode.py new file mode 100755 index 000000000..0a9031da8 --- /dev/null +++ b/tests/scripts/thread-cert/test_srp_server_anycast_mode.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2021, 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. +# + +import unittest + +import config +import thread_cert + +# Test description: +# +# This test covers the SRP server's behavior using different address +# modes (unicast or anycast). The address mode indicates how the SRP +# server determines its address and port and how this info is +# published in the Thread Network Data. When using anycast address +# mode, the SRP server and DNS server/resolver will both listen on port +# 53 and they re-use the same socket. This test verifies the behavior of +# both modules in such a situation. +# +# +# Topology: +# +# One leader and two router nodes, all connected. The leader acts as SRP +# server and DNS resolver. One router acts as SRP client, and the +# other acts as DNS client browsing for registered service names. +# + +SERVER = 1 +CLIENT = 2 +BROWSER = 3 + +DOMAIN = 'default.service.arpa.' +HOST = 'host' +INSTANCE = 'ins1' +SERVICE = '_srv._udp' +SERVICE_PORT = 1313 + +SRP_SERVER_ANYCAST_PORT = 53 +SRP_SERVER_ANYCAST_SEQ_NUM = 17 +DNS_RESOLVER_PORT = 53 + +THREAD_ENTERPRISE_NUMBER = 44970 +ANYCAST_SERVICE_NUM = 0x5c +UNICAST_SERVICE_NUM = 0x5d + + +class TestSrpServerAnycastMode(thread_cert.TestCase): + SUPPORT_NCP = False + USE_MESSAGE_FACTORY = False + + TOPOLOGY = { + SERVER: { + 'mode': 'rdn', + }, + CLIENT: { + 'mode': 'rdn', + }, + BROWSER: { + 'mode': 'rdn', + }, + } + + def test(self): + server = self.nodes[SERVER] + client = self.nodes[CLIENT] + browser = self.nodes[BROWSER] + + #------------------------------------------------------------------- + # Form the network. + + server.start() + self.simulator.go(5) + self.assertEqual(server.get_state(), 'leader') + + client.start() + browser.start() + self.simulator.go(5) + self.assertEqual(client.get_state(), 'router') + self.assertEqual(browser.get_state(), 'router') + + #------------------------------------------------------------------- + # Go through the entire test twice, first time using anycast address + # mode and second time using unicast address mode. + + for addr_mode in ['anycast', 'unicast']: + + #--------------------------------------------------------------- + # Set the SRP server address mode and start the SRP server. + + server.srp_server_set_addr_mode(addr_mode) + + if addr_mode == 'anycast': + server.srp_server_set_anycast_seq_num(SRP_SERVER_ANYCAST_SEQ_NUM) + self.assertEqual(server.srp_server_get_anycast_seq_num(), SRP_SERVER_ANYCAST_SEQ_NUM) + + self.assertEqual(server.srp_server_get_addr_mode(), addr_mode) + server.srp_server_set_enabled(True) + self.simulator.go(5) + + #--------------------------------------------------------------- + # Verify the published SRP server info in the Network Data. + + netdata_services = client.get_services() + self.assertEqual(len(netdata_services), 1) + netdata_service = netdata_services[0] + + self.assertEqual(int(netdata_service[0]), THREAD_ENTERPRISE_NUMBER) + data = bytes.fromhex(netdata_service[1]) + self.assertEqual(netdata_service[3], 's') + + if addr_mode == 'anycast': + self.assertTrue(len(data) >= 2) + self.assertEqual(data[0], ANYCAST_SERVICE_NUM) + self.assertEqual(data[1], SRP_SERVER_ANYCAST_SEQ_NUM) + else: + self.assertTrue(len(data) >= 1) + self.assertEqual(data[0], UNICAST_SERVICE_NUM) + self.assertEqual(netdata_service[3], 's') + + #--------------------------------------------------------------- + # Enable auto-start on SRP client. Verify that it does find the + # server and uses the proper address and port number. + + client.srp_client_enable_auto_start_mode() + self.simulator.go(5) + + if addr_mode == 'anycast': + server_alocs = server.get_ip6_address(config.ADDRESS_TYPE.ALOC) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertIn(client.srp_client_get_server_address(), server_alocs) + self.assertEqual(client.srp_client_get_server_port(), SRP_SERVER_ANYCAST_PORT) + else: + self.assertIn(client.srp_client_get_server_address(), server.get_mleid()) + self.assertEqual(client.srp_client_get_server_port(), server.get_srp_server_port()) + + #--------------------------------------------------------------- + # Add a service on the SRP client and verify its successful + # registration with SRP server. + + client.srp_client_set_host_name(HOST) + client.srp_client_set_host_address(client.get_mleid()) + client.srp_client_add_service(INSTANCE, SERVICE, SERVICE_PORT) + self.simulator.go(5) + + client_services = client.srp_client_get_services() + self.assertEqual(len(client_services), 1) + client_service = client_services[0] + self.assertEqual(client_service['instance'], INSTANCE) + self.assertEqual(client_service['name'], SERVICE) + self.assertEqual(int(client_service['port']), SERVICE_PORT) + self.assertEqual(client_service['state'], 'Registered') + + #--------------------------------------------------------------- + # Browse for a matching service name and verify that the registered + # service is successfully found. + + service_instances = browser.dns_browse(f'{SERVICE}.{DOMAIN}', server.get_mleid(), DNS_RESOLVER_PORT) + self.assertEqual({INSTANCE}, set(service_instances.keys())) + + service_instance = service_instances[INSTANCE] + self.assertEqual(service_instance['host'], f'{HOST}.{DOMAIN}') + self.assertEqual(int(service_instance['port']), SERVICE_PORT) + self.assertEqual(service_instance['address'], client.get_mleid()) + + #--------------------------------------------------------------- + # Stop SRP client and server and clear host (and service) on the + # client. + + client.srp_client_clear_host() + client.srp_client_stop() + server.srp_server_set_enabled(False) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index 959b0aadd..daf58df6c 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -510,6 +510,14 @@ */ #define OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE 1 +/** + * @def OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE + * + * Define to 1 to enable DNS-SD Server support. + * + */ +#define OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE 1 + #if OPENTHREAD_RADIO /** * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE