diff --git a/Android.mk b/Android.mk index be47e592a..9fc9a1ed5 100644 --- a/Android.mk +++ b/Android.mk @@ -187,6 +187,7 @@ LOCAL_SRC_FILES := \ src/core/api/random_noncrypto_api.cpp \ src/core/api/server_api.cpp \ src/core/api/srp_client_api.cpp \ + src/core/api/srp_server_api.cpp \ src/core/api/tasklet_api.cpp \ src/core/api/thread_api.cpp \ src/core/api/thread_ftd_api.cpp \ @@ -264,6 +265,7 @@ LOCAL_SRC_FILES := \ src/core/net/ip6_mpl.cpp \ src/core/net/netif.cpp \ src/core/net/srp_client.cpp \ + src/core/net/srp_server.cpp \ src/core/net/udp6.cpp \ src/core/radio/radio.cpp \ src/core/radio/radio_callbacks.cpp \ @@ -421,6 +423,7 @@ LOCAL_SRC_FILES := \ src/cli/cli_joiner.cpp \ src/cli/cli_network_data.cpp \ src/cli/cli_srp_client.cpp \ + src/cli/cli_srp_server.cpp \ src/cli/cli_uart.cpp \ src/cli/cli_udp.cpp \ $(NULL) diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 330fb68c8..90acba0e8 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -282,6 +282,11 @@ if(OT_SNTP_CLIENT) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE=1") endif() +option(OT_SRP_SERVER "enable SRP server") +if (OT_SRP_SERVER) + target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_SRP_SERVER_ENABLE=1") +endif() + option(OT_TIME_SYNC "enable the time synchronization service feature") if(OT_TIME_SYNC) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_TIME_SYNC_ENABLE=1") diff --git a/etc/gn/openthread.gni b/etc/gn/openthread.gni index 19b8ace79..141bd721d 100644 --- a/etc/gn/openthread.gni +++ b/etc/gn/openthread.gni @@ -198,6 +198,9 @@ if (openthread_enable_core_config_args) { # Enable SNTP Client support openthread_config_sntp_client_enable = false + # Enable SRP Server support + openthread_config_srp_server_enable = false + # Enable the time synchronization service feature openthread_config_time_sync_enable = false diff --git a/examples/README.md b/examples/README.md index 22ad5d853..474eafd87 100644 --- a/examples/README.md +++ b/examples/README.md @@ -57,6 +57,7 @@ This page lists the available common switches with description. Unless stated ot | SNTP_CLIENT | OT_SNTP_CLIENT | Enables support for SNTP Client. | | SPINEL_ENCRYPTER_LIBS | not implemented | Specifies library files (absolute paths) for implementing the NCP Spinel Encrypter. | | SRP_CLIENT | OT_SRP_CLIENT | Enable support for SRP client. | +| SRP_SERVER | OT_SRP_SERVER | Enable support for SRP server. | | THREAD_VERSION | OT_THREAD_VERSION | Enables the chosen Thread version (1.1 (default) / 1.2). For example, set to `1.2` for Thread 1.2. | | TIME_SYNC | OT_TIME_SYNC | Enables the time synchronization service feature. **Note: Enabling this feature breaks conformance to the Thread Specification.** | | | UDP_FORWARD | OT_UDP_FORWARD | Enables support for UDP forward. | Enable this switch on the Border Router device (running on the NCP design) with External Commissioning support to service Thread Commissioner packets on the NCP side. | diff --git a/examples/common-switches.mk b/examples/common-switches.mk index 34ce908d4..81e01ba6f 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -77,6 +77,7 @@ SETTINGS_RAM ?= 0 SLAAC ?= 1 SNTP_CLIENT ?= 0 SRP_CLIENT ?= 0 +SRP_SERVER ?= 0 THREAD_VERSION ?= 1.1 TIME_SYNC ?= 0 UDP_FORWARD ?= 0 @@ -273,6 +274,10 @@ ifeq ($(SRP_CLIENT),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_SRP_CLIENT_ENABLE=1 endif +ifeq ($(SRP_SERVER),1) +COMMONCFLAGS += -DOPENTHREAD_CONFIG_SRP_SERVER_ENABLE=1 +endif + ifeq ($(THREAD_VERSION),1.1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_THREAD_VERSION=2 else ifeq ($(THREAD_VERSION),1.2) diff --git a/include/Makefile.am b/include/Makefile.am index df80474e7..b4cca2284 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -75,6 +75,7 @@ openthread_headers = \ openthread/server.h \ openthread/sntp.h \ openthread/srp_client.h \ + openthread/srp_server.h \ openthread/tasklet.h \ openthread/thread.h \ openthread/thread_ftd.h \ diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index 605252376..15457a0a2 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -115,6 +115,7 @@ source_set("openthread") { "server.h", "sntp.h", "srp_client.h", + "srp_server.h", "tasklet.h", "thread.h", "thread_ftd.h", diff --git a/include/openthread/srp_server.h b/include/openthread/srp_server.h new file mode 100644 index 000000000..d3a4b06dc --- /dev/null +++ b/include/openthread/srp_server.h @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2020, 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. + */ + +/** + * @file + * @brief + * This file defines the API for server of the Service Registration Protocol (SRP). + */ + +#ifndef OPENTHREAD_SRP_SERVER_H_ +#define OPENTHREAD_SRP_SERVER_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup api-srp + * + * @brief + * This module includes functions of the Service Registration Protocol. + * + * @{ + * + */ + +/** + * This opaque type represents a SRP service host. + * + */ +typedef void otSrpServerHost; + +/** + * This opaque type represents a SRP service. + * + */ +typedef void otSrpServerService; + +/** + * This method returns the domain authorized to the SRP server. + * + * If the domain if not set by SetDomain, "default.service.arpa." will be returned. + * A trailing dot is always appended even if the domain is set without it. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns A pointer to the dot-joined domain string. + * + */ +const char *otSrpServerGetDomain(otInstance *aInstance); + +/** + * This method sets the domain on the SRP server. + * + * A trailing dot will be appended to @p aDomain if it is not already there. + * This method should only be called before the SRP server is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aDomain The domain to be set. MUST NOT be nullptr. + * + * @retval OT_ERROR_NONE Successfully set the domain to @p aDomain. + * @retval OT_ERROR_INVALID_STATE The SRP server is already enabled and the Domain cannot be changed. + * @retval OT_ERROR_INVALID_ARGS The argument @p aDomain is not a valid DNS domain name. + * @retval OT_ERROR_NO_BUFS There is no memory to store content of @p aDomain. + * + */ +otError otSrpServerSetDomain(otInstance *aInstance, const char *aDomain); + +/** + * This method enables/disables the SRP server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aEnabled A boolean to enable/disable the SRP server. + * + */ +void otSrpServerSetEnabled(otInstance *aInstance, bool aEnabled); + +/** + * This method sets LEASE & KEY-LEASE range that is acceptable by the SRP server. + * + * When a non-zero LEASE time is requested from a client, the granted value will be + * limited in range [aMinLease, aMaxLease]; and a non-zero KEY-LEASE will be granted + * in range [aMinKeyLease, aMaxKeyLease]. For zero LEASE or KEY-LEASE time, zero will + * be granted. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMinLease The minimum LEASE interval in seconds. + * @param[in] aMaxLease The maximum LEASE interval in seconds. + * @param[in] aMinKeyLease The minimum KEY-LEASE interval in seconds. + * @param[in] aMaxKeyLease The maximum KEY-LEASE interval in seconds. + * + * @retval OT_ERROR_NONE Successfully set the LEASE and KEY-LEASE ranges. + * @retval OT_ERROR_INVALID_ARGS The LEASE or KEY-LEASE range is not valid. + * + */ +otError otSrpServerSetLeaseRange(otInstance *aInstance, + uint32_t aMinLease, + uint32_t aMaxLease, + uint32_t aMinKeyLease, + uint32_t aMaxKeyLease); + +/** + * This method handles SRP service updates. + * + * This function is called by the SRP server to notify that a SRP host and possibly SRP services + * are being updated. It is important that the SRP updates are not commited until the handler + * returns the result by calling otSrpServerHandleServiceUpdateResult or times out after @p aTimeout. + * + * A SRP service observer should always call otSrpServerHandleServiceUpdateResult with error code + * OT_ERROR_NONE immediately after receiving the update events. + * + * A more generic handler may perform validations on the SRP host/services and rejects the SRP updates + * if any validation fails. For example, an Advertising Proxy should advertise (or remove) the host and + * services on a multicast-capable link and returns specific error code if any failure occurs. + * + * @param[in] aHost A pointer to the otSrpServerHost object which contains the SRP updates. + * The pointer should be passed back to otSrpServerHandleServiceUpdateResult, but + * the content MUST not be accessed after this method returns. The handler + * should publish/un-publish the host and each service points to this host + * with below rules: + * 1. If the host is not deleted (indicated by `otSrpServerHostIsDeleted`), + * then it should be published or updated with mDNS. Otherwise, the host + * should be un-published (remove AAAA RRs). + * 2. For each service points to this host, it must be un-published if the host + * is to be un-published. Otherwise, the handler should publish or update the + * service when it is not deleted (indicated by `otSrpServerServiceIsDeleted`) + * and un-publish it when deleted. + * @param[in] aTimeout The maximum time in milliseconds for the handler to process the service event. + * @param[in] aContext A pointer to application-specific context. + * + * @sa otSrpServerSetServiceUpdateHandler + * @sa otSrpServerHandleServiceUpdateResult + * + */ +typedef void (*otSrpServerServiceUpdateHandler)(const otSrpServerHost *aHost, uint32_t aTimeout, void *aContext); + +/** + * This method sets the SRP service updates handler on SRP server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aServiceHandler A pointer to a service handler. Use NULL to remove the handler. + * @param[in] aContext A pointer to arbitrary context information. + * May be NULL if not used. + * + */ +void otSrpServerSetServiceUpdateHandler(otInstance * aInstance, + otSrpServerServiceUpdateHandler aServiceHandler, + void * aContext); + +/** + * This method reports the result of processing a SRP update to the SRP server. + * + * The Service Update Handler should call this function to return the result of its + * processing of a SRP update. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aHost A pointer to the Host object which represents a SRP update. + * @param[in] aError An error to be returned to the SRP server. Use OT_ERROR_DUPLICATED + * to represent DNS name conflicts. + * + */ +void otSrpServerHandleServiceUpdateResult(otInstance *aInstance, const otSrpServerHost *aHost, otError aError); + +/** + * This method returns the next registered host on the SRP server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aHost A pointer to current host; use NULL to get the first host. + * + * @returns A pointer to the registered host. NULL, if no more hosts can be found. + * + */ +const otSrpServerHost *otSrpServerGetNextHost(otInstance *aInstance, const otSrpServerHost *aHost); + +/** + * This method tells if the SRP service host has been deleted. + * + * A SRP service host can be deleted but retains its name for future uses. + * In this case, the host instance is not removed from the SRP server/registry. + * + * @param[in] aHost A pointer to the SRP service host. + * + * @returns TRUE if the host has been deleted, FALSE if not. + * + */ +bool otSrpServerHostIsDeleted(const otSrpServerHost *aHost); + +/** + * This method returns the full name of the host. + * + * @param[in] aHost A pointer to the SRP service host. + * + * @returns A pointer to the null-terminated host name string. + * + */ +const char *otSrpServerHostGetFullName(const otSrpServerHost *aHost); + +/** + * This method returns the addresses of given host. + * + * @param[in] aHost A pointer to the SRP service host. + * @param[out] aAddressesNum A pointer to where we should output the number of the addresses to. + * + * @returns A pointer to the array of IPv6 Address. + * + */ +const otIp6Address *otSrpServerHostGetAddresses(const otSrpServerHost *aHost, uint8_t *aAddressesNum); + +/** + * This method returns the next service of given host. + * + * @param[in] aHost A pointer to the SRP service host. + * @param[in] aService A pointer to current SRP service instance; use NULL to get the first service. + * + * @returns A pointer to the next service or NULL if there is no more services. + * + */ +const otSrpServerService *otSrpServerHostGetNextService(const otSrpServerHost * aHost, + const otSrpServerService *aService); + +/** + * This method tells if the SRP service has been deleted. + * + * A SRP service can be deleted but retains its name for future uses. + * In this case, the service instance is not removed from the SRP server/registry. + * It is guaranteed that all services are deleted if the host is deleted. + * + * @param[in] aService A pointer to the SRP service. + * + * @returns TRUE if the service has been deleted, FALSE if not. + * + */ +bool otSrpServerServiceIsDeleted(const otSrpServerService *aService); + +/** + * This method returns the full name of the service. + * + * @param[in] aService A pointer to the SRP service. + * + * @returns A pointer to the null-terminated service name string. + * + */ +const char *otSrpServerServiceGetFullName(const otSrpServerService *aService); + +/** + * This method returns the port of the service instance. + * + * @param[in] aService A pointer to the SRP service. + * + * @returns The port of the service. + * + */ +uint16_t otSrpServerServiceGetPort(const otSrpServerService *aService); + +/** + * This method returns the weight of the service instance. + * + * @param[in] aService A pointer to the SRP service. + * + * @returns The weight of the service. + * + */ +uint16_t otSrpServerServiceGetWeight(const otSrpServerService *aService); + +/** + * This method returns the priority of the service instance. + * + * @param[in] aService A pointer to the SRP service. + * + * @returns The priority of the service. + * + */ +uint16_t otSrpServerServiceGetPriority(const otSrpServerService *aService); + +/** + * This method returns the TXT data of the service instance. + * + * @param[in] aService A pointer to the SRP service. + * @param[out] aTxtLength A pointer to the output of the TXT data length. + * + * @returns A pointer to the standard TXT data with format described by RFC 6763. + * + */ +const uint8_t *otSrpServerServiceGetTxtData(const otSrpServerService *aService, uint16_t *aTxtLength); + +/** + * This method returns the host which the service instance reside on. + * + * @param[in] aService A pointer to the SRP service. + * + * @returns A pointer to the host instance. + * + */ +const otSrpServerHost *otSrpServerServiceGetHost(const otSrpServerService *aService); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_SRP_SERVER_H_ diff --git a/script/check-scan-build b/script/check-scan-build index 3695321fd..e1980b805 100755 --- a/script/check-scan-build +++ b/script/check-scan-build @@ -74,6 +74,7 @@ do_scan_build() "-DOPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE=1" "-DOPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE=1" "-DOPENTHREAD_CONFIG_SRP_CLIENT_ENABLE=1" + "-DOPENTHREAD_CONFIG_SRP_SERVER_ENABLE=1" "-DOPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE=1" "-DOPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE=1" "-DOPENTHREAD_CONFIG_UDP_FORWARD_ENABLE=1" diff --git a/script/check-size b/script/check-size index 2fc597ed0..cb386f607 100755 --- a/script/check-size +++ b/script/check-size @@ -127,6 +127,7 @@ size_nrf52840_version() "SLAAC=1" "SNTP_CLIENT=1" "SRP_CLIENT=1" + "SRP_SERVER=1" "TIME_SYNC=1" "UDP_FORWARD=1" ) diff --git a/script/test b/script/test index 2a05ad471..c3eff5be6 100755 --- a/script/test +++ b/script/test @@ -52,7 +52,15 @@ readonly OT_COREDUMP_DIR="${PWD}/ot-core-dump" build_simulation() { local version="$1" - local options=("-DOT_MESSAGE_USE_HEAP=ON" "-DOT_THREAD_VERSION=${version}" "-DBUILD_TESTING=ON" "-DOT_REFERENCE_DEVICE=ON") + local options=( + "-DOT_MESSAGE_USE_HEAP=ON" + "-DOT_THREAD_VERSION=${version}" + "-DBUILD_TESTING=ON" + "-DOT_REFERENCE_DEVICE=ON" + "-DOT_SRP_SERVER=ON" + "-DOT_SRP_CLIENT=ON" + "-DOT_SERVICE=ON" + "-DOT_ECDSA=ON") if [[ ${version} == "1.2" ]]; then options+=("-DOT_DUA=ON") @@ -192,6 +200,8 @@ do_cert() fi fi + export PYTHONPATH=tests/scripts/thread-cert + [[ ! -d tmp ]] || rm -rvf tmp PYTHONUNBUFFERED=1 "$@" } @@ -238,6 +248,10 @@ do_build_otbr_docker() local otbr_options="-DOT_SLAAC=ON -DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON -DOTBR_REST=OFF -DOTBR_WEB=OFF" local otbr_docker_image=${OTBR_DOCKER_IMAGE:-otbr-ot12-backbone-ci} + # Always enable SRP server for OTBR. + # TODO: enable SRP server inside OTBR after this PR merged. + otbr_options="${otbr_options} -DOT_SRP_SERVER=ON -DOT_SERVICE=ON -DOT_ECDSA=ON" + if [[ ${BORDER_ROUTING} == "1" ]]; then otbr_options="${otbr_options} -DOT_BORDER_ROUTING=ON" fi diff --git a/src/cli/BUILD.gn b/src/cli/BUILD.gn index 72040917c..3464a8601 100644 --- a/src/cli/BUILD.gn +++ b/src/cli/BUILD.gn @@ -47,6 +47,8 @@ openthread_cli_sources = [ "cli_network_data.hpp", "cli_srp_client.cpp", "cli_srp_client.hpp", + "cli_srp_server.cpp", + "cli_srp_server.hpp", "cli_uart.cpp", "cli_uart.hpp", "cli_udp.cpp", diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 64d5247e2..31de6aa23 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -48,6 +48,7 @@ set(COMMON_SOURCES cli_joiner.cpp cli_network_data.cpp cli_srp_client.cpp + cli_srp_server.cpp cli_uart.cpp cli_udp.cpp ) diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am index 70852f3ba..3491638a8 100644 --- a/src/cli/Makefile.am +++ b/src/cli/Makefile.am @@ -159,6 +159,7 @@ SOURCES_COMMON = \ cli_joiner.cpp \ cli_network_data.cpp \ cli_srp_client.cpp \ + cli_srp_server.cpp \ cli_uart.cpp \ cli_udp.cpp \ $(NULL) @@ -182,6 +183,7 @@ noinst_HEADERS = \ cli_joiner.hpp \ cli_network_data.hpp \ cli_srp_client.hpp \ + cli_srp_server.hpp \ cli_uart.hpp \ cli_udp.hpp \ x509_cert_key.hpp \ diff --git a/src/cli/README_SRP_SERVER.md b/src/cli/README_SRP_SERVER.md new file mode 100644 index 000000000..effa6385d --- /dev/null +++ b/src/cli/README_SRP_SERVER.md @@ -0,0 +1,120 @@ +# OpenThread CLI - SRP Server + +## Quick Start + +See [README_SRP.md](README_SRP.md). + +## Command List + +- [help](#help) +- [disable](#disable) +- [domain](#domain) +- [enable](#enable) +- [host](#host) +- [lease](#lease) +- [service](#service) + +## Command Details + +### help + +Usage: `srp server help` + +Print SRP server help menu. + +```bash +> srp server help +disable +domain +enable +help +host +lease +service +Done +``` + +### disable + +Usage: `srp server disable` + +Disable the SRP server. + +```bash +> srp server disable +Done +``` + +### domain + +Usage: `srp server domain [domain-name]` + +Get the domain. + +```bash +> srp server domain +default.service.arpa. +Done +``` + +Set the domain. + +```bash +> srp server domain thread.service.arpa. +Done +``` + +### enable + +Usage: `srp server enable` + +Enable the SRP server. + +```bash +> srp server enable +Done +``` + +### host + +Usage: `srp server host` + +Print information of all registered hosts. + +```bash +> srp server host +srp-api-test-1.default.service.arpa. + deleted: false + addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10] +srp-api-test-0.default.service.arpa. + deleted: false + addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10] +Done +``` + +### srp server service + +Usage: `srp server service` + +Print information of all registered services. + +```bash +> srp server service +srp-api-test-1._ipps._tcp.default.service.arpa. + deleted: false + port: 49152 + priority: 0 + weight: 0 + TXT: 0130 + host: srp-api-test-1.default.service.arpa. + addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10] +srp-api-test-0._ipps._tcp.default.service.arpa. + deleted: false + port: 49152 + priority: 0 + weight: 0 + TXT: 0130 + host: srp-api-test-0.default.service.arpa. + addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10] +Done +``` diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 21b92df9e..6c91b25a1 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -143,6 +143,9 @@ Interpreter::Interpreter(Instance *aInstance) #endif #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE , mSrpClient(*this) +#endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + , mSrpServer(*this) #endif , mInstance(aInstance) { @@ -178,9 +181,9 @@ void Interpreter::OutputResult(otError aError) } } -void Interpreter::OutputBytes(const uint8_t *aBytes, uint8_t aLength) +void Interpreter::OutputBytes(const uint8_t *aBytes, uint16_t aLength) { - for (int i = 0; i < aLength; i++) + for (uint16_t i = 0; i < aLength; i++) { OutputFormat("%02x", aBytes[i]); } @@ -3872,29 +3875,41 @@ void Interpreter::HandleSntpResponse(uint64_t aTime, otError aResult) } #endif // OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE -#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE +#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE || OPENTHREAD_CONFIG_SRP_SERVER_ENABLE otError Interpreter::ProcessSrp(uint8_t aArgsLength, char *aArgs[]) { otError error = OT_ERROR_NONE; if (aArgsLength == 0) { +#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE OutputLine("client"); - // OutputLine("server"); +#endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + OutputLine("server"); +#endif ExitNow(); } +#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE if (strcmp(aArgs[0], "client") == 0) { ExitNow(error = mSrpClient.Process(aArgsLength - 1, aArgs + 1)); } +#endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + if (strcmp(aArgs[0], "server") == 0) + { + ExitNow(error = mSrpServer.Process(aArgsLength - 1, aArgs + 1)); + } +#endif error = OT_ERROR_INVALID_COMMAND; exit: return error; } -#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE || OPENTHREAD_CONFIG_SRP_SERVER_ENABLE otError Interpreter::ProcessState(uint8_t aArgsLength, char *aArgs[]) { diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index a5de30800..0497f1d09 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -51,6 +51,7 @@ #include "cli/cli_joiner.hpp" #include "cli/cli_network_data.hpp" #include "cli/cli_srp_client.hpp" +#include "cli/cli_srp_server.hpp" #include "cli/cli_udp.hpp" #if OPENTHREAD_CONFIG_COAP_API_ENABLE #include "cli/cli_coap.hpp" @@ -88,6 +89,7 @@ class Interpreter friend class Joiner; friend class NetworkData; friend class SrpClient; + friend class SrpServer; friend class UdpExample; public: @@ -148,7 +150,7 @@ public: * @param[in] aLength @p aBytes length. * */ - void OutputBytes(const uint8_t *aBytes, uint8_t aLength); + void OutputBytes(const uint8_t *aBytes, uint16_t aLength); /** * This method writes a number of bytes to the CLI console as a hex string. @@ -496,7 +498,7 @@ private: #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE otError ProcessSntp(uint8_t aArgsLength, char *aArgs[]); #endif -#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE +#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE || OPENTHREAD_CONFIG_SRP_SERVER_ENABLE otError ProcessSrp(uint8_t aArgsLength, char *aArgs[]); #endif otError ProcessState(uint8_t aArgsLength, char *aArgs[]); @@ -760,7 +762,7 @@ private: #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE {"sntp", &Interpreter::ProcessSntp}, #endif -#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE +#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE || OPENTHREAD_CONFIG_SRP_SERVER_ENABLE {"srp", &Interpreter::ProcessSrp}, #endif {"state", &Interpreter::ProcessState}, @@ -818,6 +820,10 @@ private: SrpClient mSrpClient; #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + SrpServer mSrpServer; +#endif + Instance *mInstance; }; diff --git a/src/cli/cli_srp_server.cpp b/src/cli/cli_srp_server.cpp new file mode 100644 index 000000000..59eaf6d61 --- /dev/null +++ b/src/cli/cli_srp_server.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2020, 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. + */ + +/** + * @file + * This file implements a simple CLI for the SRP server. + */ + +#include "cli_srp_server.hpp" + +#include + +#include "cli/cli.hpp" +#include "common/string.hpp" +#include "utils/parse_cmdline.hpp" + +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + +namespace ot { +namespace Cli { + +constexpr SrpServer::Command SrpServer::sCommands[]; + +otError SrpServer::Process(uint8_t aArgsLength, char *aArgs[]) +{ + otError error = OT_ERROR_INVALID_COMMAND; + const Command *command; + + VerifyOrExit(aArgsLength != 0, IgnoreError(ProcessHelp(0, nullptr))); + + command = Utils::LookupTable::Find(aArgs[0], sCommands); + VerifyOrExit(command != nullptr); + + error = (this->*command->mHandler)(aArgsLength, aArgs); + +exit: + return error; +} + +otError SrpServer::ProcessDomain(uint8_t aArgsLength, char *aArgs[]) +{ + otError error = OT_ERROR_NONE; + + if (aArgsLength > 1) + { + SuccessOrExit(error = otSrpServerSetDomain(mInterpreter.mInstance, aArgs[1])); + } + else + { + mInterpreter.OutputLine(otSrpServerGetDomain(mInterpreter.mInstance)); + } + +exit: + return error; +} + +otError SrpServer::ProcessEnable(uint8_t aArgsLength, char *aArgs[]) +{ + OT_UNUSED_VARIABLE(aArgsLength); + OT_UNUSED_VARIABLE(aArgs); + + otSrpServerSetEnabled(mInterpreter.mInstance, /* aEnabled */ true); + + return OT_ERROR_NONE; +} + +otError SrpServer::ProcessDisable(uint8_t aArgsLength, char *aArgs[]) +{ + OT_UNUSED_VARIABLE(aArgsLength); + OT_UNUSED_VARIABLE(aArgs); + + otSrpServerSetEnabled(mInterpreter.mInstance, /* aEnabled */ false); + + return OT_ERROR_NONE; +} + +otError SrpServer::ProcessLease(uint8_t aArgsLength, char *aArgs[]) +{ + otError error = OT_ERROR_NONE; + uint32_t minLease; + uint32_t maxLease; + uint32_t minKeyLease; + uint32_t maxKeyLease; + + VerifyOrExit(aArgsLength == 5, error = OT_ERROR_INVALID_ARGS); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint32(aArgs[1], minLease)); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint32(aArgs[2], maxLease)); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint32(aArgs[3], minKeyLease)); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint32(aArgs[4], maxKeyLease)); + + error = otSrpServerSetLeaseRange(mInterpreter.mInstance, minLease, maxLease, minKeyLease, maxKeyLease); + +exit: + return error; +} + +otError SrpServer::ProcessHost(uint8_t aArgsLength, char *aArgs[]) +{ + OT_UNUSED_VARIABLE(aArgs); + + otError error = OT_ERROR_NONE; + const otSrpServerHost *host; + + VerifyOrExit(aArgsLength <= 1, error = OT_ERROR_INVALID_ARGS); + + host = nullptr; + while ((host = otSrpServerGetNextHost(mInterpreter.mInstance, host)) != nullptr) + { + const otIp6Address *addresses; + uint8_t addressesNum; + bool isDeleted = otSrpServerHostIsDeleted(host); + + mInterpreter.OutputLine(otSrpServerHostGetFullName(host)); + mInterpreter.OutputLine(Interpreter::kIndentSize, "deleted: %s", isDeleted ? "true" : "false"); + if (isDeleted) + { + continue; + } + + mInterpreter.OutputSpaces(Interpreter::kIndentSize); + mInterpreter.OutputFormat("addresses: ["); + + addresses = otSrpServerHostGetAddresses(host, &addressesNum); + + for (uint8_t i = 0; i < addressesNum; ++i) + { + mInterpreter.OutputIp6Address(addresses[i]); + if (i < addressesNum - 1) + { + mInterpreter.OutputFormat(", "); + } + } + + mInterpreter.OutputFormat("]\r\n"); + } + +exit: + return error; +} + +otError SrpServer::ProcessService(uint8_t aArgsLength, char *aArgs[]) +{ + OT_UNUSED_VARIABLE(aArgs); + + otError error = OT_ERROR_NONE; + const otSrpServerHost *host; + + VerifyOrExit(aArgsLength <= 1, error = OT_ERROR_INVALID_ARGS); + + host = nullptr; + while ((host = otSrpServerGetNextHost(mInterpreter.mInstance, host)) != nullptr) + { + const otSrpServerService *service = nullptr; + + while ((service = otSrpServerHostGetNextService(host, service)) != nullptr) + { + const otIp6Address *addresses; + uint8_t addressesNum; + const uint8_t * txtData; + uint16_t txtLength; + bool isDeleted = otSrpServerServiceIsDeleted(service); + + mInterpreter.OutputLine(otSrpServerServiceGetFullName(service)); + mInterpreter.OutputLine(Interpreter::kIndentSize, "deleted: %s", isDeleted ? "true" : "false"); + if (isDeleted) + { + continue; + } + + mInterpreter.OutputLine(Interpreter::kIndentSize, "port: %hu", otSrpServerServiceGetPort(service)); + mInterpreter.OutputLine(Interpreter::kIndentSize, "priority: %hu", otSrpServerServiceGetPriority(service)); + mInterpreter.OutputLine(Interpreter::kIndentSize, "weight: %hu", otSrpServerServiceGetWeight(service)); + + txtData = otSrpServerServiceGetTxtData(service, &txtLength); + + if (txtLength > 0) + { + mInterpreter.OutputSpaces(Interpreter::kIndentSize); + mInterpreter.OutputFormat("TXT: "); + mInterpreter.OutputBytes(txtData, txtLength); + mInterpreter.OutputFormat("\r\n"); + } + + mInterpreter.OutputLine(Interpreter::kIndentSize, "host: %s", otSrpServerHostGetFullName(host)); + mInterpreter.OutputSpaces(Interpreter::kIndentSize); + mInterpreter.OutputFormat("addresses: ["); + + addresses = otSrpServerHostGetAddresses(host, &addressesNum); + for (uint8_t i = 0; i < addressesNum; ++i) + { + mInterpreter.OutputIp6Address(addresses[i]); + if (i < addressesNum - 1) + { + mInterpreter.OutputFormat(", "); + } + } + mInterpreter.OutputFormat("]\r\n"); + } + } + +exit: + return error; +} + +otError SrpServer::ProcessHelp(uint8_t aArgsLength, char *aArgs[]) +{ + OT_UNUSED_VARIABLE(aArgsLength); + OT_UNUSED_VARIABLE(aArgs); + + for (const Command &command : sCommands) + { + mInterpreter.OutputLine(command.mName); + } + + return OT_ERROR_NONE; +} + +} // namespace Cli +} // namespace ot + +#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE diff --git a/src/cli/cli_srp_server.hpp b/src/cli/cli_srp_server.hpp new file mode 100644 index 000000000..e40de9bd9 --- /dev/null +++ b/src/cli/cli_srp_server.hpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020, 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. + */ + +/** + * @file + * This file contains definitions for a simple CLI to control the SRP server. + */ + +#ifndef CLI_SRP_SERVER_HPP_ +#define CLI_SRP_SERVER_HPP_ + +#include "openthread-core-config.h" + +#include + +#include "utils/lookup_table.hpp" + +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + +namespace ot { +namespace Cli { + +class Interpreter; + +/** + * This class implements the SRP Server CLI interpreter. + * + */ +class SrpServer +{ +public: + /** + * Constructor + * + * @param[in] aInterpreter The CLI interpreter. + * + */ + explicit SrpServer(Interpreter &aInterpreter) + : mInterpreter(aInterpreter) + { + } + + /** + * This method interprets a list of CLI arguments. + * + * @param[in] aArgsLength The number of elements in @p aArgs. + * @param[in] aArgs A pointer to an array of command line arguments. + * + * @retval OT_ERROR_NONE Successfully executed the CLI command. + * @retval ... Failed to execute the CLI command. + * + */ + otError Process(uint8_t aArgsLength, char *aArgs[]); + +private: + struct Command + { + const char *mName; + otError (SrpServer::*mHandler)(uint8_t aArgsLength, char *aArgs[]); + }; + + otError ProcessDomain(uint8_t aArgsLength, char *aArgs[]); + otError ProcessEnable(uint8_t aArgsLength, char *aArgs[]); + otError ProcessDisable(uint8_t aArgsLength, char *aArgs[]); + otError ProcessLease(uint8_t aArgsLength, char *aArgs[]); + otError ProcessHost(uint8_t aArgsLength, char *aArgs[]); + otError ProcessService(uint8_t aArgsLength, char *aArgs[]); + otError ProcessHelp(uint8_t aArgsLength, char *aArgs[]); + + 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}, + }; + + static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); + + Interpreter &mInterpreter; +}; + +} // namespace Cli +} // namespace ot + +#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + +#endif // CLI_SRP_SERVER_HPP_ diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 18f3e3320..528d0df2a 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -224,6 +224,10 @@ if (openthread_enable_core_config_args) { defines += [ "OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE=1" ] } + if (openthread_config_srp_server_enable) { + defines += [ "OPENTHREAD_CONFIG_SRP_SERVER_ENABLE=1" ] + } + if (openthread_config_time_sync_enable) { defines += [ "OPENTHREAD_CONFIG_TIME_SYNC_ENABLE=1" ] } @@ -334,6 +338,7 @@ openthread_core_files = [ "api/server_api.cpp", "api/sntp_api.cpp", "api/srp_client_api.cpp", + "api/srp_server_api.cpp", "api/tasklet_api.cpp", "api/thread_api.cpp", "api/thread_ftd_api.cpp", @@ -506,6 +511,8 @@ openthread_core_files = [ "net/socket.hpp", "net/srp_client.cpp", "net/srp_client.hpp", + "net/srp_server.cpp", + "net/srp_server.hpp", "net/tcp.hpp", "net/udp6.cpp", "net/udp6.hpp", @@ -687,6 +694,7 @@ source_set("libopenthread_core_config") { "config/radio_link.h", "config/sntp_client.h", "config/srp_client.h", + "config/srp_server.h", "config/time_sync.h", "config/tmf.h", "openthread-core-config.h", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 982f734a4..04bb78038 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -67,6 +67,7 @@ set(COMMON_SOURCES api/server_api.cpp api/sntp_api.cpp api/srp_client_api.cpp + api/srp_server_api.cpp api/tasklet_api.cpp api/thread_api.cpp api/thread_ftd_api.cpp @@ -146,6 +147,7 @@ set(COMMON_SOURCES net/netif.cpp net/sntp_client.cpp net/srp_client.cpp + net/srp_server.cpp net/udp6.cpp radio/radio.cpp radio/radio_callbacks.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 7e05b477d..45e1ca0e6 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -144,6 +144,7 @@ SOURCES_COMMON = \ api/server_api.cpp \ api/sntp_api.cpp \ api/srp_client_api.cpp \ + api/srp_server_api.cpp \ api/tasklet_api.cpp \ api/thread_api.cpp \ api/thread_ftd_api.cpp \ @@ -223,6 +224,7 @@ SOURCES_COMMON = \ net/netif.cpp \ net/sntp_client.cpp \ net/srp_client.cpp \ + net/srp_server.cpp \ net/udp6.cpp \ radio/radio.cpp \ radio/radio_callbacks.cpp \ @@ -415,6 +417,7 @@ HEADERS_COMMON = \ config/radio_link.h \ config/sntp_client.h \ config/srp_client.h \ + config/srp_server.h \ config/time_sync.h \ config/tmf.h \ crypto/aes_ccm.hpp \ @@ -467,6 +470,7 @@ HEADERS_COMMON = \ net/sntp_client.hpp \ net/socket.hpp \ net/srp_client.hpp \ + net/srp_server.hpp \ net/tcp.hpp \ net/udp6.hpp \ radio/radio.hpp \ diff --git a/src/core/api/srp_server_api.cpp b/src/core/api/srp_server_api.cpp new file mode 100644 index 000000000..059118751 --- /dev/null +++ b/src/core/api/srp_server_api.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020, 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. + */ + +/** + * @file + * This file defines the OpenThread SRP server API. + */ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + +#include + +#include "common/instance.hpp" +#include "common/locator-getters.hpp" + +using namespace ot; + +const char *otSrpServerGetDomain(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().GetDomain(); +} + +otError otSrpServerSetDomain(otInstance *aInstance, const char *aDomain) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().SetDomain(aDomain); +} + +void otSrpServerSetEnabled(otInstance *aInstance, bool aEnabled) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().SetEnabled(aEnabled); +} + +otError otSrpServerSetLeaseRange(otInstance *aInstance, + uint32_t aMinLease, + uint32_t aMaxLease, + uint32_t aMinKeyLease, + uint32_t aMaxKeyLease) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().SetLeaseRange(aMinLease, aMaxLease, aMinKeyLease, aMaxKeyLease); +} + +void otSrpServerSetServiceUpdateHandler(otInstance * aInstance, + otSrpServerServiceUpdateHandler aServiceHandler, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().SetServiceHandler(aServiceHandler, aContext); +} + +void otSrpServerHandleServiceUpdateResult(otInstance *aInstance, const otSrpServerHost *aHost, otError aError) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().HandleAdvertisingResult(static_cast(aHost), aError); +} + +const otSrpServerHost *otSrpServerGetNextHost(otInstance *aInstance, const otSrpServerHost *aHost) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().GetNextHost(static_cast(aHost)); +} + +bool otSrpServerHostIsDeleted(const otSrpServerHost *aHost) +{ + return static_cast(aHost)->IsDeleted(); +} + +const char *otSrpServerHostGetFullName(const otSrpServerHost *aHost) +{ + return static_cast(aHost)->GetFullName(); +} + +const otIp6Address *otSrpServerHostGetAddresses(const otSrpServerHost *aHost, uint8_t *aAddressesNum) +{ + auto host = static_cast(aHost); + + return host->GetAddresses(*aAddressesNum); +} + +const otSrpServerService *otSrpServerHostGetNextService(const otSrpServerHost * aHost, + const otSrpServerService *aService) +{ + auto host = static_cast(aHost); + + return host->GetNextService(static_cast(aService)); +} + +bool otSrpServerServiceIsDeleted(const otSrpServerService *aService) +{ + return static_cast(aService)->IsDeleted(); +} + +const char *otSrpServerServiceGetFullName(const otSrpServerService *aService) +{ + return static_cast(aService)->GetFullName(); +} + +uint16_t otSrpServerServiceGetPort(const otSrpServerService *aService) +{ + return static_cast(aService)->GetPort(); +} + +uint16_t otSrpServerServiceGetWeight(const otSrpServerService *aService) +{ + return static_cast(aService)->GetWeight(); +} + +uint16_t otSrpServerServiceGetPriority(const otSrpServerService *aService) +{ + return static_cast(aService)->GetPriority(); +} + +const uint8_t *otSrpServerServiceGetTxtData(const otSrpServerService *aService, uint16_t *aTxtLength) +{ + return static_cast(aService)->GetTxtData(*aTxtLength); +} + +const otSrpServerHost *otSrpServerServiceGetHost(const otSrpServerService *aService) +{ + return &static_cast(aService)->GetHost(); +} + +#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index 57b244cfb..111e8e874 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -869,6 +869,13 @@ template <> inline BorderRouter::RoutingManager &Instance::Get(void) } #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +template <> inline Srp::Server &Instance::Get(void) +{ + return mThreadNetif.mSrpServer; +} +#endif + #endif // OPENTHREAD_MTD || OPENTHREAD_FTD #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE diff --git a/src/core/common/notifier.cpp b/src/core/common/notifier.cpp index 583df96a0..d1b98ab45 100644 --- a/src/core/common/notifier.cpp +++ b/src/core/common/notifier.cpp @@ -183,6 +183,9 @@ void Notifier::EmitEvents(void) #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE Get().HandleNotifierEvents(events); #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + Get().HandleNotifierEvents(events); +#endif #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE Get().HandleNotifierEvents(events); #endif diff --git a/src/core/config/openthread-core-default-config.h b/src/core/config/openthread-core-default-config.h index a2076b354..5697d72c8 100644 --- a/src/core/config/openthread-core-default-config.h +++ b/src/core/config/openthread-core-default-config.h @@ -36,6 +36,7 @@ #define OPENTHREAD_CORE_DEFAULT_CONFIG_H_ #include "config/coap.h" +#include "config/srp_server.h" /** * @def OPENTHREAD_CONFIG_STACK_VENDOR_OUI @@ -265,7 +266,10 @@ * */ #ifndef OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +// Internal heap doesn't support size larger than 64K bytes. +#define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE (63 * 1024) +#elif OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE #define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE (3072 * sizeof(void *)) #else #define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE (1568 * sizeof(void *)) @@ -279,7 +283,10 @@ * */ #ifndef OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS -#if OPENTHREAD_CONFIG_ECDSA_ENABLE +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +// Internal heap doesn't support size larger than 64K bytes. +#define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS (63 * 1024) +#elif OPENTHREAD_CONFIG_ECDSA_ENABLE #define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS 2600 #else #define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS 384 diff --git a/src/core/config/srp_server.h b/src/core/config/srp_server.h new file mode 100644 index 000000000..3b9147479 --- /dev/null +++ b/src/core/config/srp_server.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2020, 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. + */ + +/** + * @file + * This file includes compile-time configurations for the SRP (Service Registration Protocol) Server. + * + */ + +#ifndef CONFIG_SRP_SERVER_H_ +#define CONFIG_SRP_SERVER_H_ + +/** + * @def OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + * + * Define to 1 to enable SRP Server support. + * + */ +#ifndef OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +#define OPENTHREAD_CONFIG_SRP_SERVER_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_TYPE + * + * Specifies the Service Type for SRP Server. + * + */ +#ifndef OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_TYPE +#define OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_TYPE 0x5du +#endif + +/** + * @def OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_UPDATE_TIMEOUT + * + * Specifies the timeout value (in milliseconds) for the service update handler. + * + * The default timeout value is the sum of the maximum total mDNS probing delays + * and a loose IPC timeout of 250ms. It is recommended that this configuration should + * not use a value smaller than the default value here, if an Advertising Proxy is used + * to handle the service update events. + * + */ +#ifndef OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_UPDATE_TIMEOUT +#define OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_UPDATE_TIMEOUT ((4 * 250u) + 250u) +#endif + +/** + * @def OPENTHREAD_CONFIG_SRP_SERVER_MAX_ADDRESSES_NUM + * + * Specifies the maximum number of addresses the SRP server can handle for a host. + * + */ +#ifndef OPENTHREAD_CONFIG_SRP_SERVER_MAX_ADDRESSES_NUM +#define OPENTHREAD_CONFIG_SRP_SERVER_MAX_ADDRESSES_NUM 2 +#endif + +#endif // CONFIG_SRP_SERVER_H_ diff --git a/src/core/net/dns_headers.cpp b/src/core/net/dns_headers.cpp index 7ecb0a90d..57e3fa250 100644 --- a/src/core/net/dns_headers.cpp +++ b/src/core/net/dns_headers.cpp @@ -373,5 +373,39 @@ exit: return error; } +bool AaaaRecord::IsValid(void) const +{ + return GetType() == Dns::ResourceRecord::kTypeAaaa && GetSize() == sizeof(*this); +} + +bool KeyRecord::IsValid(void) const +{ + return GetType() == Dns::ResourceRecord::kTypeKey; +} + +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +void Ecdsa256KeyRecord::Init(void) +{ + KeyRecord::Init(); + SetAlgorithm(kAlgorithmEcdsaP256Sha256); +} + +bool Ecdsa256KeyRecord::IsValid(void) const +{ + return KeyRecord::IsValid() && GetLength() == sizeof(*this) - sizeof(ResourceRecord) && + GetAlgorithm() == kAlgorithmEcdsaP256Sha256; +} +#endif + +bool SigRecord::IsValid(void) const +{ + return GetType() == Dns::ResourceRecord::kTypeSig && GetLength() >= sizeof(*this) - sizeof(ResourceRecord); +} + +bool LeaseOption::IsValid(void) const +{ + return GetLeaseInterval() <= GetKeyLeaseInterval(); +} + } // namespace Dns } // namespace ot diff --git a/src/core/net/dns_headers.hpp b/src/core/net/dns_headers.hpp index 728346e7a..ebb291330 100644 --- a/src/core/net/dns_headers.hpp +++ b/src/core/net/dns_headers.hpp @@ -39,6 +39,7 @@ #include "common/clearable.hpp" #include "common/encoding.hpp" #include "common/message.hpp" +#include "crypto/ecdsa.hpp" #include "net/ip6_address.hpp" namespace ot { @@ -923,6 +924,14 @@ public: SetLength(sizeof(Ip6::Address)); } + /** + * This method tells whether this is a valid AAAA record. + * + * @returns A boolean indicates whether this is a valid AAAA record. + * + */ + bool IsValid(void) const; + /** * This method sets the IPv6 address of the resource record. * @@ -1095,6 +1104,14 @@ public: */ void Init(uint16_t aClass = kClassInternet) { ResourceRecord::Init(kTypeKey, aClass); } + /** + * This method tells whether the KEY record is valid. + * + * @returns TRUE if this is a valid KEY record, FALSE if an invalid KEY record. + * + */ + bool IsValid(void) const; + /** * This method gets the key use (or key type) flags. * @@ -1189,6 +1206,60 @@ private: } OT_TOOL_PACKED_END; +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +OT_TOOL_PACKED_BEGIN +class Ecdsa256KeyRecord : public KeyRecord, public Clearable +{ +public: + /** + * This method initializes the KEY Resource Record to ECDSA with curve P-256. + * + * Other record fields (TTL, length, flags, protocol) remain unchanged/uninitialized. + * + */ + void Init(void); + + /** + * This method tells whether this is a valid ECDSA DNSKEY with curve P-256. + * + * @returns A boolean that indicates whether this is a valid ECDSA DNSKEY RR with curve P-256. + * + */ + bool IsValid(void) const; + + /** + * This method returns the ECDSA P-256 public kek. + * + * @returns A reference to the public key. + * + */ + const Crypto::Ecdsa::P256::PublicKey &GetKey(void) const { return mKey; } + + /** + * This comparator tells whether two Ecdsa256KeyRecord objects are equal. + * + * @param[in] aOther The other Ecdsa256KeyRecord object. + * + * @returns TRUE if they are equal, FALSE if not. + * + */ + bool operator==(const Ecdsa256KeyRecord &aOther) const { return memcmp(this, &aOther, sizeof(*this)) == 0; } + + /** + * This comparator tells whether two Ecdsa256KeyRecord objects are not equal. + * + * @param[in] aOther The other Ecdsa256KeyRecord object. + * + * @returns TRUE if they are not equal, FALSE if equal. + * + */ + bool operator!=(const Ecdsa256KeyRecord &aOther) const { return !(*this == aOther); } + +private: + Crypto::Ecdsa::P256::PublicKey mKey; +} OT_TOOL_PACKED_END; +#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + /** * This class implements Resource Record body format of SIG type (RFC 2535 - section-4.1). * @@ -1210,6 +1281,14 @@ public: */ void Init(uint16_t aClass) { ResourceRecord::Init(kTypeSig, aClass); } + /** + * This method tells whether the SIG record is valid. + * + * @returns TRUE if this is a valid SIG record, FALSE if not a valid SIG record. + * + */ + bool IsValid(void) const; + /** * This method returns the SIG record's type-covered value. * @@ -1569,6 +1648,14 @@ public: SetOptionLength(kOptionLength); } + /** + * This method tells whether this is a valid Lease Option. + * + * @returns TRUE if this is a valid Lease Option, FALSE if not a valid Lease Option. + * + */ + bool IsValid(void) const; + /** * This method returns the Update Lease OPT record's lease interval value. * diff --git a/src/core/net/socket.hpp b/src/core/net/socket.hpp index bff21c48d..84d44d0e4 100644 --- a/src/core/net/socket.hpp +++ b/src/core/net/socket.hpp @@ -236,6 +236,19 @@ public: class SockAddr : public otSockAddr, public Clearable { public: + enum + { + // The socket address string length is: + // len('[') + len(mAddress) + len(']') + len(':') + len(mPort) + kIp6SocketAddressStringSize = 1 + Address::kIp6AddressStringSize + 1 + 1 + 5, + }; + + /** + * This type defines the fixed-length `String` object returned from `ToString()`. + * + */ + typedef String InfoString; + /** * This constructor initializes the socket address (all fields are set to zero). * diff --git a/src/core/net/srp_server.cpp b/src/core/net/srp_server.cpp new file mode 100644 index 000000000..72311c7e6 --- /dev/null +++ b/src/core/net/srp_server.cpp @@ -0,0 +1,1651 @@ +/* + * Copyright (c) 2020, 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. + */ + +/** + * @file + * This file includes implementation for SRP server. + */ + +#include "srp_server.hpp" + +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + +#include "common/instance.hpp" +#include "common/locator-getters.hpp" +#include "common/logging.hpp" +#include "common/new.hpp" +#include "net/dns_headers.hpp" +#include "thread/network_data_local.hpp" +#include "thread/network_data_notifier.hpp" +#include "thread/thread_netif.hpp" +#include "utils/heap.hpp" + +namespace ot { +namespace Srp { + +static const char kDefaultDomain[] = "default.service.arpa."; + +static Dns::UpdateHeader::Response ErrorToDnsResponseCode(otError aError) +{ + Dns::UpdateHeader::Response responseCode; + + switch (aError) + { + case OT_ERROR_NONE: + responseCode = Dns::UpdateHeader::kResponseSuccess; + break; + case OT_ERROR_NO_BUFS: + responseCode = Dns::UpdateHeader::kResponseServerFailure; + break; + case OT_ERROR_PARSE: + responseCode = Dns::UpdateHeader::kResponseFormatError; + break; + case OT_ERROR_DUPLICATED: + responseCode = Dns::UpdateHeader::kResponseNameExists; + break; + default: + responseCode = Dns::UpdateHeader::kResponseRefused; + break; + } + + return responseCode; +} + +Server::Server(Instance &aInstance) + : InstanceLocator(aInstance) + , mSocket(aInstance) + , mAdvertisingHandler(nullptr) + , mAdvertisingHandlerContext(nullptr) + , mDomain(nullptr) + , mMinLease(kDefaultMinLease) + , mMaxLease(kDefaultMaxLease) + , mMinKeyLease(kDefaultMinKeyLease) + , mMaxKeyLease(kDefaultMaxKeyLease) + , mLeaseTimer(aInstance, HandleLeaseTimer, this) + , mOutstandingUpdatesTimer(aInstance, HandleOutstandingUpdatesTimer, this) + , mEnabled(false) +{ + IgnoreError(SetDomain(kDefaultDomain)); +} + +Server::~Server(void) +{ + GetInstance().HeapFree(mDomain); +} + +void Server::SetServiceHandler(otSrpServerServiceUpdateHandler aServiceHandler, void *aServiceHandlerContext) +{ + mAdvertisingHandler = aServiceHandler; + mAdvertisingHandlerContext = aServiceHandlerContext; +} + +bool Server::IsRunning(void) const +{ + return mSocket.IsBound(); +} + +void Server::SetEnabled(bool aEnabled) +{ + VerifyOrExit(mEnabled != aEnabled); + + mEnabled = aEnabled; + + if (!mEnabled) + { + Stop(); + } + else if (Get().IsAttached()) + { + Start(); + } + +exit: + return; +} + +otError Server::SetLeaseRange(uint32_t aMinLease, uint32_t aMaxLease, uint32_t aMinKeyLease, uint32_t aMaxKeyLease) +{ + otError error = OT_ERROR_NONE; + + // TODO: Support longer LEASE. + // We use milliseconds timer for LEASE & KEY-LEASE, this is to avoid overflow. + VerifyOrExit(aMaxKeyLease <= TimerMilli::kMaxDelay / 1000, error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(aMinLease <= aMaxLease, error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(aMinKeyLease <= aMaxKeyLease, error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(aMinLease <= aMinKeyLease, error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(aMaxLease <= aMaxKeyLease, error = OT_ERROR_INVALID_ARGS); + + mMinLease = aMinLease; + mMaxLease = aMaxLease; + mMinKeyLease = aMinKeyLease; + mMaxKeyLease = aMaxKeyLease; + +exit: + return error; +} + +uint32_t Server::GrantLease(uint32_t aLease) const +{ + OT_ASSERT(mMinLease <= mMaxLease); + + return (aLease == 0) ? 0 : OT_MAX(mMinLease, OT_MIN(mMaxLease, aLease)); +} + +uint32_t Server::GrantKeyLease(uint32_t aKeyLease) const +{ + OT_ASSERT(mMinKeyLease <= mMaxKeyLease); + + return (aKeyLease == 0) ? 0 : OT_MAX(mMinKeyLease, OT_MIN(mMaxKeyLease, aKeyLease)); +} + +const char *Server::GetDomain(void) const +{ + return mDomain; +} + +otError Server::SetDomain(const char *aDomain) +{ + otError error = OT_ERROR_NONE; + char * buf = nullptr; + size_t appendTrailingDot = 0; + size_t length = strlen(aDomain); + + VerifyOrExit(!mEnabled, error = OT_ERROR_INVALID_STATE); + + VerifyOrExit(length > 0 && length <= Dns::Name::kMaxLength, error = OT_ERROR_INVALID_ARGS); + if (aDomain[length - 1] != '.') + { + appendTrailingDot = 1; + } + + buf = static_cast(GetInstance().HeapCAlloc(1, length + appendTrailingDot + 1)); + VerifyOrExit(buf != nullptr, error = OT_ERROR_NO_BUFS); + + strcpy(buf, aDomain); + if (appendTrailingDot) + { + buf[length] = '.'; + buf[length + 1] = '\0'; + } + GetInstance().HeapFree(mDomain); + mDomain = buf; + +exit: + if (error != OT_ERROR_NONE) + { + GetInstance().HeapFree(buf); + } + return error; +} + +const Server::Host *Server::GetNextHost(const Server::Host *aHost) +{ + return (aHost == nullptr) ? mHosts.GetHead() : aHost->GetNext(); +} + +// This method adds a SRP service host and takes ownership of it. +// The caller MUST make sure that there is no existing host with the same hostname. +void Server::AddHost(Host *aHost) +{ + OT_ASSERT(mHosts.FindMatching(aHost->GetFullName()) == nullptr); + IgnoreError(mHosts.Add(*aHost)); +} + +void Server::RemoveAndFreeHost(Host *aHost) +{ + otLogInfoSrp("[server] fully remove host %s", aHost->GetFullName()); + IgnoreError(mHosts.Remove(*aHost)); + aHost->Free(); +} + +Server::Service *Server::FindService(const char *aFullName) +{ + Service *service = nullptr; + + for (Host *host = mHosts.GetHead(); host != nullptr; host = host->GetNext()) + { + service = host->FindService(aFullName); + if (service != nullptr) + { + break; + } + } + + return service; +} + +bool Server::HasNameConflictsWith(Host &aHost) +{ + bool hasConflicts = false; + const Service *service = nullptr; + Host * existingHost = mHosts.FindMatching(aHost.GetFullName()); + + if (existingHost != nullptr && *aHost.GetKey() != *existingHost->GetKey()) + { + ExitNow(hasConflicts = true); + } + + // Check not only services of this host but all hosts. + while ((service = aHost.GetNextService(service)) != nullptr) + { + Service *existingService = FindService(service->mFullName); + if (existingService != nullptr && *service->GetHost().GetKey() != *existingService->GetHost().GetKey()) + { + ExitNow(hasConflicts = true); + } + } + +exit: + return hasConflicts; +} + +void Server::HandleAdvertisingResult(const Host *aHost, otError aError) +{ + UpdateMetadata *update = mOutstandingUpdates.FindMatching(aHost); + + if (update != nullptr) + { + HandleAdvertisingResult(update, aError); + } + else + { + otLogInfoSrp("[server] delayed SRP host update result, the SRP update has been committed"); + } +} + +void Server::HandleAdvertisingResult(UpdateMetadata *aUpdate, otError aError) +{ + HandleSrpUpdateResult(aError, aUpdate->GetDnsHeader(), aUpdate->GetHost(), aUpdate->GetMessageInfo()); + + IgnoreError(mOutstandingUpdates.Remove(*aUpdate)); + aUpdate->Free(); + + if (mOutstandingUpdates.IsEmpty()) + { + mOutstandingUpdatesTimer.Stop(); + } + else + { + mOutstandingUpdatesTimer.StartAt(mOutstandingUpdates.GetTail()->GetExpireTime(), 0); + } +} + +void Server::HandleSrpUpdateResult(otError aError, + const Dns::UpdateHeader &aDnsHeader, + Host & aHost, + const Ip6::MessageInfo & aMessageInfo) +{ + Host * existingHost; + uint32_t hostLease; + uint32_t hostKeyLease; + uint32_t grantedLease; + uint32_t grantedKeyLease; + + SuccessOrExit(aError); + + hostLease = aHost.GetLease(); + hostKeyLease = aHost.GetKeyLease(); + grantedLease = GrantLease(hostLease); + grantedKeyLease = GrantKeyLease(hostKeyLease); + + aHost.SetLease(grantedLease); + aHost.SetKeyLease(grantedKeyLease); + + existingHost = mHosts.FindMatching(aHost.GetFullName()); + + if (aHost.GetLease() == 0) + { + if (aHost.GetKeyLease() == 0) + { + otLogInfoSrp("[server] remove key of host %s", aHost.GetFullName()); + + if (existingHost != nullptr) + { + RemoveAndFreeHost(existingHost); + } + } + else if (existingHost != nullptr) + { + Service *service = nullptr; + + existingHost->SetLease(aHost.GetLease()); + existingHost->SetKeyLease(aHost.GetKeyLease()); + + // Clear all resources associated to this host and its services. + existingHost->ClearResources(); + otLogInfoSrp("[server] remove host '%s' (but retain its name)", existingHost->GetFullName()); + while ((service = existingHost->GetNextService(service)) != nullptr) + { + service->DeleteResourcesButRetainName(); + } + } + + aHost.Free(); + } + else if (existingHost != nullptr) + { + const Service *service = nullptr; + + // Merge current updates into existing host. + + otLogInfoSrp("[server] update host %s", existingHost->GetFullName()); + + existingHost->CopyResourcesFrom(aHost); + while ((service = aHost.GetNextService(service)) != nullptr) + { + Service *existingService = existingHost->FindService(service->mFullName); + + if (service->mIsDeleted) + { + if (existingService != nullptr) + { + existingService->DeleteResourcesButRetainName(); + } + } + else + { + Service *newService = existingHost->AddService(service->mFullName); + + VerifyOrExit(newService != nullptr, aError = OT_ERROR_NO_BUFS); + SuccessOrExit(aError = newService->CopyResourcesFrom(*service)); + otLogInfoSrp("[server] %s service %s", (existingService != nullptr) ? "update existing" : "add new", + service->mFullName); + } + } + + aHost.Free(); + } + else + { + otLogInfoSrp("[server] add new host %s", aHost.GetFullName()); + AddHost(&aHost); + } + + // Re-schedule the lease timer. + HandleLeaseTimer(); + +exit: + if (aError == OT_ERROR_NONE && !(grantedLease == hostLease && grantedKeyLease == hostKeyLease)) + { + SendResponse(aDnsHeader, grantedLease, grantedKeyLease, aMessageInfo); + } + else + { + SendResponse(aDnsHeader, ErrorToDnsResponseCode(aError), aMessageInfo); + } +} + +void Server::Start(void) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(!IsRunning()); + + SuccessOrExit(error = mSocket.Open(HandleUdpReceive, this)); + SuccessOrExit(error = mSocket.Bind(0)); + + SuccessOrExit(error = PublishServerData()); + + otLogInfoSrp("[server] start listening on port %hu", mSocket.GetSockName().mPort); + +exit: + if (error != OT_ERROR_NONE) + { + otLogCritSrp("[server] failed to start: %s", otThreadErrorToString(error)); + // Cleanup any resources we may have allocated. + Stop(); + } +} + +void Server::Stop(void) +{ + VerifyOrExit(IsRunning()); + + UnpublishServerData(); + + while (!mHosts.IsEmpty()) + { + mHosts.Pop()->Free(); + } + + while (!mOutstandingUpdates.IsEmpty()) + { + mOutstandingUpdates.Pop()->Free(); + } + + mLeaseTimer.Stop(); + mOutstandingUpdatesTimer.Stop(); + + otLogInfoSrp("[server] stop listening on %hu", mSocket.GetSockName().mPort); + IgnoreError(mSocket.Close()); + +exit: + return; +} + +void Server::HandleNotifierEvents(Events aEvents) +{ + VerifyOrExit(mEnabled); + VerifyOrExit(aEvents.Contains(kEventThreadRoleChanged)); + + if (Get().IsAttached()) + { + Start(); + } + else + { + Stop(); + } + +exit: + return; +} + +otError Server::PublishServerData(void) +{ + otError error; + const uint8_t serviceData[] = {kThreadServiceTypeSrpServer}; + uint8_t serverData[sizeof(uint16_t)]; + + OT_ASSERT(mSocket.IsBound()); + + Encoding::BigEndian::WriteUint16(mSocket.GetSockName().mPort, serverData); + + SuccessOrExit(error = Get().AddService( + NetworkData::ServiceTlv::kThreadEnterpriseNumber, serviceData, sizeof(serviceData), + /* aServerStable */ true, serverData, sizeof(serverData))); + Get().HandleServerDataUpdated(); + +exit: + return error; +} + +void Server::UnpublishServerData(void) +{ + otError error; + const uint8_t serviceData[] = {kThreadServiceTypeSrpServer}; + + SuccessOrExit(error = Get().RemoveService(NetworkData::ServiceTlv::kThreadEnterpriseNumber, + serviceData, sizeof(serviceData))); + Get().HandleServerDataUpdated(); + +exit: + if (error != OT_ERROR_NONE) + { + otLogWarnSrp("[server] failed to unpublish SRP service: %s", otThreadErrorToString(error)); + } +} + +const Server::UpdateMetadata *Server::FindOutstandingUpdate(const Ip6::MessageInfo &aMessageInfo, + uint16_t aDnsMessageId) +{ + const UpdateMetadata *ret = nullptr; + + for (const UpdateMetadata *update = mOutstandingUpdates.GetHead(); update != nullptr; update = update->GetNext()) + { + if (aDnsMessageId == update->GetDnsHeader().GetMessageId() && + aMessageInfo.GetPeerAddr() == update->GetMessageInfo().GetPeerAddr() && + aMessageInfo.GetPeerPort() == update->GetMessageInfo().GetPeerPort()) + { + ExitNow(ret = update); + } + } + +exit: + return ret; +} + +void Server::HandleDnsUpdate(Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const Dns::UpdateHeader &aDnsHeader, + uint16_t aOffset) +{ + otError error = OT_ERROR_NONE; + Dns::Zone zone; + Host * host = nullptr; + + uint16_t headerOffset = aOffset - sizeof(aDnsHeader); + + otLogInfoSrp("[server] receive DNS update from %s", aMessageInfo.GetPeerAddr().ToString().AsCString()); + + SuccessOrExit(error = ProcessZoneSection(aMessage, aDnsHeader, aOffset, zone)); + + if (FindOutstandingUpdate(aMessageInfo, aDnsHeader.GetMessageId()) != nullptr) + { + otLogInfoSrp("[server] drop duplicated SRP update request: messageId=%hu", aDnsHeader.GetMessageId()); + + // Silently drop duplicate requests. + // This could rarely happen, because the outstanding SRP update timer should + // be shorter than the SRP update retransmission timer. + ExitNow(error = OT_ERROR_NONE); + } + + // Per 2.3.2 of SRP draft 6, no prerequisites should be included in a SRP update. + VerifyOrExit(aDnsHeader.GetPrerequisiteRecordCount() == 0, error = OT_ERROR_FAILED); + + host = Host::New(GetInstance()); + VerifyOrExit(host != nullptr, error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = ProcessUpdateSection(*host, aMessage, aDnsHeader, zone, headerOffset, aOffset)); + + // Parse lease time and validate signature. + SuccessOrExit(error = ProcessAdditionalSection(host, aMessage, aDnsHeader, headerOffset, aOffset)); + + HandleUpdate(aDnsHeader, host, aMessageInfo); + +exit: + if (error != OT_ERROR_NONE) + { + if (host != nullptr) + { + host->Free(); + } + SendResponse(aDnsHeader, ErrorToDnsResponseCode(error), aMessageInfo); + } +} + +otError Server::ProcessZoneSection(const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + uint16_t & aOffset, + Dns::Zone & aZone) +{ + otError error = OT_ERROR_NONE; + Dns::Zone zone; + + VerifyOrExit(aDnsHeader.GetZoneRecordCount() == 1, error = OT_ERROR_PARSE); + + SuccessOrExit(error = Dns::Name::ParseName(aMessage, aOffset)); + SuccessOrExit(error = aMessage.Read(aOffset, zone)); + aOffset += sizeof(zone); + + VerifyOrExit(zone.GetType() == Dns::ResourceRecord::kTypeSoa, error = OT_ERROR_PARSE); + aZone = zone; + +exit: + return error; +} + +otError Server::ProcessUpdateSection(Host & aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + const Dns::Zone & aZone, + uint16_t aHeaderOffset, + uint16_t & aOffset) +{ + otError error = OT_ERROR_NONE; + + // Process Service Discovery, Host and Service Description Instructions with + // 3 times iterations over all DNS update RRs. The order of those processes matters. + + // 0. Enumerate over all Service Discovery Instructions before processing any other records. + // So that we will know whether a name is a hostname or service instance name when processing + // a "Delete All RRsets from a name" record. + error = ProcessServiceDiscoveryInstructions(aHost, aMessage, aDnsHeader, aZone, aHeaderOffset, aOffset); + SuccessOrExit(error); + + // 1. Enumerate over all RRs to build the Host Description Instruction. + error = ProcessHostDescriptionInstruction(aHost, aMessage, aDnsHeader, aZone, aHeaderOffset, aOffset); + SuccessOrExit(error); + + // 2. Enumerate over all RRs to build the Service Description Insutructions. + error = ProcessServiceDescriptionInstructions(aHost, aMessage, aDnsHeader, aZone, aHeaderOffset, aOffset); + SuccessOrExit(error); + + // 3. Verify that there are no name conflicts. + VerifyOrExit(!HasNameConflictsWith(aHost), error = OT_ERROR_DUPLICATED); + +exit: + return error; +} + +otError Server::ProcessHostDescriptionInstruction(Host & aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + const Dns::Zone & aZone, + uint16_t aHeaderOffset, + uint16_t aOffset) +{ + otError error; + + OT_ASSERT(aHost.GetFullName() == nullptr); + + for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i) + { + char name[Dns::Name::kMaxLength + 1]; + Dns::ResourceRecord record; + + SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, aHeaderOffset, name, sizeof(name))); + SuccessOrExit(error = aMessage.Read(aOffset, record)); + + if (record.GetClass() == Dns::ResourceRecord::kClassAny) + { + Service *service; + + // Delete All RRsets from a name. + VerifyOrExit(IsValidDeleteAllRecord(record), error = OT_ERROR_FAILED); + + service = aHost.FindService(name); + if (service == nullptr) + { + // A "Delete All RRsets from a name" RR can only apply to a Service or Host Description. + + if (aHost.GetFullName()) + { + VerifyOrExit(aHost.Matches(name), error = OT_ERROR_FAILED); + } + else + { + SuccessOrExit(error = aHost.SetFullName(name)); + } + aHost.ClearResources(); + } + + aOffset += record.GetSize(); + continue; + } + + if (record.GetType() == Dns::ResourceRecord::kTypeAaaa) + { + Dns::AaaaRecord aaaaRecord; + + VerifyOrExit(record.GetClass() == aZone.GetClass(), error = OT_ERROR_FAILED); + if (aHost.GetFullName() == nullptr) + { + SuccessOrExit(error = aHost.SetFullName(name)); + } + else + { + VerifyOrExit(aHost.Matches(name), error = OT_ERROR_FAILED); + } + + SuccessOrExit(error = aMessage.Read(aOffset, aaaaRecord)); + VerifyOrExit(aaaaRecord.IsValid(), error = OT_ERROR_PARSE); + + // Tolerate OT_ERROR_DROP for AAAA Resources. + VerifyOrExit(aHost.AddIp6Address(aaaaRecord.GetAddress()) != OT_ERROR_NO_BUFS, error = OT_ERROR_NO_BUFS); + + aOffset += aaaaRecord.GetSize(); + } + else if (record.GetType() == Dns::ResourceRecord::kTypeKey) + { + // We currently support only ECDSA P-256. + Dns::Ecdsa256KeyRecord key; + + VerifyOrExit(record.GetClass() == aZone.GetClass(), error = OT_ERROR_FAILED); + VerifyOrExit(aHost.GetKey() == nullptr, error = OT_ERROR_FAILED); + + SuccessOrExit(error = aMessage.Read(aOffset, key)); + VerifyOrExit(key.IsValid(), error = OT_ERROR_PARSE); + + aHost.SetKey(key); + + aOffset += record.GetSize(); + } + else + { + aOffset += record.GetSize(); + } + } + + // Verify that we have a complete Host Description Instruction. + + VerifyOrExit(aHost.GetFullName() != nullptr, error = OT_ERROR_FAILED); + VerifyOrExit(aHost.GetKey() != nullptr, error = OT_ERROR_FAILED); + { + uint8_t hostAddressesNum; + + aHost.GetAddresses(hostAddressesNum); + + // There MUST be at least one valid address if we have nonzero lease. + VerifyOrExit(aHost.GetLease() > 0 || hostAddressesNum > 0, error = OT_ERROR_FAILED); + } + +exit: + return error; +} + +otError Server::ProcessServiceDiscoveryInstructions(Host & aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + const Dns::Zone & aZone, + uint16_t aHeaderOffset, + uint16_t aOffset) +{ + otError error = OT_ERROR_NONE; + + for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i) + { + char name[Dns::Name::kMaxLength + 1]; + Dns::ResourceRecord record; + char serviceName[Dns::Name::kMaxLength + 1]; + Service * service; + + SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, aHeaderOffset, name, sizeof(name))); + SuccessOrExit(error = aMessage.Read(aOffset, record)); + + aOffset += sizeof(record); + + if (record.GetType() == Dns::ResourceRecord::kTypePtr) + { + SuccessOrExit(error = + Dns::Name::ReadName(aMessage, aOffset, aHeaderOffset, serviceName, sizeof(serviceName))); + } + else + { + aOffset += record.GetLength(); + continue; + } + + VerifyOrExit(record.GetClass() == Dns::ResourceRecord::kClassNone || record.GetClass() == aZone.GetClass(), + error = OT_ERROR_FAILED); + + // TODO: check if the RR name and the full service name matches. + + service = aHost.FindService(serviceName); + VerifyOrExit(service == nullptr, error = OT_ERROR_FAILED); + service = aHost.AddService(serviceName); + VerifyOrExit(service != nullptr, error = OT_ERROR_NO_BUFS); + + // This RR is a "Delete an RR from an RRset" update when the CLASS is NONE. + service->mIsDeleted = (record.GetClass() == Dns::ResourceRecord::kClassNone); + } + +exit: + return error; +} + +otError Server::ProcessServiceDescriptionInstructions(Host & aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + const Dns::Zone & aZone, + uint16_t aHeaderOffset, + uint16_t & aOffset) +{ + Service *service; + otError error = OT_ERROR_NONE; + + for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i) + { + char name[Dns::Name::kMaxLength + 1]; + Dns::ResourceRecord record; + + SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, aHeaderOffset, name, sizeof(name))); + SuccessOrExit(error = aMessage.Read(aOffset, record)); + + if (record.GetClass() == Dns::ResourceRecord::kClassAny) + { + // Delete All RRsets from a name. + VerifyOrExit(IsValidDeleteAllRecord(record), error = OT_ERROR_FAILED); + service = aHost.FindService(name); + if (service != nullptr) + { + service->ClearResources(); + } + + aOffset += record.GetSize(); + continue; + } + + if (record.GetType() == Dns::ResourceRecord::kTypeSrv) + { + Dns::SrvRecord srvRecord; + char hostName[Dns::Name::kMaxLength + 1]; + uint16_t hostNameLength = sizeof(hostName); + + VerifyOrExit(record.GetClass() == aZone.GetClass(), error = OT_ERROR_FAILED); + SuccessOrExit(error = aMessage.Read(aOffset, srvRecord)); + aOffset += sizeof(srvRecord); + + SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, aHeaderOffset, hostName, hostNameLength)); + VerifyOrExit(aHost.Matches(hostName), error = OT_ERROR_FAILED); + + service = aHost.FindService(name); + VerifyOrExit(service != nullptr && !service->mIsDeleted, error = OT_ERROR_FAILED); + + // Make sure that this is the first SRV RR for this service. + VerifyOrExit(service->mPort == 0, error = OT_ERROR_FAILED); + service->mPriority = srvRecord.GetPriority(); + service->mWeight = srvRecord.GetWeight(); + service->mPort = srvRecord.GetPort(); + } + else if (record.GetType() == Dns::ResourceRecord::kTypeTxt) + { + VerifyOrExit(record.GetClass() == aZone.GetClass(), error = OT_ERROR_FAILED); + + service = aHost.FindService(name); + VerifyOrExit(service != nullptr && !service->mIsDeleted, error = OT_ERROR_FAILED); + + aOffset += sizeof(record); + SuccessOrExit(error = service->SetTxtDataFromMessage(aMessage, aOffset, record.GetLength())); + aOffset += record.GetLength(); + } + else + { + aOffset += record.GetSize(); + } + } + + service = nullptr; + while ((service = aHost.GetNextService(service)) != nullptr) + { + VerifyOrExit(service->mIsDeleted || (service->mTxtData != nullptr && service->mPort != 0), + error = OT_ERROR_FAILED); + } + +exit: + return error; +} + +bool Server::IsValidDeleteAllRecord(const Dns::ResourceRecord &aRecord) +{ + return aRecord.GetClass() == Dns::ResourceRecord::kClassAny && aRecord.GetType() == Dns::ResourceRecord::kTypeAny && + aRecord.GetTtl() == 0 && aRecord.GetLength() == 0; +} + +otError Server::ProcessAdditionalSection(Host * aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + uint16_t aHeaderOffset, + uint16_t & aOffset) +{ + otError error = OT_ERROR_NONE; + Dns::OptRecord optRecord; + Dns::LeaseOption leaseOption; + Dns::SigRecord sigRecord; + char name[2]; // The root domain name (".") is expected. + uint16_t sigOffset; + uint16_t sigRdataOffset; + char signerName[Dns::Name::kMaxLength + 1]; + uint16_t signatureLength; + + VerifyOrExit(aDnsHeader.GetAdditionalRecordCount() == 2, error = OT_ERROR_FAILED); + + // EDNS(0) Update Lease Option. + + SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, aHeaderOffset, name, sizeof(name))); + SuccessOrExit(error = aMessage.Read(aOffset, optRecord)); + SuccessOrExit(error = aMessage.Read(aOffset + sizeof(optRecord), leaseOption)); + VerifyOrExit(leaseOption.IsValid(), error = OT_ERROR_FAILED); + VerifyOrExit(optRecord.GetSize() == sizeof(optRecord) + sizeof(leaseOption), error = OT_ERROR_PARSE); + + aOffset += optRecord.GetSize(); + + aHost->SetLease(leaseOption.GetLeaseInterval()); + aHost->SetKeyLease(leaseOption.GetKeyLeaseInterval()); + + // SIG(0). + + sigOffset = aOffset; + SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, aHeaderOffset, name, sizeof(name))); + SuccessOrExit(error = aMessage.Read(aOffset, sigRecord)); + VerifyOrExit(sigRecord.IsValid(), error = OT_ERROR_PARSE); + + sigRdataOffset = aOffset + sizeof(Dns::ResourceRecord); + aOffset += sizeof(sigRecord); + + // TODO: Verify that the signature doesn't expire. This is not + // implemented because the end device may not be able to get + // the synchronized date/time. + + SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, aHeaderOffset, signerName, sizeof(signerName))); + + signatureLength = sigRecord.GetLength() - (aOffset - sigRdataOffset); + aOffset += signatureLength; + + // Verify the signature. Currently supports only ECDSA. + + VerifyOrExit(sigRecord.GetAlgorithm() == Dns::KeyRecord::kAlgorithmEcdsaP256Sha256, error = OT_ERROR_FAILED); + VerifyOrExit(sigRecord.GetTypeCovered() == 0, error = OT_ERROR_FAILED); + VerifyOrExit(signatureLength == Crypto::Ecdsa::P256::Signature::kSize, error = OT_ERROR_PARSE); + + SuccessOrExit(error = VerifySignature(*aHost->GetKey(), aMessage, aDnsHeader, sigOffset, sigRdataOffset, + sigRecord.GetLength(), signerName)); + +exit: + return error; +} + +otError Server::VerifySignature(const Dns::Ecdsa256KeyRecord &aKey, + const Message & aMessage, + Dns::UpdateHeader aDnsHeader, + uint16_t aSigOffset, + uint16_t aSigRdataOffset, + uint16_t aSigRdataLength, + const char * aSignerName) +{ + otError error; + uint16_t offset = aMessage.GetOffset(); + uint16_t signatureOffset; + Crypto::Sha256 sha256; + Crypto::Sha256::Hash hash; + Crypto::Ecdsa::P256::Signature signature; + Message * signerNameMessage = nullptr; + + VerifyOrExit(aSigRdataLength >= Crypto::Ecdsa::P256::Signature::kSize, error = OT_ERROR_INVALID_ARGS); + + sha256.Start(); + + // SIG RDATA less signature. + sha256.Update(aMessage, aSigRdataOffset, sizeof(Dns::SigRecord) - sizeof(Dns::ResourceRecord)); + + // The uncompressed (canonical) form of the signer name should be used for signature + // verification. See https://tools.ietf.org/html/rfc2931#section-3.1 for details. + signerNameMessage = Get().NewMessage(0); + VerifyOrExit(signerNameMessage != nullptr, error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = Dns::Name::AppendName(aSignerName, *signerNameMessage)); + sha256.Update(*signerNameMessage, signerNameMessage->GetOffset(), signerNameMessage->GetLength()); + + // We need the DNS header before appending the SIG RR. + aDnsHeader.SetAdditionalRecordCount(aDnsHeader.GetAdditionalRecordCount() - 1); + sha256.Update(aDnsHeader); + sha256.Update(aMessage, offset + sizeof(aDnsHeader), aSigOffset - offset - sizeof(aDnsHeader)); + + sha256.Finish(hash); + + signatureOffset = aSigRdataOffset + aSigRdataLength - Crypto::Ecdsa::P256::Signature::kSize; + SuccessOrExit(error = aMessage.Read(signatureOffset, signature)); + + error = aKey.GetKey().Verify(hash, signature); + +exit: + FreeMessage(signerNameMessage); + return error; +} + +void Server::HandleUpdate(const Dns::UpdateHeader &aDnsHeader, Host *aHost, const Ip6::MessageInfo &aMessageInfo) +{ + otError error = OT_ERROR_NONE; + + if (aHost->GetLease() == 0) + { + Host *existingHost = mHosts.FindMatching(aHost->GetFullName()); + + aHost->ClearResources(); + + // The client may not include all services it has registered and we should append + // those services for current SRP update. + if (existingHost != nullptr) + { + Service *existingService = nullptr; + + while ((existingService = existingHost->GetNextService(existingService)) != nullptr) + { + if (!existingService->mIsDeleted) + { + Service *service = aHost->AddService(existingService->mFullName); + VerifyOrExit(service != nullptr, error = OT_ERROR_NO_BUFS); + service->mIsDeleted = true; + } + } + } + } + +exit: + if (error != OT_ERROR_NONE) + { + HandleSrpUpdateResult(error, aDnsHeader, *aHost, aMessageInfo); + } + else if (mAdvertisingHandler != nullptr) + { + UpdateMetadata *update = UpdateMetadata::New(GetInstance(), aDnsHeader, aHost, aMessageInfo); + + IgnoreError(mOutstandingUpdates.Add(*update)); + mOutstandingUpdatesTimer.StartAt(mOutstandingUpdates.GetTail()->GetExpireTime(), 0); + + mAdvertisingHandler(aHost, kDefaultEventsHandlerTimeout, mAdvertisingHandlerContext); + } + else + { + HandleSrpUpdateResult(OT_ERROR_NONE, aDnsHeader, *aHost, aMessageInfo); + } +} + +void Server::SendResponse(const Dns::UpdateHeader & aHeader, + Dns::UpdateHeader::Response aResponseCode, + const Ip6::MessageInfo & aMessageInfo) +{ + otError error; + Message * response = nullptr; + Dns::UpdateHeader header; + + response = mSocket.NewMessage(0); + VerifyOrExit(response != nullptr, error = OT_ERROR_NO_BUFS); + + header.SetMessageId(aHeader.GetMessageId()); + header.SetType(Dns::UpdateHeader::kTypeResponse); + header.SetQueryType(aHeader.GetQueryType()); + header.SetResponseCode(aResponseCode); + SuccessOrExit(error = response->Append(header)); + + SuccessOrExit(error = mSocket.SendTo(*response, aMessageInfo)); + + if (aResponseCode != Dns::UpdateHeader::kResponseSuccess) + { + otLogInfoSrp("[server] send fail response: %d", aResponseCode); + } + else + { + otLogInfoSrp("[server] send success response"); + } + +exit: + if (error != OT_ERROR_NONE) + { + otLogWarnSrp("[server] failed to send response: %s", otThreadErrorToString(error)); + FreeMessage(response); + } +} + +void Server::SendResponse(const Dns::UpdateHeader &aHeader, + uint32_t aLease, + uint32_t aKeyLease, + const Ip6::MessageInfo & aMessageInfo) +{ + otError error; + Message * response = nullptr; + Dns::UpdateHeader header; + Dns::OptRecord optRecord; + Dns::LeaseOption leaseOption; + + response = mSocket.NewMessage(0); + VerifyOrExit(response != nullptr, error = OT_ERROR_NO_BUFS); + + header.SetMessageId(aHeader.GetMessageId()); + header.SetType(Dns::UpdateHeader::kTypeResponse); + header.SetQueryType(aHeader.GetQueryType()); + header.SetResponseCode(Dns::UpdateHeader::kResponseSuccess); + header.SetAdditionalRecordCount(1); + SuccessOrExit(error = response->Append(header)); + + // Append the root domain ("."). + SuccessOrExit(error = Dns::Name::AppendTerminator(*response)); + + optRecord.Init(); + optRecord.SetUdpPayloadSize(kUdpPayloadSize); + optRecord.SetDnsSecurityFlag(); + optRecord.SetLength(sizeof(Dns::LeaseOption)); + SuccessOrExit(error = response->Append(optRecord)); + + leaseOption.Init(); + leaseOption.SetLeaseInterval(aLease); + leaseOption.SetKeyLeaseInterval(aKeyLease); + SuccessOrExit(error = response->Append(leaseOption)); + + SuccessOrExit(error = mSocket.SendTo(*response, aMessageInfo)); + + otLogInfoSrp("[server] send response with granted lease: %u and key lease: %u", aLease, aKeyLease); + +exit: + if (error != OT_ERROR_NONE) + { + otLogWarnSrp("[server] failed to send response: %s", otThreadErrorToString(error)); + FreeMessage(response); + } +} + +void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) +{ + static_cast(aContext)->HandleUdpReceive(*static_cast(aMessage), + *static_cast(aMessageInfo)); +} + +void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + otError error; + Dns::UpdateHeader dnsHeader; + uint16_t offset = aMessage.GetOffset(); + + SuccessOrExit(error = aMessage.Read(offset, dnsHeader)); + offset += sizeof(dnsHeader); + + // Handles only queries. + VerifyOrExit(dnsHeader.GetType() == Dns::UpdateHeader::Type::kTypeQuery, error = OT_ERROR_DROP); + + switch (dnsHeader.GetQueryType()) + { + case Dns::UpdateHeader::kQueryTypeUpdate: + HandleDnsUpdate(aMessage, aMessageInfo, dnsHeader, offset); + break; + default: + error = OT_ERROR_DROP; + break; + } + +exit: + if (error != OT_ERROR_NONE) + { + otLogInfoSrp("[server] failed to handle DNS message: %s", otThreadErrorToString(error)); + } +} + +void Server::HandleLeaseTimer(Timer &aTimer) +{ + aTimer.GetOwner().HandleLeaseTimer(); +} + +void Server::HandleLeaseTimer(void) +{ + TimeMilli now = TimerMilli::GetNow(); + TimeMilli earliestExpireTime = now.GetDistantFuture(); + Host * host = mHosts.GetHead(); + + while (host != nullptr) + { + Host *nextHost = host->GetNext(); + + if (host->GetKeyExpireTime() <= now) + { + otLogInfoSrp("[server] KEY LEASE of host %s expired", host->GetFullName()); + + // Removes the whole host and all services if the KEY RR expired. + RemoveAndFreeHost(host); + } + else if (host->IsDeleted()) + { + // The host has been deleted, but the hostname & service instance names retain. + + Service *service; + + earliestExpireTime = OT_MIN(earliestExpireTime, host->GetKeyExpireTime()); + + // Check if any service instance name expired. + service = host->GetNextService(nullptr); + while (service != nullptr) + { + OT_ASSERT(service->mIsDeleted); + + Service *nextService = service->GetNext(); + + if (service->GetKeyExpireTime() <= now) + { + otLogInfoSrp("[server] KEY LEASE of service %s expired", service->mFullName); + host->RemoveAndFreeService(service); + } + else + { + earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime()); + } + + service = nextService; + } + } + else if (host->GetExpireTime() <= now) + { + Service *service = nullptr; + + otLogInfoSrp("[server] LEASE of host %s expired", host->GetFullName()); + + // If the host expired, delete all resources of this host and its services. + host->DeleteResourcesButRetainName(); + while ((service = host->GetNextService(service)) != nullptr) + { + service->DeleteResourcesButRetainName(); + } + + earliestExpireTime = OT_MIN(earliestExpireTime, host->GetKeyExpireTime()); + } + else + { + // The host doesn't expire, check if any service expired or is explicitly removed. + + OT_ASSERT(!host->IsDeleted()); + + Service *service = host->GetNextService(nullptr); + + earliestExpireTime = OT_MIN(earliestExpireTime, host->GetExpireTime()); + + while (service != nullptr) + { + Service *nextService = service->GetNext(); + + if (service->mIsDeleted) + { + // The service has been deleted but the name retains. + earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime()); + } + else if (service->GetExpireTime() <= now) + { + otLogInfoSrp("[server] LEASE of service %s expired", service->mFullName); + + // The service gets expired, delete it. + service->DeleteResourcesButRetainName(); + earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime()); + } + else + { + earliestExpireTime = OT_MIN(earliestExpireTime, service->GetExpireTime()); + } + + service = nextService; + } + } + + host = nextHost; + } + + if (earliestExpireTime != now.GetDistantFuture()) + { + if (!mLeaseTimer.IsRunning() || earliestExpireTime <= mLeaseTimer.GetFireTime()) + { + otLogInfoSrp("[server] lease timer is scheduled for %u seconds", (earliestExpireTime - now) / 1000); + mLeaseTimer.StartAt(earliestExpireTime, 0); + } + } + else + { + otLogInfoSrp("[server] lease timer is stopped"); + mLeaseTimer.Stop(); + } +} + +void Server::HandleOutstandingUpdatesTimer(Timer &aTimer) +{ + aTimer.GetOwner().HandleOutstandingUpdatesTimer(); +} + +void Server::HandleOutstandingUpdatesTimer(void) +{ + while (!mOutstandingUpdates.IsEmpty() && mOutstandingUpdates.GetTail()->GetExpireTime() <= TimerMilli::GetNow()) + { + HandleAdvertisingResult(mOutstandingUpdates.GetTail(), OT_ERROR_RESPONSE_TIMEOUT); + } +} + +Server::Service *Server::Service::New(Instance &aInstance, const char *aFullName) +{ + void * buf; + Service *service = nullptr; + + buf = aInstance.HeapCAlloc(1, sizeof(Service)); + VerifyOrExit(buf != nullptr); + + service = new (buf) Service(aInstance); + if (service->SetFullName(aFullName) != OT_ERROR_NONE) + { + service->Free(); + service = nullptr; + } + +exit: + return service; +} + +void Server::Service::Free(void) +{ + GetInstance().HeapFree(mFullName); + GetInstance().HeapFree(mTxtData); + GetInstance().HeapFree(this); +} + +Server::Service::Service(Instance &aInstance) + : InstanceLocator(aInstance) + , mFullName(nullptr) + , mPriority(0) + , mWeight(0) + , mPort(0) + , mTxtLength(0) + , mTxtData(nullptr) + , mHost(nullptr) + , mNext(nullptr) + , mTimeLastUpdate(TimerMilli::GetNow()) +{ +} + +otError Server::Service::SetFullName(const char *aFullName) +{ + OT_ASSERT(aFullName != nullptr); + + otError error = OT_ERROR_NONE; + char * nameCopy = static_cast(GetInstance().HeapCAlloc(1, strlen(aFullName) + 1)); + + VerifyOrExit(nameCopy != nullptr, error = OT_ERROR_NO_BUFS); + strcpy(nameCopy, aFullName); + + GetInstance().HeapFree(mFullName); + mFullName = nameCopy; + +exit: + return error; +} + +TimeMilli Server::Service::GetExpireTime(void) const +{ + OT_ASSERT(!mIsDeleted); + OT_ASSERT(!GetHost().IsDeleted()); + + return mTimeLastUpdate + Time::SecToMsec(GetHost().GetLease()); +} + +TimeMilli Server::Service::GetKeyExpireTime(void) const +{ + return mTimeLastUpdate + Time::SecToMsec(GetHost().GetKeyLease()); +} + +otError Server::Service::SetTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength) +{ + otError error = OT_ERROR_NONE; + uint8_t *txtData; + + txtData = static_cast(GetInstance().HeapCAlloc(1, aTxtDataLength)); + VerifyOrExit(txtData != nullptr, error = OT_ERROR_NO_BUFS); + + memcpy(txtData, aTxtData, aTxtDataLength); + + GetInstance().HeapFree(mTxtData); + mTxtData = txtData; + mTxtLength = aTxtDataLength; + + // If a TXT RR is associated to this service, the service will retain. + mIsDeleted = false; + +exit: + return error; +} + +otError Server::Service::SetTxtDataFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength) +{ + otError error = OT_ERROR_NONE; + uint8_t *txtData; + + txtData = static_cast(GetInstance().HeapCAlloc(1, aLength)); + VerifyOrExit(txtData != nullptr, error = OT_ERROR_NO_BUFS); + VerifyOrExit(aMessage.ReadBytes(aOffset, txtData, aLength) == aLength, error = OT_ERROR_PARSE); + + GetInstance().HeapFree(mTxtData); + mTxtData = txtData; + mTxtLength = aLength; + + mIsDeleted = false; + +exit: + if (error != OT_ERROR_NONE) + { + GetInstance().HeapFree(txtData); + } + + return error; +} + +void Server::Service::ClearResources(void) +{ + mPort = 0; + GetInstance().HeapFree(mTxtData); + mTxtData = nullptr; + mTxtLength = 0; +} + +void Server::Service::DeleteResourcesButRetainName(void) +{ + mIsDeleted = true; + ClearResources(); + otLogInfoSrp("[server] remove service '%s' (but retain its name)", mFullName); +} + +otError Server::Service::CopyResourcesFrom(const Service &aService) +{ + otError error; + + SuccessOrExit(error = SetTxtData(aService.mTxtData, aService.mTxtLength)); + mPriority = aService.mPriority; + mWeight = aService.mWeight; + mPort = aService.mPort; + + mIsDeleted = false; + mTimeLastUpdate = TimerMilli::GetNow(); + +exit: + return error; +} + +bool Server::Service::Matches(const char *aFullName) const +{ + return (mFullName != nullptr) && (strcmp(mFullName, aFullName) == 0); +} + +Server::Host *Server::Host::New(Instance &aInstance) +{ + void *buf; + Host *host = nullptr; + + buf = aInstance.HeapCAlloc(1, sizeof(Host)); + VerifyOrExit(buf != nullptr); + + host = new (buf) Host(aInstance); + +exit: + return host; +} + +void Server::Host::Free(void) +{ + RemoveAndFreeAllServices(); + GetInstance().HeapFree(mFullName); + GetInstance().HeapFree(this); +} + +Server::Host::Host(Instance &aInstance) + : InstanceLocator(aInstance) + , mFullName(nullptr) + , mAddressesNum(0) + , mNext(nullptr) + , mLease(0) + , mKeyLease(0) + , mTimeLastUpdate(TimerMilli::GetNow()) +{ + mKey.Clear(); +} + +otError Server::Host::SetFullName(const char *aFullName) +{ + OT_ASSERT(aFullName != nullptr); + + otError error = OT_ERROR_NONE; + char * nameCopy = static_cast(GetInstance().HeapCAlloc(1, strlen(aFullName) + 1)); + + VerifyOrExit(nameCopy != nullptr, error = OT_ERROR_NO_BUFS); + strcpy(nameCopy, aFullName); + + if (mFullName != nullptr) + { + GetInstance().HeapFree(mFullName); + } + mFullName = nameCopy; + +exit: + return error; +} + +void Server::Host::SetKey(Dns::Ecdsa256KeyRecord &aKey) +{ + OT_ASSERT(aKey.IsValid()); + + mKey = aKey; +} + +void Server::Host::SetLease(uint32_t aLease) +{ + mLease = aLease; +} + +void Server::Host::SetKeyLease(uint32_t aKeyLease) +{ + mKeyLease = aKeyLease; +} + +TimeMilli Server::Host::GetExpireTime(void) const +{ + OT_ASSERT(!IsDeleted()); + + return mTimeLastUpdate + Time::SecToMsec(mLease); +} + +TimeMilli Server::Host::GetKeyExpireTime(void) const +{ + return mTimeLastUpdate + Time::SecToMsec(mKeyLease); +} + +// Add a new service entry to the host, do nothing if there is already +// such services with the same name. +Server::Service *Server::Host::AddService(const char *aFullName) +{ + Service *service = FindService(aFullName); + + VerifyOrExit(service == nullptr); + + service = Service::New(GetInstance(), aFullName); + if (service != nullptr) + { + IgnoreError(mServices.Add(*service)); + service->mHost = this; + } + +exit: + return service; +} + +void Server::Host::RemoveAndFreeService(Service *aService) +{ + if (aService != nullptr) + { + IgnoreError(mServices.Remove(*aService)); + aService->Free(); + } +} + +void Server::Host::RemoveAndFreeAllServices(void) +{ + while (!mServices.IsEmpty()) + { + RemoveAndFreeService(mServices.GetHead()); + } +} + +void Server::Host::ClearResources(void) +{ + mAddressesNum = 0; +} + +void Server::Host::DeleteResourcesButRetainName(void) +{ + // Mark the host as deleted. + mLease = 0; + ClearResources(); + otLogInfoSrp("[server] remove host '%s' (but retain its name)", mFullName); +} + +void Server::Host::CopyResourcesFrom(const Host &aHost) +{ + memcpy(mAddresses, aHost.mAddresses, aHost.mAddressesNum * sizeof(mAddresses[0])); + mAddressesNum = aHost.mAddressesNum; + mKey = aHost.mKey; + mLease = aHost.mLease; + mKeyLease = aHost.mKeyLease; + + mTimeLastUpdate = TimerMilli::GetNow(); +} + +Server::Service *Server::Host::FindService(const char *aFullName) +{ + return mServices.FindMatching(aFullName); +} + +otError Server::Host::AddIp6Address(const Ip6::Address &aIp6Address) +{ + otError error = OT_ERROR_NONE; + + if (aIp6Address.IsMulticast() || aIp6Address.IsUnspecified() || aIp6Address.IsLoopback()) + { + // We don't like those address because they cannot be used + // for communication with exterior devices. + ExitNow(error = OT_ERROR_DROP); + } + + for (const Ip6::Address &addr : mAddresses) + { + if (aIp6Address == addr) + { + // Drop duplicate addresses. + ExitNow(error = OT_ERROR_DROP); + } + } + + if (mAddressesNum >= kMaxAddressesNum) + { + otLogWarnSrp("[server] too many addresses for host %s", GetFullName()); + ExitNow(error = OT_ERROR_NO_BUFS); + } + + mAddresses[mAddressesNum++] = aIp6Address; + +exit: + return error; +} + +bool Server::Host::Matches(const char *aName) const +{ + return mFullName != nullptr && strcmp(mFullName, aName) == 0; +} + +Server::UpdateMetadata *Server::UpdateMetadata::New(Instance & aInstance, + const Dns::UpdateHeader &aHeader, + Host * aHost, + const Ip6::MessageInfo & aMessageInfo) +{ + void * buf; + UpdateMetadata *update = nullptr; + + buf = aInstance.HeapCAlloc(1, sizeof(UpdateMetadata)); + VerifyOrExit(buf != nullptr); + + update = new (buf) UpdateMetadata(aInstance, aHeader, aHost, aMessageInfo); + +exit: + return update; +} + +void Server::UpdateMetadata::Free(void) +{ + GetInstance().HeapFree(this); +} + +Server::UpdateMetadata::UpdateMetadata(Instance & aInstance, + const Dns::UpdateHeader &aHeader, + Host * aHost, + const Ip6::MessageInfo & aMessageInfo) + : InstanceLocator(aInstance) + , mExpireTime(TimerMilli::GetNow() + kDefaultEventsHandlerTimeout) + , mDnsHeader(aHeader) + , mHost(aHost) + , mMessageInfo(aMessageInfo) + , mNext(nullptr) +{ +} + +} // namespace Srp +} // namespace ot + +#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE diff --git a/src/core/net/srp_server.hpp b/src/core/net/srp_server.hpp new file mode 100644 index 000000000..7bc987b21 --- /dev/null +++ b/src/core/net/srp_server.hpp @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2020, 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. + */ + +/** + * @file + * This file includes definitions for SRP server. + */ + +#ifndef NET_SRP_SERVER_HPP_ +#define NET_SRP_SERVER_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + +#if !OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE +#error "OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE is required for OPENTHREAD_CONFIG_SRP_SERVER_ENABLE" +#endif + +#if !OPENTHREAD_CONFIG_ECDSA_ENABLE +#error "OPENTHREAD_CONFIG_ECDSA_ENABLE is required for OPENTHREAD_CONFIG_SRP_SERVER_ENABLE" +#endif + +#include +#include + +#include "common/clearable.hpp" +#include "common/linked_list.hpp" +#include "common/locator.hpp" +#include "common/non_copyable.hpp" +#include "common/notifier.hpp" +#include "common/timer.hpp" +#include "crypto/ecdsa.hpp" +#include "net/dns_headers.hpp" +#include "net/ip6.hpp" +#include "net/ip6_address.hpp" +#include "net/udp6.hpp" + +namespace ot { +namespace Srp { + +/** + * This class implements the SRP server. + * + */ +class Server : public InstanceLocator, private NonCopyable +{ + friend class ot::Notifier; + +public: + class Host; + class Service; + + /** + * This class implements a server-side SRP service. + * + */ + class Service : public InstanceLocator, public LinkedListEntry, private NonCopyable + { + friend class LinkedListEntry; + friend class Server; + + public: + /** + * This method creates a new Service object with given full name. + * + * @param[in] aInstance A reference to the OpenThread instance. + * @param[in] aFullName The full name of the service instance. + * + * @returns A pointer to the newly created Service object. nullptr if + * cannot allocate memory for the object. + * + */ + static Service *New(Instance &aInstance, const char *aFullName); + + /** + * This method frees the Service object. + * + */ + void Free(void); + + /** + * This method tells if the SRP service has been deleted. + * + * A SRP service can be deleted but retains its name for future uses. + * In this case, the service instance is not removed from the SRP server/registry. + * It is guaranteed that all services are deleted if the host is deleted. + * + * @returns TRUE if the service has been deleted, FALSE if not. + * + */ + bool IsDeleted(void) const { return mIsDeleted; } + + /** + * This method returns the full name of the service. + * + * @returns A pointer to the null-terminated service name string. + * + */ + const char *GetFullName(void) const { return mFullName; } + + /** + * This method returns the port of the service instance. + * + * @returns The port of the service. + * + */ + uint16_t GetPort(void) const { return mPort; } + + /** + * This method returns the weight of the service instance. + * + * @returns The weight of the service. + * + */ + uint16_t GetWeight(void) const { return mWeight; } + + /** + * This method returns the priority of the service instance. + * + * @param[in] aService A pointer to the SRP service. + * + * @returns The priority of the service. + * + */ + uint16_t GetPriority(void) const { return mPriority; } + + /** + * This method returns the TXT data of the service instance. + * + * @param[out] aTxtLength A pointer to the output of the TXT data length. + * + * @returns A pointer to the standard TXT data with format described by RFC 6763. + * + */ + const uint8_t *GetTxtData(uint16_t &aTxtLength) const + { + aTxtLength = mTxtLength; + return mTxtData; + } + + /** + * This method returns the host which the service instance reside on. + * + * @returns A reference to the host instance. + * + */ + const Host &GetHost(void) const { return *static_cast(mHost); } + + /** + * This method returns the expire time (in milliseconds) of the service. + * + * @returns The service expire time in milliseconds. + * + */ + TimeMilli GetExpireTime(void) const; + + /** + * This method returns the key expire time (in milliseconds) of the service. + * + * @returns The service key expire time in milliseconds. + * + */ + TimeMilli GetKeyExpireTime(void) const; + + /** + * This method tells whether this service matches a given full name. + * + * @param[in] aFullName The full name. + * + * @returns TRUE if the servce matches the full name, FALSE if doesn't match. + * + */ + bool Matches(const char *aFullName) const; + + private: + explicit Service(Instance &aInstance); + otError SetFullName(const char *aFullName); + otError SetTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength); + otError SetTxtDataFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength); + otError CopyResourcesFrom(const Service &aService); + void ClearResources(void); + void DeleteResourcesButRetainName(void); + + char * mFullName; + uint16_t mPriority; + uint16_t mWeight; + uint16_t mPort; + uint16_t mTxtLength; + uint8_t * mTxtData; + otSrpServerHost *mHost; + Service * mNext; + TimeMilli mTimeLastUpdate; + bool mIsDeleted; + }; + + /** + * This class implements the Host which registers services on the SRP server. + * + */ + class Host : public InstanceLocator, public LinkedListEntry, private NonCopyable + { + friend class LinkedListEntry; + friend class Server; + + public: + /** + * This method creates a new Host object with given full name. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + * @returns A pointer to the newly created Host object. nullptr if + * cannot allocate memory for the object. + * + */ + static Host *New(Instance &aInstance); + + /** + * This method Frees the Host object. + * + */ + void Free(void); + + /** + * This method tells whether the Host object has been deleted. + * + * The Host object retains event if the host has been deleted by the SRP client, + * because the host name may retain. + * + * @returns TRUE if the host is deleted, FALSE if the host is not deleted. + * + */ + bool IsDeleted(void) const { return (mLease == 0); } + + /** + * This method returns the full name of the host. + * + * @returns A pointer to the null-terminated full host name. + * + */ + const char *GetFullName(void) const { return mFullName; } + + /** + * This method returns adrersses of the host. + * + * @param[out] aAddressesNum The number of the addresses. + * + * @returns A pointer to the addresses array. + * + */ + const Ip6::Address *GetAddresses(uint8_t &aAddressesNum) const + { + aAddressesNum = mAddressesNum; + return mAddresses; + } + + /** + * This method returns the LEASE time of the host. + * + * @returns The LEASE time in seconds. + * + */ + uint32_t GetLease(void) const { return mLease; } + + /** + * This method returns the KEY-LEASE time of the key of the host. + * + * @returns The KEY-LEASE time in seconds. + * + */ + uint32_t GetKeyLease(void) const { return mKeyLease; } + + /** + * This method returns the KEY resource of the host. + * + * @returns A pointer to the ECDSA P 256 public key if there is valid one. + * nullptr if no valid key exists. + * + */ + const Dns::Ecdsa256KeyRecord *GetKey(void) const { return mKey.IsValid() ? &mKey : nullptr; } + + /** + * This method returns the expire time (in milliseconds) of the host. + * + * @returns The expire time in milliseconds. + * + */ + TimeMilli GetExpireTime(void) const; + + /** + * This method returns the expire time (in milliseconds) of the key of the host. + * + * @returns The expire time of the key in milliseconds. + * + */ + TimeMilli GetKeyExpireTime(void) const; + + /** + * This method returns the next service of the host. + * + * @param[in] aService A pointer to current service. + * + * @returns A pointer to the next service or NULL if no more services exist. + * + */ + const Service *GetNextService(const Service *aService) const + { + return aService ? aService->GetNext() : mServices.GetHead(); + } + + /** + * This method tells whether the host matches a given full name. + * + * @param[in] aFullName The full name. + * + * @returns A boolean that indicates whether the host matches the given name. + * + */ + bool Matches(const char *aName) const; + + private: + enum : uint8_t + { + kMaxAddressesNum = OPENTHREAD_CONFIG_SRP_SERVER_MAX_ADDRESSES_NUM, + }; + + explicit Host(Instance &aInstance); + otError SetFullName(const char *aFullName); + void SetKey(Dns::Ecdsa256KeyRecord &aKey); + void SetLease(uint32_t aLease); + void SetKeyLease(uint32_t aKeyLease); + Service *GetNextService(Service *aService) { return aService ? aService->GetNext() : mServices.GetHead(); } + Service *AddService(const char *aFullName); + void RemoveAndFreeService(Service *aService); + void RemoveAndFreeAllServices(void); + void ClearResources(void); + void DeleteResourcesButRetainName(void); + void CopyResourcesFrom(const Host &aHost); + Service *FindService(const char *aFullName); + otError AddIp6Address(const Ip6::Address &aIp6Address); + + char * mFullName; + Ip6::Address mAddresses[kMaxAddressesNum]; + uint8_t mAddressesNum; + Host * mNext; + + Dns::Ecdsa256KeyRecord mKey; + uint32_t mLease; // The LEASE time in seconds. + uint32_t mKeyLease; // The KEY-LEASE time in seconds. + TimeMilli mTimeLastUpdate; + LinkedList mServices; + }; + + /** + * This constructor initializes the SRP server object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit Server(Instance &aInstance); + ~Server(void); + + /** + * This method sets the SRP service events handler. + * + * @param[in] aServiceHandler A service events handler. + * @param[in] aServiceHandlerContext A pointer to arbitrary context information. + * + * @note The handler SHOULD call HandleAdvertisingResult to report the result of its processing. + * Otherwise, a SRP update will be considered failed. + * + * @sa HandleAdvertisingResult + * + */ + void SetServiceHandler(otSrpServerServiceUpdateHandler aServiceHandler, void *aServiceHandlerContext); + + /** + * This method returns the domain authorized to the SRP server. + * + * If the domain if not set by SetDomain, "default.service.arpa." will be returned. + * A trailing dot is always appended even if the domain is set without it. + * + * @returns A pointer to the dot-joined domain string. + * + */ + const char *GetDomain(void) const; + + /** + * This method sets the domain on the SRP server. + * + * A trailing dot will be appended to @p aDomain if it is not already there. + * This method should only be called before the SRP server is enabled. + * + * @param[in] aDomain The domain to be set. MUST NOT be nullptr. + * + * @retval OT_ERROR_NONE Successfully set the domain to @p aDomain. + * @retval OT_ERROR_INVALID_STATE The SRP server is already enabled and the Domain cannot be changed. + * @retval OT_ERROR_INVALID_ARGS The argument @p aDomain is not a valid DNS domain name. + * @retval OT_ERROR_NO_BUFS There is no memory to store content of @p aDomain. + * + */ + otError SetDomain(const char *aDomain); + + /** + * This method tells whether the SRP server is currently running. + * + * @returns A boolean that indicates whether the server is running. + * + */ + bool IsRunning(void) const; + + /** + * This method enables/disables the SRP server. + * + * @param[in] aEnabled A boolean to enable/disable the SRP server. + * + */ + void SetEnabled(bool aEnabled); + + /** + * This method sets LEASE & KEY-LEASE range that is acceptable by the SRP server. + * + * When a LEASE time is requested from a client, the granted value will be + * limited in range [aMinLease, aMaxLease]; and a KEY-LEASE will be granted + * in range [aMinKeyLease, aMaxKeyLease]. + * + * @param[in] aMinLease The minimum LEASE interval in seconds. + * @param[in] aMaxLease The maximum LEASE interval in seconds. + * @param[in] aMinKeyLease The minimum KEY-LEASE interval in seconds. + * @param[in] aMaxKeyLease The maximum KEY-LEASE interval in seconds. + * + * @retval OT_ERROR_NONE Successfully set the LEASE and KEY-LEASE ranges. + * @retval OT_ERROR_INVALID_ARGS The LEASE or KEY-LEASE range is not valid. + * + */ + otError SetLeaseRange(uint32_t aMinLease, uint32_t aMaxLease, uint32_t aMinKeyLease, uint32_t aMaxKeyLease); + + /** + * This method returns the next registered SRP host. + * + * @param[in] aHost The current SRP host; use nullptr to get the first SRP host. + * + * @returns A pointer to the next SRP host or nullptr if no more SRP hosts can be found. + * + */ + const Host *GetNextHost(const Host *aHost); + + /** + * This method receives the service advertising result. + * + * @param[in] aHost A pointer to the Host object which contains the SRP service updates. + * @param[in] aError The service advertising result. + * + */ + void HandleAdvertisingResult(const Host *aHost, otError aError); + +private: + enum : uint8_t + { + kThreadServiceTypeSrpServer = OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_TYPE, + }; + + enum : uint16_t + { + kUdpPayloadSize = Ip6::Ip6::kMaxDatagramLength - sizeof(Ip6::Udp::Header), // Max UDP payload size + }; + + enum : uint32_t + { + kDefaultMinLease = 60u * 30, // Default minimum lease time, 30 min (in seconds). + kDefaultMaxLease = 3600u * 2, // Default maximum lease time, 2 hours (in seconds). + kDefaultMinKeyLease = 3600u * 24, // Default minimum key-lease time, 1 day (in seconds). + kDefaultMaxKeyLease = 3600u * 24 * 14, // Default maximum key-lease time, 14 days (in seconds). + kDefaultEventsHandlerTimeout = OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_UPDATE_TIMEOUT, + }; + + /** + * This class includes metadata for processing a SRP update (register, deregister) + * and sending DNS response to the client. + * + */ + class UpdateMetadata : public InstanceLocator, public LinkedListEntry + { + friend class LinkedListEntry; + + public: + static UpdateMetadata * New(Instance & aInstance, + const Dns::UpdateHeader &aHeader, + Host * aHost, + const Ip6::MessageInfo & aMessageInfo); + void Free(void); + TimeMilli GetExpireTime(void) const { return mExpireTime; } + const Dns::UpdateHeader &GetDnsHeader(void) const { return mDnsHeader; } + Host & GetHost(void) { return *mHost; } + const Ip6::MessageInfo & GetMessageInfo(void) const { return mMessageInfo; } + bool Matches(const Host *aHost) const { return mHost == aHost; } + + private: + UpdateMetadata(Instance & aInstance, + const Dns::UpdateHeader &aHeader, + Host * aHost, + const Ip6::MessageInfo & aMessageInfo); + + TimeMilli mExpireTime; + Dns::UpdateHeader mDnsHeader; + Host * mHost; // The host will be updated. The UpdateMetadata has no ownership of this host. + Ip6::MessageInfo mMessageInfo; // The message info of the DNS update request. + UpdateMetadata * mNext; + }; + + void Start(void); + void Stop(void); + void HandleNotifierEvents(Events aEvents); + otError PublishServerData(void); + void UnpublishServerData(void); + uint32_t GrantLease(uint32_t aLease) const; + uint32_t GrantKeyLease(uint32_t aKeyLease) const; + + void HandleSrpUpdateResult(otError aError, + 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); + otError ProcessUpdateSection(Host & aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + const Dns::Zone & aZone, + uint16_t aHeaderOffset, + uint16_t & aOffset); + otError ProcessAdditionalSection(Host * aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + uint16_t aHeaderOffset, + uint16_t & aOffset); + otError VerifySignature(const Dns::Ecdsa256KeyRecord &aKey, + const Message & aMessage, + Dns::UpdateHeader aDnsHeader, + uint16_t aSigOffset, + uint16_t aSigRdataOffset, + uint16_t aSigRdataLength, + const char * aSignerName); + + static otError ProcessZoneSection(const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + uint16_t & aOffset, + Dns::Zone & aZone); + static otError ProcessHostDescriptionInstruction(Host & aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + const Dns::Zone & aZone, + uint16_t aHeaderOffset, + uint16_t aOffset); + static otError ProcessServiceDiscoveryInstructions(Host & aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + const Dns::Zone & aZone, + uint16_t aHeaderOffset, + uint16_t aOffset); + static otError ProcessServiceDescriptionInstructions(Host & aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + const Dns::Zone & aZone, + uint16_t aHeaderOffset, + uint16_t & aOffset); + static bool IsValidDeleteAllRecord(const Dns::ResourceRecord &aRecord); + + void HandleUpdate(const Dns::UpdateHeader &aDnsHeader, Host *aHost, const Ip6::MessageInfo &aMessageInfo); + void AddHost(Host *aHost); + void RemoveAndFreeHost(Host *aHost); + Service * FindService(const char *aFullName); + bool HasNameConflictsWith(Host &aHost); + void SendResponse(const Dns::UpdateHeader & aHeader, + Dns::UpdateHeader::Response aResponseCode, + const Ip6::MessageInfo & aMessageInfo); + void SendResponse(const Dns::UpdateHeader &aHeader, + uint32_t aLease, + uint32_t aKeyLease, + const Ip6::MessageInfo & aMessageInfo); + static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); + void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + static void HandleLeaseTimer(Timer &aTimer); + void HandleLeaseTimer(void); + static void HandleOutstandingUpdatesTimer(Timer &aTimer); + void HandleOutstandingUpdatesTimer(void); + + void HandleAdvertisingResult(UpdateMetadata *aUpdate, otError aError); + const UpdateMetadata *FindOutstandingUpdate(const Ip6::MessageInfo &aMessageInfo, uint16_t aDnsMessageId); + + Ip6::Udp::Socket mSocket; + otSrpServerServiceUpdateHandler mAdvertisingHandler; + void * mAdvertisingHandlerContext; + + char *mDomain; + + uint32_t mMinLease; // The minimum lease time in seconds. + uint32_t mMaxLease; // The maximum lease time in seconds. + uint32_t mMinKeyLease; // The minimum key-lease time in seconds. + uint32_t mMaxKeyLease; // The maximum key-lease time in seconds. + + LinkedList mHosts; + TimerMilli mLeaseTimer; + + TimerMilli mOutstandingUpdatesTimer; + LinkedList mOutstandingUpdates; + + bool mEnabled; +}; + +} // namespace Srp +} // namespace ot + +#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +#endif // NET_SRP_SERVER_HPP_ diff --git a/src/core/openthread-core-config.h b/src/core/openthread-core-config.h index b4b2585bb..9fedd76b6 100644 --- a/src/core/openthread-core-config.h +++ b/src/core/openthread-core-config.h @@ -77,6 +77,7 @@ #include "config/radio_link.h" #include "config/sntp_client.h" #include "config/srp_client.h" +#include "config/srp_server.h" #include "config/time_sync.h" #include "config/tmf.h" diff --git a/src/core/thread/thread_netif.cpp b/src/core/thread/thread_netif.cpp index 341a50658..e8248c2b9 100644 --- a/src/core/thread/thread_netif.cpp +++ b/src/core/thread/thread_netif.cpp @@ -124,6 +124,10 @@ ThreadNetif::ThreadNetif(Instance &aInstance) #if OPENTHREAD_CONFIG_DUA_ENABLE || (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE) , mDuaManager(aInstance) #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + , mSrpServer(aInstance) +#endif + , mChildSupervisor(aInstance) , mSupervisionListener(aInstance) , mAnnounceBegin(aInstance) diff --git a/src/core/thread/thread_netif.hpp b/src/core/thread/thread_netif.hpp index 71810b33f..a80a71c56 100644 --- a/src/core/thread/thread_netif.hpp +++ b/src/core/thread/thread_netif.hpp @@ -64,6 +64,10 @@ #include "thread/dua_manager.hpp" #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +#include "net/srp_server.hpp" +#endif + #include "meshcop/dataset_manager.hpp" #if OPENTHREAD_CONFIG_JOINER_ENABLE @@ -266,6 +270,10 @@ private: #if OPENTHREAD_CONFIG_DUA_ENABLE || (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE) DuaManager mDuaManager; #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + Srp::Server mSrpServer; +#endif + Utils::ChildSupervisor mChildSupervisor; Utils::SupervisionListener mSupervisionListener; AnnounceBeginServer mAnnounceBegin;