[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:
Simon Lin
2021-02-10 12:25:26 +08:00
committed by GitHub
parent 779890474b
commit 89aacf5dd7
27 changed files with 1643 additions and 13 deletions
+1
View File
@@ -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 \
+5
View File
@@ -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")
+3
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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. |
+5
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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.
+7
View File
@@ -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",
+1
View File
@@ -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
+3
View File
@@ -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 \
+7
View File
@@ -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)
{
+58
View File
@@ -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_
+11 -5
View File
@@ -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))
{
+59 -4
View File
@@ -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.
*
+651
View File
@@ -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
+291
View File
@@ -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_
+14
View File
@@ -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;
+11
View File
@@ -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);
+1
View File
@@ -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"
+9
View File
@@ -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
+4
View File
@@ -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
+1
View File
@@ -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
+2
View File
@@ -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()
+97 -2
View File
@@ -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
+113
View File
@@ -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()