mirror of
https://github.com/espressif/openthread.git
synced 2026-06-05 21:14:49 +00:00
[dns-sd] DNS-SD server implementation (#6103)
This commit implements the DNS-SD server:
- Handle Standard DNS Query from clients
- Supported resource records: PTR, SRV, TXT, AAAA
- Query services from SRP server
- Add tests
- simulation test using DNS client (only test AAAA query)
- OTBR test using dig command to verify all resource records can be
queried successfully
This commit is contained in:
@@ -257,6 +257,7 @@ LOCAL_SRC_FILES := \
|
||||
src/core/net/dhcp6_server.cpp \
|
||||
src/core/net/dns_client.cpp \
|
||||
src/core/net/dns_headers.cpp \
|
||||
src/core/net/dnssd_server.cpp \
|
||||
src/core/net/icmp6.cpp \
|
||||
src/core/net/ip6.cpp \
|
||||
src/core/net/ip6_address.cpp \
|
||||
|
||||
@@ -167,6 +167,11 @@ if(OT_DNS_CLIENT)
|
||||
target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE=1")
|
||||
endif()
|
||||
|
||||
option(OT_DNSSD_SERVER "enable DNS-SD server support")
|
||||
if(OT_DNSSD_SERVER)
|
||||
target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE=1")
|
||||
endif()
|
||||
|
||||
option(OT_ECDSA "enable ECDSA support")
|
||||
if(OT_ECDSA)
|
||||
target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_ECDSA_ENABLE=1")
|
||||
|
||||
@@ -129,6 +129,9 @@ if (openthread_enable_core_config_args) {
|
||||
# Enable DNS client support
|
||||
openthread_config_dns_client_enable = false
|
||||
|
||||
# Enable DNS-SD server support
|
||||
openthread_config_dnssd_server_enable = false
|
||||
|
||||
# Enable ECDSA support
|
||||
openthread_config_ecdsa_enable = false
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ DHCP6_CLIENT ?= 1
|
||||
DHCP6_SERVER ?= 1
|
||||
DIAGNOSTIC ?= 1
|
||||
DNS_CLIENT ?= 1
|
||||
DNSSD_SERVER ?= 1
|
||||
ECDSA ?= 1
|
||||
IP6_FRAGM ?= 1
|
||||
JAM_DETECTION ?= 1
|
||||
|
||||
@@ -32,6 +32,7 @@ This page lists the available common switches with description. Unless stated ot
|
||||
| DEBUG_UART | not implemented | Enables the Debug UART platform feature. |
|
||||
| DEBUG_UART_LOG | not implemented | Enables the log output for the debug UART. Requires OPENTHREAD_CONFIG_ENABLE_DEBUG_UART to be enabled. |
|
||||
| DNS_CLIENT | OT_DNS_CLIENT | Enables support for DNS client. Enable this switch on a device that sends a DNS query for AAAA (IPv6) record. |
|
||||
| DNSSD_SERVER | OT_DNSSD_SERVER | Enables support for DNS-SD server. DNS-SD server use service information from local SRP server to resolve DNS-SD query questions. |
|
||||
| DUA | OT_DUA | Enables the Domain Unicast Address feature for Thread 1.2. |
|
||||
| DYNAMIC_LOG_LEVEL | not implemented | Enables the dynamic log level feature. Enable this switch if OpenThread log level is required to be set at runtime. See [Logging guide](https://openthread.io/guides/build/logs) to learn more. |
|
||||
| ECDSA | OT_ECDSA | Enables support for Elliptic Curve Digital Signature Algorithm. Enable this switch if ECDSA digital signature is used by application. |
|
||||
|
||||
@@ -51,6 +51,7 @@ DIAGNOSTIC ?= 0
|
||||
DISABLE_DOC ?= 0
|
||||
DISABLE_TOOLS ?= 0
|
||||
DNS_CLIENT ?= 0
|
||||
DNSSD_SERVER ?= 0
|
||||
DUA ?= 0
|
||||
DYNAMIC_LOG_LEVEL ?= 0
|
||||
ECDSA ?= 0
|
||||
@@ -185,6 +186,10 @@ ifeq ($(DNS_CLIENT),1)
|
||||
COMMONCFLAGS += -DOPENTHREAD_CONFIG_DNS_CLIENT_ENABLE=1
|
||||
endif
|
||||
|
||||
ifeq ($(DNSSD_SERVER),1)
|
||||
COMMONCFLAGS += -DOPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE=1
|
||||
endif
|
||||
|
||||
ifeq ($(DUA),1)
|
||||
COMMONCFLAGS += -DOPENTHREAD_CONFIG_DUA_ENABLE=1
|
||||
endif
|
||||
|
||||
@@ -115,6 +115,7 @@ size_nrf52840_version()
|
||||
"DIAGNOSTIC=1"
|
||||
"DISABLE_DOC=1"
|
||||
"DNS_CLIENT=1"
|
||||
"DNSSD_SERVER=1"
|
||||
"ECDSA=1"
|
||||
"FULL_LOGS=1"
|
||||
"JAM_DETECTION=1"
|
||||
|
||||
+5
-2
@@ -62,7 +62,10 @@ build_simulation()
|
||||
"-DOT_SRP_SERVER=ON"
|
||||
"-DOT_SRP_CLIENT=ON"
|
||||
"-DOT_SERVICE=ON"
|
||||
"-DOT_ECDSA=ON")
|
||||
"-DOT_ECDSA=ON"
|
||||
"-DOT_DNSSD_SERVER=ON"
|
||||
"-DOT_DNS_CLIENT=ON"
|
||||
)
|
||||
|
||||
if [[ ${FULL_LOGS} == 1 ]]; then
|
||||
options+=("-DOT_FULL_LOGS=ON")
|
||||
@@ -255,7 +258,7 @@ do_build_otbr_docker()
|
||||
echo "Building OTBR Docker ..."
|
||||
local otdir
|
||||
local otbrdir
|
||||
local otbr_options="-DOT_SLAAC=ON -DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON -DOTBR_REST=OFF -DOTBR_WEB=OFF -DOTBR_MDNS=mDNSResponder -DOTBR_SRP_ADVERTISING_PROXY=ON"
|
||||
local otbr_options="-DOT_DNSSD_SERVER=ON -DOT_DNS_CLIENT=ON -DOT_SRP_CLIENT=ON -DOT_SLAAC=ON -DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON -DOTBR_REST=OFF -DOTBR_WEB=OFF -DOTBR_MDNS=mDNSResponder -DOTBR_SRP_ADVERTISING_PROXY=ON"
|
||||
local otbr_docker_image=${OTBR_DOCKER_IMAGE:-otbr-ot12-backbone-ci}
|
||||
|
||||
# Always enable SRP server for OTBR.
|
||||
|
||||
@@ -132,6 +132,10 @@ if (openthread_enable_core_config_args) {
|
||||
defines += [ "OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE=1" ]
|
||||
}
|
||||
|
||||
if (openthread_config_dnssd_server_enable) {
|
||||
defines += [ "OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE=1" ]
|
||||
}
|
||||
|
||||
if (openthread_config_ecdsa_enable) {
|
||||
defines += [ "OPENTHREAD_CONFIG_ECDSA_ENABLE=1" ]
|
||||
}
|
||||
@@ -470,6 +474,8 @@ openthread_core_files = [
|
||||
"net/dns_client.hpp",
|
||||
"net/dns_headers.cpp",
|
||||
"net/dns_headers.hpp",
|
||||
"net/dnssd_server.cpp",
|
||||
"net/dnssd_server.hpp",
|
||||
"net/icmp6.cpp",
|
||||
"net/icmp6.hpp",
|
||||
"net/ip6.cpp",
|
||||
@@ -661,6 +667,7 @@ source_set("libopenthread_core_config") {
|
||||
"config/dhcp6_server.h",
|
||||
"config/diag.h",
|
||||
"config/dns_client.h",
|
||||
"config/dnssd_server.h",
|
||||
"config/ip6.h",
|
||||
"config/joiner.h",
|
||||
"config/link_quality.h",
|
||||
|
||||
@@ -138,6 +138,7 @@ set(COMMON_SOURCES
|
||||
net/dhcp6_server.cpp
|
||||
net/dns_client.cpp
|
||||
net/dns_headers.cpp
|
||||
net/dnssd_server.cpp
|
||||
net/icmp6.cpp
|
||||
net/ip6.cpp
|
||||
net/ip6_address.cpp
|
||||
|
||||
@@ -215,6 +215,7 @@ SOURCES_COMMON = \
|
||||
net/dhcp6_server.cpp \
|
||||
net/dns_client.cpp \
|
||||
net/dns_headers.cpp \
|
||||
net/dnssd_server.cpp \
|
||||
net/icmp6.cpp \
|
||||
net/ip6.cpp \
|
||||
net/ip6_address.cpp \
|
||||
@@ -405,6 +406,7 @@ HEADERS_COMMON = \
|
||||
config/dhcp6_server.h \
|
||||
config/diag.h \
|
||||
config/dns_client.h \
|
||||
config/dnssd_server.h \
|
||||
config/ip6.h \
|
||||
config/joiner.h \
|
||||
config/link_quality.h \
|
||||
@@ -462,6 +464,7 @@ HEADERS_COMMON = \
|
||||
net/dhcp6_server.hpp \
|
||||
net/dns_client.hpp \
|
||||
net/dns_headers.hpp \
|
||||
net/dnssd_server.hpp \
|
||||
net/icmp6.hpp \
|
||||
net/ip6.hpp \
|
||||
net/ip6_address.hpp \
|
||||
|
||||
@@ -683,6 +683,13 @@ template <> inline Srp::Client &Instance::Get(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
|
||||
template <> inline Dns::ServiceDiscovery::Server &Instance::Get(void)
|
||||
{
|
||||
return mThreadNetif.mDnssdServer;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_FTD || OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE
|
||||
template <> inline NetworkDiagnostic::NetworkDiagnostic &Instance::Get(void)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2021, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file includes compile-time configurations for the DNS-SD Server.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_DNSSD_SERVER_H_
|
||||
#define CONFIG_DNSSD_SERVER_H_
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
|
||||
*
|
||||
* Define to 1 to enable DNS-SD Server support.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
|
||||
#define OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_DNSSD_SERVER_PORT
|
||||
*
|
||||
* Define the the DNS-SD Server port.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_DNSSD_SERVER_PORT
|
||||
#define OPENTHREAD_CONFIG_DNSSD_SERVER_PORT 53
|
||||
#endif
|
||||
|
||||
#endif // CONFIG_DNSSD_SERVER_H_
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
#include "common/code_utils.hpp"
|
||||
#include "common/debug.hpp"
|
||||
#include "common/logging.hpp"
|
||||
#include "common/random.hpp"
|
||||
#include "common/string.hpp"
|
||||
|
||||
@@ -104,20 +105,25 @@ otError Name::AppendLabel(const char *aLabel, Message &aMessage)
|
||||
return AppendLabel(aLabel, static_cast<uint8_t>(StringLength(aLabel, kMaxLabelLength + 1)), aMessage);
|
||||
}
|
||||
|
||||
otError Name::AppendLabel(const char *aLabel, uint8_t aLabelLength, Message &aMessage)
|
||||
otError Name::AppendLabel(const char *aLabel, uint8_t aLength, Message &aMessage)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
VerifyOrExit((0 < aLabelLength) && (aLabelLength <= kMaxLabelLength), error = OT_ERROR_INVALID_ARGS);
|
||||
VerifyOrExit((0 < aLength) && (aLength <= kMaxLabelLength), error = OT_ERROR_INVALID_ARGS);
|
||||
|
||||
SuccessOrExit(error = aMessage.Append(aLabelLength));
|
||||
error = aMessage.AppendBytes(aLabel, aLabelLength);
|
||||
SuccessOrExit(error = aMessage.Append(aLength));
|
||||
error = aMessage.AppendBytes(aLabel, aLength);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError Name::AppendMultipleLabels(const char *aLabels, Message &aMessage)
|
||||
{
|
||||
return AppendMultipleLabels(aLabels, kMaxLength, aMessage);
|
||||
}
|
||||
|
||||
otError Name::AppendMultipleLabels(const char *aLabels, uint8_t aLength, Message &aMessage)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
uint16_t index = 0;
|
||||
@@ -128,7 +134,7 @@ otError Name::AppendMultipleLabels(const char *aLabels, Message &aMessage)
|
||||
|
||||
do
|
||||
{
|
||||
ch = aLabels[index];
|
||||
ch = index < aLength ? aLabels[index] : static_cast<char>(kNullChar);
|
||||
|
||||
if ((ch == kNullChar) || (ch == kLabelSeperatorChar))
|
||||
{
|
||||
|
||||
@@ -495,6 +495,11 @@ public:
|
||||
kMaxEncodedLength = 255, ///< Max length of an encoded name.
|
||||
};
|
||||
|
||||
enum : char
|
||||
{
|
||||
kLabelSeperatorChar = '.',
|
||||
};
|
||||
|
||||
/**
|
||||
* This enumeration represents the name type.
|
||||
*
|
||||
@@ -647,6 +652,26 @@ public:
|
||||
*/
|
||||
static otError AppendLabel(const char *aLabel, Message &aMessage);
|
||||
|
||||
/**
|
||||
* This static method encodes and appends a single name label of specified length to a message.
|
||||
*
|
||||
* The @p aLabel is assumed to contain a single name label of given @p aLength. @p aLabel must not contain
|
||||
* '\0' characters within the length @p aLength. Unlike `AppendMultipleLabels()` which parses the label string
|
||||
* and treats it as sequence of multiple (dot-separated) labels, this method always appends @p aLabel as a single
|
||||
* whole label. This allows the label string to even contain dot '.' character, which, for example, is useful for
|
||||
* "Service Instance Names" where <Instance> portion is a user-friendly name and can contain dot characters.
|
||||
*
|
||||
* @param[in] aLabel The label string to append. MUST NOT be nullptr.
|
||||
* @param[in] aLength The length of the label to append.
|
||||
* @param[in] aMessage The message to append to.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully encoded and appended the name label to @p aMessage.
|
||||
* @retval OT_ERROR_INVALID_ARGS @p aLabel is not valid (e.g., label length is not within valid range).
|
||||
* @retval OT_ERROR_NO_BUFS Insufficient available buffers to grow the message.
|
||||
*
|
||||
*/
|
||||
static otError AppendLabel(const char *aLabel, uint8_t aLength, Message &aMessage);
|
||||
|
||||
/**
|
||||
* This static method encodes and appends a sequence of name labels to a given message.
|
||||
*
|
||||
@@ -669,6 +694,33 @@ public:
|
||||
*/
|
||||
static otError AppendMultipleLabels(const char *aLabels, Message &aMessage);
|
||||
|
||||
/**
|
||||
* This static method encodes and appends a sequence of name labels within the specified length to a given message.
|
||||
* This method stops appending labels if @p aLength characters are read or '\0' is found before @p aLength
|
||||
* characters.
|
||||
*
|
||||
* This method is useful for appending a number of labels of the name instead of appending all labels.
|
||||
*
|
||||
* The @p aLabels must follow "<label1>.<label2>.<label3>", i.e., a sequence of labels separated by dot '.' char.
|
||||
* E.g., "_http._tcp", "_http._tcp." (same as previous one), "host-1.test".
|
||||
*
|
||||
* This method validates that the @p aLabels is a valid name format, i.e., no empty label, and labels are
|
||||
* `kMaxLabelLength` (63) characters or less.
|
||||
*
|
||||
* @note This method NEVER adds a label terminator (empty label) to the message, even in the case where @p aLabels
|
||||
* ends with a dot character, e.g., "host-1.test." is treated same as "host-1.test".
|
||||
*
|
||||
* @param[in] aLabels A name label string. Can be nullptr (then treated as "").
|
||||
* @param[in] aLength The max length of the name labels to encode.
|
||||
* @param[in] aMessage The message to which to append the encoded name.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully encoded and appended the name label(s) to @p aMessage.
|
||||
* @retval OT_ERROR_INVALID_ARGS Name label @p aLabels is not valid.
|
||||
* @retval OT_ERROR_NO_BUFS Insufficient available buffers to grow the message.
|
||||
*
|
||||
*/
|
||||
static otError AppendMultipleLabels(const char *aLabels, uint8_t aLength, Message &aMessage);
|
||||
|
||||
/**
|
||||
* This static method appends a name label terminator to a message.
|
||||
*
|
||||
@@ -906,8 +958,7 @@ public:
|
||||
private:
|
||||
enum : char
|
||||
{
|
||||
kNullChar = '\0',
|
||||
kLabelSeperatorChar = '.',
|
||||
kNullChar = '\0',
|
||||
};
|
||||
|
||||
enum : uint8_t
|
||||
@@ -956,8 +1007,6 @@ private:
|
||||
uint16_t mNameEndOffset; // Offset in `mMessage` to the byte after the end of domain name field.
|
||||
};
|
||||
|
||||
static otError AppendLabel(const char *aLabel, uint8_t aLabelLength, Message &aMessage);
|
||||
|
||||
Name(const char *aString, const Message *aMessage, uint16_t aOffset)
|
||||
: mString(aString)
|
||||
, mMessage(aMessage)
|
||||
@@ -2374,6 +2423,12 @@ OT_TOOL_PACKED_BEGIN
|
||||
class Question
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Default constructor for Question.
|
||||
*
|
||||
*/
|
||||
Question() = default;
|
||||
|
||||
/**
|
||||
* Constructor for Question.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,651 @@
|
||||
/*
|
||||
* Copyright (c) 2021, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file implements the DNS-SD server.
|
||||
*/
|
||||
|
||||
#include "dnssd_server.hpp"
|
||||
|
||||
#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
|
||||
|
||||
#include "common/code_utils.hpp"
|
||||
#include "common/debug.hpp"
|
||||
#include "common/instance.hpp"
|
||||
#include "common/locator-getters.hpp"
|
||||
#include "common/logging.hpp"
|
||||
#include "net/srp_server.hpp"
|
||||
#include "net/udp6.hpp"
|
||||
|
||||
using ot::Encoding::BigEndian::HostSwap16;
|
||||
|
||||
namespace ot {
|
||||
namespace Dns {
|
||||
namespace ServiceDiscovery {
|
||||
|
||||
const char Server::kDnssdProtocolUdp[4] = {'_', 'u', 'd', 'p'};
|
||||
const char Server::kDnssdProtocolTcp[4] = {'_', 't', 'c', 'p'};
|
||||
const char Server::kDefaultDomainName[] = "default.service.arpa.";
|
||||
|
||||
Server::Server(Instance &aInstance)
|
||||
: InstanceLocator(aInstance)
|
||||
, mSocket(aInstance)
|
||||
{
|
||||
}
|
||||
|
||||
otError Server::Start(void)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
VerifyOrExit(!IsRunning());
|
||||
|
||||
SuccessOrExit(error = mSocket.Open(&Server::HandleUdpReceive, this));
|
||||
SuccessOrExit(error = mSocket.Bind(kPort));
|
||||
|
||||
exit:
|
||||
otLogInfoDns("[server] started: %s", otThreadErrorToString(error));
|
||||
return error;
|
||||
}
|
||||
|
||||
void Server::Stop(void)
|
||||
{
|
||||
IgnoreError(mSocket.Close());
|
||||
otLogInfoDns("[server] stopped");
|
||||
}
|
||||
|
||||
void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
|
||||
{
|
||||
static_cast<Server *>(aContext)->HandleUdpReceive(*static_cast<Message *>(aMessage),
|
||||
*static_cast<const Ip6::MessageInfo *>(aMessageInfo));
|
||||
}
|
||||
|
||||
void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
Header requestHeader;
|
||||
Message *responseMessage = nullptr;
|
||||
|
||||
SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), requestHeader));
|
||||
VerifyOrExit(requestHeader.GetType() == Header::kTypeQuery, error = OT_ERROR_DROP);
|
||||
|
||||
responseMessage = mSocket.NewMessage(0);
|
||||
VerifyOrExit(responseMessage != nullptr, error = OT_ERROR_NO_BUFS);
|
||||
|
||||
// Allocate space for DNS header
|
||||
SuccessOrExit(error = responseMessage->SetLength(sizeof(Header)));
|
||||
|
||||
// ProcessQuery is assumed to always prepare the response DNS message and header properly even in the case of system
|
||||
// failures (e.g. no more buffers).
|
||||
ProcessQuery(aMessage, *responseMessage, requestHeader);
|
||||
|
||||
error = mSocket.SendTo(*responseMessage, aMessageInfo);
|
||||
|
||||
exit:
|
||||
FreeMessageOnError(responseMessage, error);
|
||||
}
|
||||
|
||||
void Server::ProcessQuery(Message &aMessage, Message &aResponse, const Header &aRequestHeader)
|
||||
{
|
||||
Header responseHeader;
|
||||
uint16_t readOffset, nameSerializeOffset;
|
||||
Question question;
|
||||
uint16_t qtype;
|
||||
char name[Dns::Name::kMaxLength + 1];
|
||||
otError error = OT_ERROR_NONE;
|
||||
NameCompressInfo compressInfo;
|
||||
|
||||
// Setup initial DNS response header
|
||||
responseHeader.Clear();
|
||||
responseHeader.SetResponseCode(Header::kResponseSuccess);
|
||||
responseHeader.SetType(Header::kTypeResponse);
|
||||
responseHeader.SetMessageId(aRequestHeader.GetMessageId());
|
||||
|
||||
// Validate the query
|
||||
VerifyOrExit(aRequestHeader.GetQueryType() == Header::kQueryTypeStandard,
|
||||
responseHeader.SetResponseCode(Header::kResponseNotImplemented));
|
||||
VerifyOrExit(!aRequestHeader.IsTruncationFlagSet(), responseHeader.SetResponseCode(Header::kResponseFormatError));
|
||||
VerifyOrExit(aRequestHeader.GetQuestionCount() == 1,
|
||||
responseHeader.SetResponseCode(Header::kResponseNotImplemented));
|
||||
|
||||
// Read the query name and question
|
||||
readOffset = sizeof(Header);
|
||||
VerifyOrExit(OT_ERROR_NONE == Dns::Name::ReadName(aMessage, readOffset, name, sizeof(name)),
|
||||
responseHeader.SetResponseCode(Header::kResponseFormatError));
|
||||
VerifyOrExit(OT_ERROR_NONE == aMessage.Read(readOffset, question),
|
||||
responseHeader.SetResponseCode(Header::kResponseFormatError));
|
||||
|
||||
// Add the question to the response, and save the serialize offset of name
|
||||
nameSerializeOffset = aResponse.GetLength();
|
||||
SuccessOrExit(error = AddQuestionToResponse(name, question, aResponse, responseHeader));
|
||||
|
||||
// Further validate the query
|
||||
qtype = question.GetType();
|
||||
VerifyOrExit(qtype == ResourceRecord::kTypePtr || qtype == ResourceRecord::kTypeSrv ||
|
||||
qtype == ResourceRecord::kTypeTxt || qtype == ResourceRecord::kTypeAaaa,
|
||||
responseHeader.SetResponseCode(Header::kResponseNotImplemented));
|
||||
|
||||
VerifyOrExit(question.GetClass() == ResourceRecord::kClassInternet ||
|
||||
question.GetClass() == ResourceRecord::kClassAny,
|
||||
responseHeader.SetResponseCode(Header::kResponseNotImplemented));
|
||||
|
||||
// Prepare the information for name compression
|
||||
VerifyOrExit(OT_ERROR_NONE == PrepareCompressInfo(qtype, name, nameSerializeOffset, compressInfo),
|
||||
responseHeader.SetResponseCode(Header::kResponseNameError));
|
||||
|
||||
// Resolve the question
|
||||
SuccessOrExit(error = ResolveQuestion(name, question, responseHeader, aResponse, compressInfo));
|
||||
|
||||
otLogInfoDns("[server] TRANSACTION=0x%04x, QUESTION=[%s %d %d], RCODE=%d, ANSWER=%d, ADDITIONAL=%d",
|
||||
aRequestHeader.GetMessageId(), name, question.GetClass(), question.GetType(),
|
||||
responseHeader.GetResponseCode(), responseHeader.GetQuestionCount(),
|
||||
responseHeader.GetAdditionalRecordCount());
|
||||
exit:
|
||||
if (error != OT_ERROR_NONE)
|
||||
{
|
||||
otLogWarnDns("[server] failed to handle DNS query: %s", otThreadErrorToString(error));
|
||||
|
||||
responseHeader.SetQuestionCount(0);
|
||||
responseHeader.SetAnswerCount(0);
|
||||
responseHeader.SetAdditionalRecordCount(0);
|
||||
responseHeader.SetResponseCode(Header::kResponseServerFailure);
|
||||
IgnoreError(aResponse.SetLength(sizeof(Header)));
|
||||
}
|
||||
|
||||
aResponse.Write(0, responseHeader);
|
||||
}
|
||||
|
||||
otError Server::AddQuestionToResponse(const char * aName,
|
||||
const Question &aQuestion,
|
||||
Message & aResponse,
|
||||
Header & aResponseHeader)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
SuccessOrExit(error = Dns::Name::AppendName(aName, aResponse));
|
||||
SuccessOrExit(error = aResponse.Append(aQuestion));
|
||||
|
||||
aResponseHeader.SetQuestionCount(1);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError Server::PrepareCompressInfo(uint16_t aQueryType,
|
||||
const char * aName,
|
||||
uint16_t aNameSerializeOffset,
|
||||
Server::NameCompressInfo &aCompressInfo)
|
||||
{
|
||||
const char * domain = kDefaultDomainName;
|
||||
NameComponentsOffsetInfo nameComponentsInfo;
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
|
||||
domain = Get<Srp::Server>().GetDomain();
|
||||
#endif
|
||||
|
||||
SuccessOrExit(error = FindNameComponents(aName, domain, nameComponentsInfo));
|
||||
|
||||
switch (aQueryType)
|
||||
{
|
||||
case ResourceRecord::kTypePtr:
|
||||
VerifyOrExit(nameComponentsInfo.IsServiceName(), error = OT_ERROR_INVALID_ARGS);
|
||||
aCompressInfo.SetServiceNameOffset(aNameSerializeOffset, aName);
|
||||
break;
|
||||
|
||||
case ResourceRecord::kTypeSrv:
|
||||
case ResourceRecord::kTypeTxt:
|
||||
VerifyOrExit(nameComponentsInfo.IsServiceInstanceName(), error = OT_ERROR_INVALID_ARGS);
|
||||
aCompressInfo.SetInstanceNameOffset(aNameSerializeOffset, aName);
|
||||
aCompressInfo.SetServiceNameOffset(aNameSerializeOffset + nameComponentsInfo.mServiceOffset,
|
||||
aName + nameComponentsInfo.mServiceOffset);
|
||||
break;
|
||||
|
||||
case ResourceRecord::kTypeAaaa:
|
||||
VerifyOrExit(nameComponentsInfo.IsHostName(), error = OT_ERROR_INVALID_ARGS);
|
||||
aCompressInfo.SetHostNameOffset(aNameSerializeOffset, aName);
|
||||
break;
|
||||
|
||||
default:
|
||||
OT_ASSERT(false);
|
||||
}
|
||||
|
||||
OT_ASSERT(nameComponentsInfo.mDomainOffset != NameComponentsOffsetInfo::kNotPresent);
|
||||
aCompressInfo.SetDomainNameOffset(aNameSerializeOffset + nameComponentsInfo.mDomainOffset,
|
||||
aName + nameComponentsInfo.mDomainOffset);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError Server::ResolveQuestion(const char * aName,
|
||||
const Question & aQuestion,
|
||||
Header & aResponseHeader,
|
||||
Message & aResponseMessage,
|
||||
NameCompressInfo &aCompressInfo)
|
||||
{
|
||||
OT_UNUSED_VARIABLE(aName);
|
||||
OT_UNUSED_VARIABLE(aQuestion);
|
||||
OT_UNUSED_VARIABLE(aResponseHeader);
|
||||
OT_UNUSED_VARIABLE(aResponseMessage);
|
||||
OT_UNUSED_VARIABLE(aCompressInfo);
|
||||
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
|
||||
SuccessOrExit(error = ResolveQuestionBySrp(aName, aQuestion, aResponseHeader, aResponseMessage,
|
||||
/* aAdditional */ false, aCompressInfo));
|
||||
|
||||
if (aResponseHeader.GetAnswerCount() > 0)
|
||||
{
|
||||
SuccessOrExit(error = ResolveQuestionBySrp(aName, aQuestion, aResponseHeader, aResponseMessage,
|
||||
/* aAdditional */ true, aCompressInfo));
|
||||
}
|
||||
else
|
||||
{
|
||||
aResponseHeader.SetResponseCode(Header::kResponseNameError);
|
||||
}
|
||||
|
||||
exit:
|
||||
#endif
|
||||
return error;
|
||||
}
|
||||
|
||||
otError Server::AppendPtrRecord(Message & aMessage,
|
||||
const char * aServiceName,
|
||||
const char * aInstanceName,
|
||||
uint32_t aTtl,
|
||||
NameCompressInfo &aCompressInfo)
|
||||
{
|
||||
otError error;
|
||||
PtrRecord ptrRecord;
|
||||
uint16_t recordOffset;
|
||||
|
||||
ptrRecord.Init();
|
||||
ptrRecord.SetTtl(aTtl);
|
||||
|
||||
SuccessOrExit(error = AppendServiceName(aMessage, aServiceName, aCompressInfo));
|
||||
|
||||
recordOffset = aMessage.GetLength();
|
||||
SuccessOrExit(error = aMessage.SetLength(recordOffset + sizeof(ptrRecord)));
|
||||
|
||||
SuccessOrExit(error = AppendInstanceName(aMessage, aInstanceName, aCompressInfo));
|
||||
|
||||
ptrRecord.SetLength(aMessage.GetLength() - (recordOffset + sizeof(ResourceRecord)));
|
||||
aMessage.Write(recordOffset, ptrRecord);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError Server::AppendSrvRecord(Message & aMessage,
|
||||
const char * aInstanceName,
|
||||
const char * aHostName,
|
||||
uint32_t aTtl,
|
||||
uint16_t aPriority,
|
||||
uint16_t aWeight,
|
||||
uint16_t aPort,
|
||||
NameCompressInfo &aCompressInfo)
|
||||
{
|
||||
SrvRecord srvRecord;
|
||||
otError error = OT_ERROR_NONE;
|
||||
uint16_t recordOffset;
|
||||
|
||||
srvRecord.Init();
|
||||
srvRecord.SetTtl(aTtl);
|
||||
srvRecord.SetPriority(aPriority);
|
||||
srvRecord.SetWeight(aWeight);
|
||||
srvRecord.SetPort(aPort);
|
||||
|
||||
SuccessOrExit(error = AppendInstanceName(aMessage, aInstanceName, aCompressInfo));
|
||||
|
||||
recordOffset = aMessage.GetLength();
|
||||
SuccessOrExit(error = aMessage.SetLength(recordOffset + sizeof(srvRecord)));
|
||||
|
||||
SuccessOrExit(error = AppendHostName(aMessage, aHostName, aCompressInfo));
|
||||
|
||||
srvRecord.SetLength(aMessage.GetLength() - (recordOffset + sizeof(ResourceRecord)));
|
||||
aMessage.Write(recordOffset, srvRecord);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError Server::AppendAaaaRecord(Message & aMessage,
|
||||
const char * aHostName,
|
||||
const Ip6::Address &aAddress,
|
||||
uint32_t aTtl,
|
||||
NameCompressInfo & aCompressInfo)
|
||||
{
|
||||
AaaaRecord aaaaRecord;
|
||||
otError error;
|
||||
|
||||
aaaaRecord.Init();
|
||||
aaaaRecord.SetTtl(aTtl);
|
||||
aaaaRecord.SetAddress(aAddress);
|
||||
|
||||
SuccessOrExit(error = AppendHostName(aMessage, aHostName, aCompressInfo));
|
||||
error = aMessage.Append(aaaaRecord);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError Server::AppendServiceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo)
|
||||
{
|
||||
OT_UNUSED_VARIABLE(aName);
|
||||
OT_ASSERT(strcmp(aCompressInfo.GetServiceName(), aName) == 0);
|
||||
|
||||
return Dns::Name::AppendPointerLabel(aCompressInfo.GetServiceNameOffset(), aMessage);
|
||||
}
|
||||
|
||||
otError Server::AppendInstanceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo)
|
||||
{
|
||||
otError error;
|
||||
|
||||
uint16_t nameOffset = aCompressInfo.GetInstanceNameOffset(aName);
|
||||
|
||||
if (nameOffset != NameCompressInfo::kUnknownOffset)
|
||||
{
|
||||
error = Dns::Name::AppendPointerLabel(nameOffset, aMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t serviceStart = static_cast<uint8_t>(StringLength(aName, Name::kMaxLength) -
|
||||
StringLength(aCompressInfo.GetServiceName(), Name::kMaxLength));
|
||||
|
||||
aCompressInfo.SetInstanceNameOffset(aMessage.GetLength(), aName);
|
||||
|
||||
SuccessOrExit(error = Dns::Name::AppendLabel(aName, serviceStart - 1, aMessage));
|
||||
error = Dns::Name::AppendPointerLabel(aCompressInfo.GetServiceNameOffset(), aMessage);
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError Server::AppendHostName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo)
|
||||
{
|
||||
otError error;
|
||||
uint16_t hostCompressOffset = aCompressInfo.GetHostNameOffset(aName);
|
||||
|
||||
if (hostCompressOffset != NameCompressInfo::kUnknownOffset)
|
||||
{
|
||||
error = Dns::Name::AppendPointerLabel(hostCompressOffset, aMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t domainStart = static_cast<uint8_t>(StringLength(aName, Name::kMaxLength) -
|
||||
StringLength(aCompressInfo.GetDomainName(), Name::kMaxLength));
|
||||
|
||||
aCompressInfo.SetHostNameOffset(aMessage.GetLength(), aName);
|
||||
|
||||
SuccessOrExit(error = Dns::Name::AppendMultipleLabels(aName, domainStart, aMessage));
|
||||
error = Dns::Name::AppendPointerLabel(aCompressInfo.GetDomainNameOffset(), aMessage);
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
void Server::IncResourceRecordCount(Header &aHeader, bool aAdditional)
|
||||
{
|
||||
if (aAdditional)
|
||||
{
|
||||
aHeader.SetAdditionalRecordCount(aHeader.GetAdditionalRecordCount() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
aHeader.SetAnswerCount(aHeader.GetAnswerCount() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
otError Server::FindNameComponents(const char *aName, const char *aDomain, NameComponentsOffsetInfo &aInfo)
|
||||
{
|
||||
uint8_t nameLen = static_cast<uint8_t>(StringLength(aName, Name::kMaxLength));
|
||||
uint8_t domainLen = static_cast<uint8_t>(StringLength(aDomain, Name::kMaxLength));
|
||||
otError error = OT_ERROR_NONE;
|
||||
uint8_t labelBegin, labelEnd;
|
||||
|
||||
VerifyOrExit(Dns::Name::IsSubDomainOf(aName, aDomain), error = OT_ERROR_INVALID_ARGS);
|
||||
|
||||
labelBegin = nameLen - domainLen;
|
||||
aInfo.mDomainOffset = labelBegin;
|
||||
|
||||
while (true)
|
||||
{
|
||||
error = FindPreviousLabel(aName, labelBegin, labelEnd);
|
||||
|
||||
VerifyOrExit(error == OT_ERROR_NONE, error = (error == OT_ERROR_NOT_FOUND ? OT_ERROR_NONE : error));
|
||||
|
||||
if (labelEnd == labelBegin + kProtocolLabelLength &&
|
||||
(memcmp(&aName[labelBegin], kDnssdProtocolUdp, kProtocolLabelLength) == 0 ||
|
||||
memcmp(&aName[labelBegin], kDnssdProtocolTcp, kProtocolLabelLength) == 0))
|
||||
{
|
||||
// <Protocol> label found
|
||||
aInfo.mProtocolOffset = labelBegin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get service label <Service>
|
||||
error = FindPreviousLabel(aName, labelBegin, labelEnd);
|
||||
VerifyOrExit(error == OT_ERROR_NONE, error = (error == OT_ERROR_NOT_FOUND ? OT_ERROR_NONE : error));
|
||||
|
||||
aInfo.mServiceOffset = labelBegin;
|
||||
|
||||
// Treat everything before <Service> as <Instance> label
|
||||
error = FindPreviousLabel(aName, labelBegin, labelEnd);
|
||||
VerifyOrExit(error == OT_ERROR_NONE, error = (error == OT_ERROR_NOT_FOUND ? OT_ERROR_NONE : error));
|
||||
|
||||
aInfo.mInstanceOffset = 0;
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError Server::FindPreviousLabel(const char *aName, uint8_t &aStart, uint8_t &aStop)
|
||||
{
|
||||
// This method finds the previous label before the current label (whose start index is @p aStart), and updates @p
|
||||
// aStart to the start index of the label and @p aStop to the index of the dot just after the label.
|
||||
// @note The input value of @p aStop does not matter because it is only used to output.
|
||||
|
||||
otError error = OT_ERROR_NONE;
|
||||
uint8_t start = aStart;
|
||||
uint8_t end;
|
||||
|
||||
VerifyOrExit(start > 0, error = OT_ERROR_NOT_FOUND);
|
||||
VerifyOrExit(aName[--start] == Name::kLabelSeperatorChar, error = OT_ERROR_INVALID_ARGS);
|
||||
|
||||
end = start;
|
||||
while (start > 0 && aName[start - 1] != Name::kLabelSeperatorChar)
|
||||
{
|
||||
start--;
|
||||
}
|
||||
|
||||
VerifyOrExit(start < end, error = OT_ERROR_INVALID_ARGS);
|
||||
|
||||
aStart = start;
|
||||
aStop = end;
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
|
||||
otError Server::ResolveQuestionBySrp(const char * aName,
|
||||
const Question & aQuestion,
|
||||
Header & aResponseHeader,
|
||||
Message & aResponseMessage,
|
||||
bool aAdditional,
|
||||
NameCompressInfo &aCompressInfo)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
const Srp::Server::Host *host = nullptr;
|
||||
TimeMilli now = TimerMilli::GetNow();
|
||||
uint16_t qtype = aQuestion.GetType();
|
||||
|
||||
while ((host = GetNextSrpHost(host)) != nullptr)
|
||||
{
|
||||
bool needAdditionalAaaaRecord = false;
|
||||
const char *hostName = host->GetFullName();
|
||||
|
||||
// Handle PTR/SRV/TXT query
|
||||
if (qtype == ResourceRecord::kTypePtr || qtype == ResourceRecord::kTypeSrv || qtype == ResourceRecord::kTypeTxt)
|
||||
{
|
||||
const Srp::Server::Service *service = nullptr;
|
||||
|
||||
while ((service = GetNextSrpService(*host, service)) != nullptr)
|
||||
{
|
||||
uint32_t instanceTtl = TimeMilli::MsecToSec(service->GetExpireTime() - TimerMilli::GetNow());
|
||||
const char *instanceName = service->GetFullName();
|
||||
bool serviceNameMatched = service->MatchesServiceName(aName);
|
||||
bool instanceNameMatched = service->Matches(aName);
|
||||
bool ptrQueryMatched = qtype == ResourceRecord::kTypePtr && serviceNameMatched;
|
||||
bool srvQueryMatched = qtype == ResourceRecord::kTypeSrv && instanceNameMatched;
|
||||
bool txtQueryMatched = qtype == ResourceRecord::kTypeTxt && instanceNameMatched;
|
||||
|
||||
if (ptrQueryMatched || srvQueryMatched)
|
||||
{
|
||||
needAdditionalAaaaRecord = true;
|
||||
}
|
||||
|
||||
if (!aAdditional && ptrQueryMatched)
|
||||
{
|
||||
SuccessOrExit(
|
||||
error = AppendPtrRecord(aResponseMessage, aName, instanceName, instanceTtl, aCompressInfo));
|
||||
IncResourceRecordCount(aResponseHeader, aAdditional);
|
||||
}
|
||||
|
||||
if ((!aAdditional && srvQueryMatched) || (aAdditional && ptrQueryMatched))
|
||||
{
|
||||
SuccessOrExit(error = AppendSrvRecord(aResponseMessage, instanceName, hostName, instanceTtl,
|
||||
service->GetPriority(), service->GetWeight(),
|
||||
service->GetPort(), aCompressInfo));
|
||||
IncResourceRecordCount(aResponseHeader, aAdditional);
|
||||
}
|
||||
|
||||
if ((!aAdditional && txtQueryMatched) || (aAdditional && ptrQueryMatched))
|
||||
{
|
||||
SuccessOrExit(
|
||||
error = AppendTxtRecord(aResponseMessage, instanceName, *service, instanceTtl, aCompressInfo));
|
||||
IncResourceRecordCount(aResponseHeader, aAdditional);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle AAAA query
|
||||
if ((!aAdditional && qtype == ResourceRecord::kTypeAaaa && host->Matches(aName)) ||
|
||||
(aAdditional && needAdditionalAaaaRecord))
|
||||
{
|
||||
uint8_t addrNum;
|
||||
const Ip6::Address *addrs = host->GetAddresses(addrNum);
|
||||
uint32_t hostTtl = TimeMilli::MsecToSec(host->GetExpireTime() - now);
|
||||
|
||||
for (uint8_t i = 0; i < addrNum; i++)
|
||||
{
|
||||
SuccessOrExit(error = AppendAaaaRecord(aResponseMessage, hostName, addrs[i], hostTtl, aCompressInfo));
|
||||
IncResourceRecordCount(aResponseHeader, aAdditional);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
const Srp::Server::Host *Server::GetNextSrpHost(const Srp::Server::Host *aHost)
|
||||
{
|
||||
const Srp::Server::Host *host = Get<Srp::Server>().GetNextHost(aHost);
|
||||
|
||||
while (host != nullptr && host->IsDeleted())
|
||||
{
|
||||
host = Get<Srp::Server>().GetNextHost(host);
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
const Srp::Server::Service *Server::GetNextSrpService(const Srp::Server::Host & aHost,
|
||||
const Srp::Server::Service *aService)
|
||||
{
|
||||
const Srp::Server::Service *service = aHost.GetNextService(aService);
|
||||
|
||||
while (service != nullptr && service->IsDeleted())
|
||||
{
|
||||
service = aHost.GetNextService(service);
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
otError Server::AppendTxtRecord(Message & aMessage,
|
||||
const char * aInstanceName,
|
||||
const Srp::Server::Service &aService,
|
||||
uint32_t aTtl,
|
||||
NameCompressInfo & aCompressInfo)
|
||||
{
|
||||
TxtRecord txtRecord;
|
||||
otError error;
|
||||
Dns::TxtRecord::TxtIterator txtIterator = OT_DNS_TXT_ITERATOR_INIT;
|
||||
Dns::TxtEntry txtEntry;
|
||||
uint16_t recordOffset;
|
||||
bool hasAnyTxtEntry = false;
|
||||
|
||||
SuccessOrExit(error = AppendInstanceName(aMessage, aInstanceName, aCompressInfo));
|
||||
|
||||
recordOffset = aMessage.GetLength();
|
||||
SuccessOrExit(error = aMessage.SetLength(recordOffset + sizeof(txtRecord)));
|
||||
|
||||
while (aService.GetNextTxtEntry(txtIterator, txtEntry) == OT_ERROR_NONE)
|
||||
{
|
||||
SuccessOrExit(error = txtEntry.AppendTo(aMessage));
|
||||
hasAnyTxtEntry = true;
|
||||
}
|
||||
|
||||
if (!hasAnyTxtEntry)
|
||||
{
|
||||
SuccessOrExit(error = aMessage.Append<uint8_t>(0));
|
||||
}
|
||||
|
||||
txtRecord.Init();
|
||||
txtRecord.SetTtl(aTtl);
|
||||
txtRecord.SetLength(aMessage.GetLength() - (recordOffset + sizeof(ResourceRecord)));
|
||||
|
||||
aMessage.Write(recordOffset, txtRecord);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
|
||||
|
||||
} // namespace ServiceDiscovery
|
||||
} // namespace Dns
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_DNS_SERVER_ENABLE
|
||||
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright (c) 2021, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef DNS_SERVER_HPP_
|
||||
#define DNS_SERVER_HPP_
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
|
||||
|
||||
#include <openthread/dns.h>
|
||||
|
||||
#include "common/message.hpp"
|
||||
#include "common/non_copyable.hpp"
|
||||
#include "common/timer.hpp"
|
||||
#include "net/dns_headers.hpp"
|
||||
#include "net/ip6.hpp"
|
||||
#include "net/netif.hpp"
|
||||
#include "net/srp_server.hpp"
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file includes definitions for the DNS-SD server.
|
||||
*/
|
||||
|
||||
namespace ot {
|
||||
namespace Dns {
|
||||
namespace ServiceDiscovery {
|
||||
|
||||
/**
|
||||
* This class implements DNS-SD server.
|
||||
*
|
||||
*/
|
||||
class Server : public InstanceLocator, private NonCopyable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* This constructor initializes the object.
|
||||
*
|
||||
* @param[in] aInstance A reference to the OpenThread instance.
|
||||
*
|
||||
*/
|
||||
explicit Server(Instance &aInstance);
|
||||
|
||||
/**
|
||||
* This method starts the DNS-SD server.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully started the DNS-SD server.
|
||||
* @retval OT_ERROR_FAILED If failed to open or bind the UDP socket.
|
||||
*
|
||||
*/
|
||||
otError Start(void);
|
||||
|
||||
/**
|
||||
* This method stops the DNS-SD server.
|
||||
*
|
||||
*/
|
||||
void Stop(void);
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
kPort = OPENTHREAD_CONFIG_DNSSD_SERVER_PORT,
|
||||
kProtocolLabelLength = 4,
|
||||
};
|
||||
|
||||
class NameCompressInfo : public Clearable<NameCompressInfo>
|
||||
{
|
||||
public:
|
||||
enum : uint16_t
|
||||
{
|
||||
kUnknownOffset = 0, // Unknown offset value (used when offset is not yet set).
|
||||
};
|
||||
|
||||
explicit NameCompressInfo(void) { Clear(); }
|
||||
|
||||
uint16_t GetDomainNameOffset(void) const
|
||||
{
|
||||
OT_ASSERT(mDomainNameOffset != kUnknownOffset);
|
||||
|
||||
return mDomainNameOffset;
|
||||
}
|
||||
|
||||
void SetDomainNameOffset(uint16_t aOffset, const char *aName)
|
||||
{
|
||||
OT_ASSERT(mDomainName == nullptr);
|
||||
|
||||
mDomainName = aName;
|
||||
mDomainNameOffset = aOffset;
|
||||
}
|
||||
|
||||
const char *GetDomainName(void) const { return mDomainName; }
|
||||
|
||||
uint16_t GetServiceNameOffset(void) const
|
||||
{
|
||||
OT_ASSERT(mServiceNameOffset != kUnknownOffset);
|
||||
|
||||
return mServiceNameOffset;
|
||||
};
|
||||
|
||||
void SetServiceNameOffset(uint16_t aOffset, const char *aName)
|
||||
{
|
||||
OT_ASSERT(mServiceName == nullptr);
|
||||
|
||||
mServiceName = aName;
|
||||
mServiceNameOffset = aOffset;
|
||||
}
|
||||
|
||||
const char *GetServiceName() const { return mServiceName; }
|
||||
|
||||
uint16_t GetInstanceNameOffset(const char *aName) const
|
||||
{
|
||||
uint16_t offset = mInstanceNameOffset;
|
||||
|
||||
if (offset != kUnknownOffset && strcmp(aName, mInstanceName) != 0)
|
||||
{
|
||||
offset = kUnknownOffset;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
void SetInstanceNameOffset(uint16_t aOffset, const char *aName)
|
||||
{
|
||||
if (mInstanceName == nullptr)
|
||||
{
|
||||
mInstanceName = aName;
|
||||
mInstanceNameOffset = aOffset;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t GetHostNameOffset(const char *aName) const
|
||||
{
|
||||
uint16_t offset = mHostNameOffset;
|
||||
|
||||
if (offset != kUnknownOffset && strcmp(aName, mHostName) != 0)
|
||||
{
|
||||
offset = kUnknownOffset;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
void SetHostNameOffset(uint16_t aOffset, const char *aName)
|
||||
{
|
||||
if (mHostName == nullptr)
|
||||
{
|
||||
mHostName = aName;
|
||||
mHostNameOffset = aOffset;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const char *mDomainName; // The serialized domain name (must NOT be nullptr)
|
||||
const char *mServiceName; // The serialized service name (must NOT be nullptr for PTR/SRV/TXT queries)
|
||||
const char *mInstanceName; // The serialized instance name or nullptr (only support one instance name)
|
||||
const char *mHostName; // The serialized host name or nullptr (only support one host name)
|
||||
uint16_t mDomainNameOffset; // Offset of domain name serialization into the response message.
|
||||
uint16_t mServiceNameOffset; // Offset of service name serialization into the response message.
|
||||
uint16_t mInstanceNameOffset; // Offset of instance name serialization into the response message.
|
||||
uint16_t mHostNameOffset; // Offset of host name serialization into the response message.
|
||||
};
|
||||
|
||||
// This structure represents the splitting information of a full name.
|
||||
struct NameComponentsOffsetInfo
|
||||
{
|
||||
enum : uint8_t
|
||||
{
|
||||
kNotPresent = 0xff, // Indicates the component is not present.
|
||||
};
|
||||
|
||||
explicit NameComponentsOffsetInfo(void)
|
||||
: mDomainOffset(kNotPresent)
|
||||
, mProtocolOffset(kNotPresent)
|
||||
, mServiceOffset(kNotPresent)
|
||||
, mInstanceOffset(kNotPresent)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsServiceInstanceName(void) const { return mInstanceOffset != kNotPresent; }
|
||||
|
||||
bool IsServiceName(void) const { return mServiceOffset != kNotPresent && mInstanceOffset == kNotPresent; }
|
||||
|
||||
bool IsHostName(void) const { return mProtocolOffset == kNotPresent && mDomainOffset != 0; }
|
||||
|
||||
uint8_t mDomainOffset; // The offset to the beginning of <Domain>.
|
||||
uint8_t mProtocolOffset; // The offset to the beginning of <Protocol> (i.e. _tcp or _udp) or `kNotPresent` if
|
||||
// the name is not a service or instance.
|
||||
uint8_t mServiceOffset; // The offset to the beginning of <Service> or `kNotPresent` if the name is not a
|
||||
// service or instance.
|
||||
uint8_t mInstanceOffset; // The offset to the beginning of <Instance> or `kNotPresent` if the name is not a
|
||||
// instance.
|
||||
};
|
||||
|
||||
bool IsRunning(void) const { return mSocket.IsBound(); }
|
||||
static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
|
||||
void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
|
||||
void ProcessQuery(Message &aMessage, Message &aResponse, const Header &aRequestHeader);
|
||||
static otError AddQuestionToResponse(const char * aName,
|
||||
const Question &aQuestion,
|
||||
Message & aResponse,
|
||||
Header & aResponseHeader);
|
||||
otError PrepareCompressInfo(uint16_t aQueryType,
|
||||
const char * aName,
|
||||
uint16_t aNameSerializeOffset,
|
||||
Server::NameCompressInfo &aCompressInfo);
|
||||
otError ResolveQuestion(const char * aName,
|
||||
const Question & aQuestion,
|
||||
Header & aResponseHeader,
|
||||
Message & aResponseMessage,
|
||||
NameCompressInfo &aCompressInfo);
|
||||
static otError AppendPtrRecord(Message & aMessage,
|
||||
const char * aServiceName,
|
||||
const char * aInstanceName,
|
||||
uint32_t aTtl,
|
||||
NameCompressInfo &aCompressInfo);
|
||||
static otError AppendSrvRecord(Message & aMessage,
|
||||
const char * aInstanceName,
|
||||
const char * aHostName,
|
||||
uint32_t aTtl,
|
||||
uint16_t aPriority,
|
||||
uint16_t aWeight,
|
||||
uint16_t aPort,
|
||||
NameCompressInfo &aCompressInfo);
|
||||
static otError AppendAaaaRecord(Message & aMessage,
|
||||
const char * aHostName,
|
||||
const Ip6::Address &aAddress,
|
||||
uint32_t aTtl,
|
||||
NameCompressInfo & aCompressInfo);
|
||||
static otError AppendServiceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo);
|
||||
static otError AppendInstanceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo);
|
||||
static otError AppendHostName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo);
|
||||
static void IncResourceRecordCount(Header &aHeader, bool aAdditional);
|
||||
static otError FindNameComponents(const char *aName, const char *aDomain, NameComponentsOffsetInfo &aInfo);
|
||||
static otError FindPreviousLabel(const char *aName, uint8_t &aStart, uint8_t &aStop);
|
||||
|
||||
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
|
||||
otError ResolveQuestionBySrp(const char * aName,
|
||||
const Question & aQuestion,
|
||||
Header & aResponseHeader,
|
||||
Message & aResponseMessage,
|
||||
bool aAdditional,
|
||||
NameCompressInfo &aCompressInfo);
|
||||
const Srp::Server::Host * GetNextSrpHost(const Srp::Server::Host *aHost);
|
||||
const Srp::Server::Service *GetNextSrpService(const Srp::Server::Host &aHost, const Srp::Server::Service *aService);
|
||||
static otError AppendTxtRecord(Message & aMessage,
|
||||
const char * aInstanceName,
|
||||
const Srp::Server::Service &aService,
|
||||
uint32_t aTtl,
|
||||
NameCompressInfo & aCompressInfo);
|
||||
#endif
|
||||
|
||||
static const char kDnssdProtocolUdp[4];
|
||||
static const char kDnssdProtocolTcp[4];
|
||||
static const char kDefaultDomainName[];
|
||||
|
||||
Ip6::Udp::Socket mSocket;
|
||||
};
|
||||
|
||||
} // namespace ServiceDiscovery
|
||||
} // namespace Dns
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
|
||||
|
||||
#endif // DNS_SERVER_HPP_
|
||||
@@ -1417,6 +1417,20 @@ bool Server::Service::Matches(const char *aFullName) const
|
||||
return (mFullName != nullptr) && (strcmp(mFullName, aFullName) == 0);
|
||||
}
|
||||
|
||||
bool Server::Service::MatchesServiceName(const char *aServiceName) const
|
||||
{
|
||||
uint8_t i = static_cast<uint8_t>(strlen(mFullName));
|
||||
uint8_t j = static_cast<uint8_t>(strlen(aServiceName));
|
||||
|
||||
while (i > 0 && j > 0 && mFullName[i - 1] == aServiceName[j - 1])
|
||||
{
|
||||
i--;
|
||||
j--;
|
||||
}
|
||||
|
||||
return j == 0 && i > 0 && mFullName[i - 1] == '.';
|
||||
}
|
||||
|
||||
Server::Host *Server::Host::New(void)
|
||||
{
|
||||
void *buf;
|
||||
|
||||
@@ -196,6 +196,17 @@ public:
|
||||
*/
|
||||
bool Matches(const char *aFullName) const;
|
||||
|
||||
/**
|
||||
* This method tells whether this service matches a given service name <Service>.<Domain>.
|
||||
*
|
||||
* @param[in] aServiceName The full service name to match.
|
||||
*
|
||||
* @retval TRUE If the service matches the full service name.
|
||||
* @retval FALSE If the service does not match the full service name.
|
||||
*
|
||||
*/
|
||||
bool MatchesServiceName(const char *aServiceName) const;
|
||||
|
||||
private:
|
||||
explicit Service(void);
|
||||
otError SetFullName(const char *aFullName);
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
#include "config/dhcp6_server.h"
|
||||
#include "config/diag.h"
|
||||
#include "config/dns_client.h"
|
||||
#include "config/dnssd_server.h"
|
||||
#include "config/ip6.h"
|
||||
#include "config/joiner.h"
|
||||
#include "config/link_quality.h"
|
||||
|
||||
@@ -65,6 +65,9 @@ ThreadNetif::ThreadNetif(Instance &aInstance)
|
||||
#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
|
||||
, mSrpClient(aInstance)
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
|
||||
, mDnssdServer(aInstance)
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
|
||||
, mSntpClient(aInstance)
|
||||
#endif
|
||||
@@ -159,6 +162,9 @@ void ThreadNetif::Up(void)
|
||||
SubscribeAllNodesMulticast();
|
||||
IgnoreError(Get<Mle::MleRouter>().Enable());
|
||||
IgnoreError(Get<Tmf::TmfAgent>().Start());
|
||||
#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
|
||||
IgnoreError(Get<Dns::ServiceDiscovery::Server>().Start());
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
|
||||
IgnoreError(Get<Dns::Client>().Start());
|
||||
#endif
|
||||
@@ -181,6 +187,9 @@ void ThreadNetif::Down(void)
|
||||
#if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
|
||||
IgnoreError(Get<Sntp::Client>().Stop());
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
|
||||
Get<Dns::ServiceDiscovery::Server>().Stop();
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_DTLS_ENABLE
|
||||
Get<Coap::CoapSecure>().Stop();
|
||||
#endif
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
#include "net/dhcp6_client.hpp"
|
||||
#include "net/dhcp6_server.hpp"
|
||||
#include "net/dns_client.hpp"
|
||||
#include "net/dnssd_server.hpp"
|
||||
#include "net/ip6_filter.hpp"
|
||||
#include "net/netif.hpp"
|
||||
#include "net/sntp_client.hpp"
|
||||
@@ -206,6 +207,9 @@ private:
|
||||
#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
|
||||
Srp::Client mSrpClient;
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
|
||||
Dns::ServiceDiscovery::Server mDnssdServer;
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
|
||||
Sntp::Client mSntpClient;
|
||||
#endif
|
||||
|
||||
@@ -49,6 +49,7 @@ DHCP6_CLIENT ?= 1
|
||||
DHCP6_SERVER ?= 1
|
||||
DIAGNOSTIC ?= 1
|
||||
DNS_CLIENT ?= 1
|
||||
DNSSD_SERVER ?= 1
|
||||
DYNAMIC_LOG_LEVEL ?= 1
|
||||
ECDSA ?= 1
|
||||
IP6_FRAGM ?= 1
|
||||
|
||||
@@ -158,6 +158,7 @@ EXTRA_DIST = \
|
||||
test_common.py \
|
||||
test_crypto.py \
|
||||
test_diag.py \
|
||||
test_dnssd.py \
|
||||
test_ipv6.py \
|
||||
test_ipv6_fragmentation.py \
|
||||
test_ipv6_source_selection.py \
|
||||
@@ -210,6 +211,7 @@ check_SCRIPTS = \
|
||||
test_common.py \
|
||||
test_crypto.py \
|
||||
test_diag.py \
|
||||
test_dnssd.py \
|
||||
test_ipv6.py \
|
||||
test_ipv6_fragmentation.py \
|
||||
test_ipv6_source_selection.py \
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2021, The OpenThread Authors.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
import ipaddress
|
||||
import json
|
||||
import logging
|
||||
import unittest
|
||||
|
||||
import config
|
||||
import thread_cert
|
||||
|
||||
# Test description:
|
||||
# This test verifies DNS-SD server works on a Duckhorn BR and is accessible from a Host.
|
||||
#
|
||||
# Topology:
|
||||
# ----------------(eth)--------------------
|
||||
# | |
|
||||
# BR1 (Leader, Server) HOST
|
||||
# / \
|
||||
# CLIENT1 CLIENT2
|
||||
|
||||
SERVER = BR1 = 1
|
||||
CLIENT1, CLIENT2 = 2, 3
|
||||
HOST = 4
|
||||
|
||||
DIGGER = HOST
|
||||
|
||||
DOMAIN = 'default.service.arpa.'
|
||||
SERVICE = '_testsrv._udp'
|
||||
SERVICE_FULL_NAME = f'{SERVICE}.{DOMAIN}'
|
||||
|
||||
VALID_SERVICE_NAMES = [
|
||||
'_abc._udp.default.service.arpa.',
|
||||
'_abc._tcp.default.service.arpa.',
|
||||
]
|
||||
|
||||
WRONG_SERVICE_NAMES = [
|
||||
'_testsrv._udp.default.service.xxxx.',
|
||||
'_testsrv._txp,default.service.arpa.',
|
||||
]
|
||||
|
||||
|
||||
class TestDnssdServerOnBr(thread_cert.TestCase):
|
||||
USE_MESSAGE_FACTORY = False
|
||||
|
||||
TOPOLOGY = {
|
||||
BR1: {
|
||||
'name': 'SERVER',
|
||||
'is_otbr': True,
|
||||
'version': '1.2',
|
||||
'router_selection_jitter': 1,
|
||||
},
|
||||
CLIENT1: {
|
||||
'name': 'CLIENT1',
|
||||
'router_selection_jitter': 1,
|
||||
},
|
||||
CLIENT2: {
|
||||
'name': 'CLIENT2',
|
||||
'router_selection_jitter': 1,
|
||||
},
|
||||
HOST: {
|
||||
'name': 'Host',
|
||||
'is_host': True
|
||||
},
|
||||
}
|
||||
|
||||
def test(self):
|
||||
self.nodes[HOST].start(start_radvd=False)
|
||||
self.simulator.go(5)
|
||||
|
||||
self.nodes[BR1].start()
|
||||
self.simulator.go(5)
|
||||
self.assertEqual('leader', self.nodes[BR1].get_state())
|
||||
self.nodes[SERVER].srp_server_set_enabled(True)
|
||||
|
||||
self.nodes[CLIENT1].start()
|
||||
|
||||
self.simulator.go(5)
|
||||
self.assertEqual('router', self.nodes[CLIENT1].get_state())
|
||||
|
||||
self.nodes[CLIENT2].start()
|
||||
self.simulator.go(5)
|
||||
self.assertEqual('router', self.nodes[CLIENT2].get_state())
|
||||
|
||||
self.simulator.go(10)
|
||||
|
||||
# Router1 can ping to/from the Host on infra link.
|
||||
self.assertTrue(self.nodes[BR1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0],
|
||||
backbone=True))
|
||||
self.assertTrue(self.nodes[HOST].ping(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
|
||||
backbone=True))
|
||||
|
||||
client1_addrs = [
|
||||
self.nodes[CLIENT1].get_mleid(), self.nodes[CLIENT1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
|
||||
]
|
||||
self._config_srp_client_services(CLIENT1, 'ins1', 'host1', 11111, 1, 1, client1_addrs)
|
||||
|
||||
client2_addrs = [
|
||||
self.nodes[CLIENT2].get_mleid(), self.nodes[CLIENT2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
|
||||
]
|
||||
self._config_srp_client_services(CLIENT2, 'ins2', 'host2', 22222, 2, 2, client2_addrs)
|
||||
|
||||
ins1_full_name = f'ins1.{SERVICE_FULL_NAME}'
|
||||
ins2_full_name = f'ins2.{SERVICE_FULL_NAME}'
|
||||
host1_full_name = f'host1.{DOMAIN}'
|
||||
host2_full_name = f'host2.{DOMAIN}'
|
||||
server_addr = self.nodes[SERVER].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
|
||||
|
||||
# check if PTR query works
|
||||
dig_result = self.nodes[DIGGER].dns_dig(server_addr, SERVICE_FULL_NAME, 'PTR')
|
||||
|
||||
self._assert_dig_result_matches(
|
||||
dig_result, {
|
||||
'QUESTION': [(SERVICE_FULL_NAME, 'IN', 'PTR')],
|
||||
'ANSWER': [(SERVICE_FULL_NAME, 'IN', 'PTR', f'ins1.{SERVICE_FULL_NAME}'),
|
||||
(SERVICE_FULL_NAME, 'IN', 'PTR', f'ins2.{SERVICE_FULL_NAME}')],
|
||||
'ADDITIONAL': [
|
||||
(ins1_full_name, 'IN', 'SRV', 1, 1, 11111, host1_full_name),
|
||||
(ins1_full_name, 'IN', 'TXT', '""'),
|
||||
(host1_full_name, 'IN', 'AAAA', client1_addrs[0]),
|
||||
(host1_full_name, 'IN', 'AAAA', client1_addrs[1]),
|
||||
(ins2_full_name, 'IN', 'SRV', 2, 2, 22222, host2_full_name),
|
||||
(ins2_full_name, 'IN', 'TXT', '""'),
|
||||
(host2_full_name, 'IN', 'AAAA', client2_addrs[0]),
|
||||
(host2_full_name, 'IN', 'AAAA', client2_addrs[1]),
|
||||
],
|
||||
})
|
||||
|
||||
# check if SRV query works
|
||||
dig_result = self.nodes[DIGGER].dns_dig(server_addr, ins1_full_name, 'SRV')
|
||||
self._assert_dig_result_matches(
|
||||
dig_result, {
|
||||
'QUESTION': [(ins1_full_name, 'IN', 'SRV')],
|
||||
'ANSWER': [(ins1_full_name, 'IN', 'SRV', 1, 1, 11111, host1_full_name),],
|
||||
'ADDITIONAL': [
|
||||
(host1_full_name, 'IN', 'AAAA', client1_addrs[0]),
|
||||
(host1_full_name, 'IN', 'AAAA', client1_addrs[1]),
|
||||
],
|
||||
})
|
||||
|
||||
dig_result = self.nodes[DIGGER].dns_dig(server_addr, ins2_full_name, 'SRV')
|
||||
self._assert_dig_result_matches(
|
||||
dig_result, {
|
||||
'QUESTION': [(ins2_full_name, 'IN', 'SRV')],
|
||||
'ANSWER': [(ins2_full_name, 'IN', 'SRV', 2, 2, 22222, host2_full_name),],
|
||||
'ADDITIONAL': [
|
||||
(host2_full_name, 'IN', 'AAAA', client2_addrs[0]),
|
||||
(host2_full_name, 'IN', 'AAAA', client2_addrs[1]),
|
||||
],
|
||||
})
|
||||
|
||||
# check if TXT query works
|
||||
dig_result = self.nodes[DIGGER].dns_dig(server_addr, ins1_full_name, 'TXT')
|
||||
self._assert_dig_result_matches(dig_result, {
|
||||
'QUESTION': [(ins1_full_name, 'IN', 'TXT')],
|
||||
'ANSWER': [(ins1_full_name, 'IN', 'TXT', '""'),],
|
||||
})
|
||||
|
||||
dig_result = self.nodes[DIGGER].dns_dig(server_addr, ins2_full_name, 'TXT')
|
||||
self._assert_dig_result_matches(dig_result, {
|
||||
'QUESTION': [(ins2_full_name, 'IN', 'TXT')],
|
||||
'ANSWER': [(ins2_full_name, 'IN', 'TXT', '""'),],
|
||||
})
|
||||
|
||||
# check if AAAA query works
|
||||
dig_result = self.nodes[DIGGER].dns_dig(server_addr, host1_full_name, 'AAAA')
|
||||
self._assert_dig_result_matches(
|
||||
dig_result, {
|
||||
'QUESTION': [(host1_full_name, 'IN', 'AAAA'),],
|
||||
'ANSWER': [
|
||||
(host1_full_name, 'IN', 'AAAA', client1_addrs[0]),
|
||||
(host1_full_name, 'IN', 'AAAA', client1_addrs[1]),
|
||||
],
|
||||
})
|
||||
|
||||
dig_result = self.nodes[DIGGER].dns_dig(server_addr, host2_full_name, 'AAAA')
|
||||
self._assert_dig_result_matches(
|
||||
dig_result, {
|
||||
'QUESTION': [(host2_full_name, 'IN', 'AAAA'),],
|
||||
'ANSWER': [
|
||||
(host2_full_name, 'IN', 'AAAA', client2_addrs[0]),
|
||||
(host2_full_name, 'IN', 'AAAA', client2_addrs[1]),
|
||||
],
|
||||
})
|
||||
|
||||
# check some invalid queries
|
||||
for qtype in ['A', 'CNAME']:
|
||||
dig_result = self.nodes[DIGGER].dns_dig(server_addr, host1_full_name, qtype)
|
||||
self._assert_dig_result_matches(dig_result, {
|
||||
'status': 'NOTIMP',
|
||||
'QUESTION': [(host1_full_name, 'IN', qtype)],
|
||||
})
|
||||
|
||||
for service_name in WRONG_SERVICE_NAMES:
|
||||
dig_result = self.nodes[DIGGER].dns_dig(server_addr, service_name, 'PTR')
|
||||
self._assert_dig_result_matches(dig_result, {
|
||||
'status': 'NXDOMAIN',
|
||||
'QUESTION': [(service_name, 'IN', 'PTR')],
|
||||
})
|
||||
|
||||
def _config_srp_client_services(self, client, instancename, hostname, port, priority, weight, addrs):
|
||||
self.nodes[client].netdata_show()
|
||||
srp_server_port = self.nodes[client].get_srp_server_port()
|
||||
|
||||
self.nodes[client].srp_client_start(self.nodes[SERVER].get_mleid(), srp_server_port)
|
||||
self.nodes[client].srp_client_set_host_name(hostname)
|
||||
self.nodes[client].srp_client_set_host_address(*addrs)
|
||||
self.nodes[client].srp_client_add_service(instancename, SERVICE, port, priority, weight)
|
||||
|
||||
self.simulator.go(5)
|
||||
self.assertEqual(self.nodes[client].srp_client_get_host_state(), 'Registered')
|
||||
|
||||
def _assert_have_question(self, dig_result, question):
|
||||
self.assertIn(question, dig_result['QUESTION'], (question, dig_result))
|
||||
|
||||
def _assert_have_answer(self, dig_result, record, additional=False):
|
||||
for dig_answer in dig_result['ANSWER' if not additional else 'ADDITIONAL']:
|
||||
dig_answer = list(dig_answer)
|
||||
dig_answer[1:2] = [] # remove TTL from answer
|
||||
|
||||
record = list(record)
|
||||
|
||||
# convert IPv6 addresses to `ipaddress.IPv6Address` before matching
|
||||
if dig_answer[2] == 'AAAA':
|
||||
dig_answer[3] = ipaddress.IPv6Address(dig_answer[3])
|
||||
|
||||
if record[2] == 'AAAA':
|
||||
record[3] = ipaddress.IPv6Address(record[3])
|
||||
|
||||
if dig_answer == record:
|
||||
return
|
||||
|
||||
self.fail((record, dig_result))
|
||||
|
||||
def _assert_dig_result_matches(self, dig_result, expected_result):
|
||||
self.assertEqual(dig_result['opcode'], expected_result.get('opcode', 'QUERY'), dig_result)
|
||||
self.assertEqual(dig_result['status'], expected_result.get('status', 'NOERROR'), dig_result)
|
||||
|
||||
self.assertEqual(len(dig_result['QUESTION']), len(expected_result.get('QUESTION', [])), dig_result)
|
||||
self.assertEqual(len(dig_result['ANSWER']), len(expected_result.get('ANSWER', [])), dig_result)
|
||||
self.assertEqual(len(dig_result['ADDITIONAL']), len(expected_result.get('ADDITIONAL', [])), dig_result)
|
||||
|
||||
for question in expected_result.get('QUESTION', []):
|
||||
self._assert_have_question(dig_result, question)
|
||||
|
||||
for record in expected_result.get('ANSWER', []):
|
||||
self._assert_have_answer(dig_result, record, additional=False)
|
||||
|
||||
for record in expected_result.get('ADDITIONAL', []):
|
||||
self._assert_have_answer(dig_result, record, additional=True)
|
||||
|
||||
logging.info("dig result matches:\r%s", json.dumps(dig_result, indent=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -218,6 +218,85 @@ class OtbrDocker:
|
||||
else:
|
||||
return lines
|
||||
|
||||
def dns_dig(self, server: str, name: str, qtype: str):
|
||||
"""
|
||||
Run dig command to query a DNS server.
|
||||
|
||||
Args:
|
||||
server: the server address.
|
||||
name: the name to query.
|
||||
qtype: the query type (e.g. AAAA, PTR, TXT, SRV).
|
||||
|
||||
Returns:
|
||||
The dig result similar as below:
|
||||
{
|
||||
"opcode": "QUERY",
|
||||
"status": "NOERROR",
|
||||
"id": "64144",
|
||||
"QUESTION": [
|
||||
('google.com.', 'IN', 'AAAA')
|
||||
],
|
||||
"ANSWER": [
|
||||
('google.com.', 107, 'IN', 'AAAA', '2404:6800:4008:c00::71'),
|
||||
('google.com.', 107, 'IN', 'AAAA', '2404:6800:4008:c00::8a'),
|
||||
('google.com.', 107, 'IN', 'AAAA', '2404:6800:4008:c00::66'),
|
||||
('google.com.', 107, 'IN', 'AAAA', '2404:6800:4008:c00::8b'),
|
||||
],
|
||||
"ADDITIONAL": [
|
||||
],
|
||||
}
|
||||
"""
|
||||
output = self.bash(f'dig -6 @{server} {name} {qtype}')
|
||||
|
||||
section = None
|
||||
dig_result = {
|
||||
'QUESTION': [],
|
||||
'ANSWER': [],
|
||||
'ADDITIONAL': [],
|
||||
}
|
||||
|
||||
for line in output:
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith(';; ->>HEADER<<- '):
|
||||
headers = line[len(';; ->>HEADER<<- '):].split(', ')
|
||||
for header in headers:
|
||||
key, val = header.split(': ')
|
||||
dig_result[key] = val
|
||||
|
||||
continue
|
||||
|
||||
if line == ';; QUESTION SECTION:':
|
||||
section = 'QUESTION'
|
||||
continue
|
||||
elif line == ';; ANSWER SECTION:':
|
||||
section = 'ANSWER'
|
||||
continue
|
||||
elif line == ';; ADDITIONAL SECTION:':
|
||||
section = 'ADDITIONAL'
|
||||
continue
|
||||
elif section and not line:
|
||||
section = None
|
||||
continue
|
||||
|
||||
if section:
|
||||
assert line
|
||||
|
||||
if section == 'QUESTION':
|
||||
assert line.startswith(';')
|
||||
line = line[1:]
|
||||
|
||||
record = list(line.split())
|
||||
|
||||
if section != 'QUESTION':
|
||||
record[1] = int(record[1])
|
||||
if record[3] == 'SRV':
|
||||
record[4], record[5], record[6] = map(int, [record[4], record[5], record[6]])
|
||||
|
||||
dig_result[section].append(tuple(record))
|
||||
|
||||
return dig_result
|
||||
|
||||
def _setup_sysctl(self):
|
||||
self.bash(f'sysctl net.ipv6.conf.{self.ETH_DEV}.accept_ra=2')
|
||||
self.bash(f'sysctl net.ipv6.conf.{self.ETH_DEV}.accept_ra_rt_info_max_plen=64')
|
||||
@@ -860,8 +939,8 @@ class NodeImpl:
|
||||
self.send_command(f'srp client host clear')
|
||||
self._expect_done()
|
||||
|
||||
def srp_client_set_host_address(self, address):
|
||||
self.send_command(f'srp client host address {address}')
|
||||
def srp_client_set_host_address(self, *addrs: str):
|
||||
self.send_command(f'srp client host address {" ".join(addrs)}')
|
||||
self._expect_done()
|
||||
|
||||
def srp_client_get_host_address(self):
|
||||
@@ -2323,6 +2402,22 @@ class NodeImpl:
|
||||
self.send_command(cmd)
|
||||
self._expect_done()
|
||||
|
||||
def dns_resolve(self, hostname, server=None, port=53):
|
||||
cmd = f'dns resolve {hostname}'
|
||||
if server is not None:
|
||||
cmd += f' {server} {port}'
|
||||
|
||||
self.send_command(cmd)
|
||||
self.simulator.go(10)
|
||||
output = self._expect_command_output(cmd)
|
||||
dns_resp = output[0]
|
||||
# example output: DNS response for host1.default.service.arpa. - fd00:db8:0:0:ae43:4938:4c42:e6af TTL: 7190
|
||||
ip, ttl = dns_resp.split(' - ')[1].split(' TTL: ')
|
||||
ip = ip.strip()
|
||||
ttl = int(ttl)
|
||||
|
||||
return (ip, ttl)
|
||||
|
||||
|
||||
class Node(NodeImpl, OtCli):
|
||||
pass
|
||||
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2021, The OpenThread Authors.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
import ipaddress
|
||||
import unittest
|
||||
|
||||
import thread_cert
|
||||
|
||||
SERVER = 1
|
||||
CLIENT1 = 2
|
||||
CLIENT2 = 3
|
||||
|
||||
DOMAIN = 'default.service.arpa.'
|
||||
SERVICE = '_ipps._tcp'
|
||||
|
||||
#
|
||||
# Topology:
|
||||
# LEADER -- CLIENT1
|
||||
# |
|
||||
# CLIENT2
|
||||
#
|
||||
|
||||
|
||||
class TestDnssd(thread_cert.TestCase):
|
||||
SUPPORT_NCP = False
|
||||
USE_MESSAGE_FACTORY = False
|
||||
|
||||
TOPOLOGY = {
|
||||
SERVER: {
|
||||
'mode': 'rdn',
|
||||
'panid': 0xface,
|
||||
},
|
||||
CLIENT1: {
|
||||
'mode': 'rdn',
|
||||
'panid': 0xface,
|
||||
'router_selection_jitter': 1,
|
||||
},
|
||||
CLIENT2: {
|
||||
'mode': 'rdn',
|
||||
'panid': 0xface,
|
||||
'router_selection_jitter': 1,
|
||||
},
|
||||
}
|
||||
|
||||
def test(self):
|
||||
self.nodes[SERVER].start()
|
||||
self.simulator.go(5)
|
||||
self.assertEqual(self.nodes[SERVER].get_state(), 'leader')
|
||||
self.nodes[SERVER].srp_server_set_enabled(True)
|
||||
|
||||
self.nodes[CLIENT1].start()
|
||||
self.simulator.go(5)
|
||||
self.assertEqual(self.nodes[CLIENT1].get_state(), 'router')
|
||||
|
||||
self.nodes[CLIENT2].start()
|
||||
self.simulator.go(5)
|
||||
self.assertEqual(self.nodes[CLIENT1].get_state(), 'router')
|
||||
|
||||
client1_addrs = [self.nodes[CLIENT1].get_mleid(), self.nodes[CLIENT1].get_rloc()]
|
||||
self._config_srp_client_services(CLIENT1, 'ins1', 'host1', 11111, 1, 1, client1_addrs)
|
||||
|
||||
client2_addrs = [self.nodes[CLIENT2].get_mleid(), self.nodes[CLIENT2].get_rloc()]
|
||||
self._config_srp_client_services(CLIENT2, 'ins2', 'host2', 22222, 2, 2, client2_addrs)
|
||||
|
||||
# Test AAAA query using DNS client
|
||||
ip, ttl = self.nodes[CLIENT1].dns_resolve(f"host1.{DOMAIN}", self.nodes[SERVER].get_mleid(), 53)
|
||||
self.assertIn(ipaddress.IPv6Address(ip), map(ipaddress.IPv6Address, client1_addrs))
|
||||
|
||||
ip, ttl = self.nodes[CLIENT1].dns_resolve(f"host2.{DOMAIN}", self.nodes[SERVER].get_mleid(), 53)
|
||||
self.assertIn(ipaddress.IPv6Address(ip), map(ipaddress.IPv6Address, client2_addrs))
|
||||
|
||||
# TODO: test other query types using DNS-SD client
|
||||
|
||||
def _config_srp_client_services(self, client, instancename, hostname, port, priority, weight, addrs):
|
||||
self.nodes[client].netdata_show()
|
||||
srp_server_port = self.nodes[client].get_srp_server_port()
|
||||
|
||||
self.nodes[client].srp_client_start(self.nodes[SERVER].get_mleid(), srp_server_port)
|
||||
self.nodes[client].srp_client_set_host_name(hostname)
|
||||
self.nodes[client].srp_client_set_host_address(*addrs)
|
||||
self.nodes[client].srp_client_add_service(instancename, SERVICE, port, priority, weight)
|
||||
|
||||
self.simulator.go(5)
|
||||
self.assertEqual(self.nodes[client].srp_client_get_host_state(), 'Registered')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user