diff --git a/include/openthread/dns_client.h b/include/openthread/dns_client.h index c790bc18c..6c9f5d1bb 100644 --- a/include/openthread/dns_client.h +++ b/include/openthread/dns_client.h @@ -515,7 +515,7 @@ typedef struct otDnsServiceResponse otDnsServiceResponse; typedef void (*otDnsServiceCallback)(otError aError, const otDnsServiceResponse *aResponse, void *aContext); /** - * This function sends a DNS service instance resolution query for a given service instance. + * This function starts a DNS service instance resolution for a given service instance. * * This function is available when `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is enabled. * @@ -554,6 +554,43 @@ otError otDnsClientResolveService(otInstance *aInstance, void *aContext, const otDnsQueryConfig *aConfig); +/** + * This function starts a DNS service instance resolution for a given service instance, with a potential follow-up + * address resolution for the host name discovered for the service instance. + * + * This function is available when `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is enabled. + * + * The @p aConfig can be NULL. In this case the default config (from `otDnsClientGetDefaultConfig()`) will be used as + * the config for this query. In a non-NULL @p aConfig, some of the fields can be left unspecified (value zero). The + * unspecified fields are then replaced by the values from the default config. This function cannot be used with + * `mServiceMode` in DNS config set to `OT_DNS_SERVICE_MODE_TXT` (i.e., querying for TXT record only) and will return + * `OT_ERROR_INVALID_ARGS`. + * + * This function behaves similarly to `otDnsClientResolveService()` sending queries for SRV and TXT records. However, + * if the server/resolver does not provide AAAA/A records for the host name in the response to SRV query (in the + * Additional Data section), it will perform host name resolution (sending an AAAA query) for the discovered host name + * from the SRV record. The callback @p aCallback is invoked when responses for all queries are received (i.e., both + * service and host address resolutions are finished). + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aInstanceLabel The service instance label. + * @param[in] aServiceName The service name (together with @p aInstanceLabel form full instance name). + * @param[in] aCallback A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aConfig A pointer to the config to use for this query. + * + * @retval OT_ERROR_NONE Query sent successfully. @p aCallback will be invoked to report the status. + * @retval OT_ERROR_NO_BUFS Insufficient buffer to prepare and send query. + * @retval OT_ERROR_INVALID_ARGS @p aInstanceLabel is NULL, or @p aConfig is invalid. + * + */ +otError otDnsClientResolveServiceAndHostAddress(otInstance *aInstance, + const char *aInstanceLabel, + const char *aServiceName, + otDnsServiceCallback aCallback, + void *aContext, + const otDnsQueryConfig *aConfig); + /** * This function gets the service instance name associated with a DNS service instance resolution response. * @@ -579,13 +616,16 @@ otError otDnsServiceResponseGetServiceName(const otDnsServiceResponse *aResponse /** * This function gets info for a service instance from a DNS service instance resolution response. * - * This function MUST only be used from `otDnsServiceCallback`. + * This function MUST only be used from a `otDnsServiceCallback` triggered from `otDnsClientResolveService()` or + * `otDnsClientResolveServiceAndHostAddress()`. * - * A service resolution DNS response may include AAAA records in its Additional Data section for host name associated - * with the service instance that is resolved. This is a SHOULD and not a MUST requirement so servers/resolvers are - * not required to provide this. This function attempts to retrieve AAAA record(s) if included in the response. If it - * is not included `mHostAddress` is set to all zero (unspecified address). If the caller wants to resolve the host - * address it can call `otDnsClientResolveAddress()` with the host name to start an address resolution query. + * When this is is used from a `otDnsClientResolveService()` callback, the DNS response from server/resolver may + * include AAAA records in its Additional Data section for the host name associated with the service instance that is + * resolved. This is a SHOULD and not a MUST requirement so servers/resolvers are not required to provide this. This + * function attempts to parse AAAA record(s) if included in the response. If it is not included `mHostAddress` is set + * to all zeros (unspecified address). To also resolve the host address, user can use the DNS client API function + * `otDnsClientResolveServiceAndHostAddress()` which will perform service resolution followed up by a host name + * address resolution query (when AAAA records are not provided by server/resolver in the SRV query response). * * - If a matching SRV record is found in @p aResponse, @p aServiceInfo is updated. * - If no matching SRV record is found, `OT_ERROR_NOT_FOUND` is returned unless the query config for this query diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 580f334c5..3cb230a9b 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (322) +#define OPENTHREAD_API_VERSION (323) /** * @addtogroup api-instance diff --git a/src/cli/README.md b/src/cli/README.md index bdc405b43..b9a85fd99 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -1269,6 +1269,14 @@ The parameters after `service-name` are optional. Any unspecified (or zero) valu > Note: The DNS server IP can be an IPv4 address, which will be synthesized to an IPv6 address using the preferred NAT64 prefix from the network data. The command will return `InvalidState` when the DNS server IP is an IPv4 address but the preferred NAT64 prefix is unavailable. +### dns servicehost \ \ \[DNS server IP\] \[DNS server port\] \[response timeout (ms)\] \[max tx attempts\] \[recursion desired (boolean)\] + +Send a service instance resolution DNS query for a given service instance with a potential follow-up address resolution for the host name discovered for the service instance (if the server/resolver does not provide AAAA/A records for the host name in the response to SRV query). + +Service instance label is provided first, followed by the service name (note that service instance label can contain dot '.' character). + +The parameters after `service-name` are optional. Any unspecified (or zero) value for these optional parameters is replaced by the value from the current default config (`dns config`). + ### dns compression \[enable|disable\] Enable/Disable the "DNS name compression" mode. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 8bb1d4950..133775e5a 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -3162,7 +3162,7 @@ template <> otError Interpreter::Process(Arg aArgs[]) * `InvalidState` when the DNS server IP is an IPv4 address but the preferred NAT64 prefix * is unavailable. When testing DNS-SD discovery proxy, the zone is not `local` and * instead should be `default.service.arpa`. - * 'OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE' is required. + * `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is required. */ else if (aArgs[0] == "browse") { @@ -3191,9 +3191,9 @@ template <> otError Interpreter::Process(Arg aArgs[]) * @par * Note: The DNS server IP can be an IPv4 address, which will be synthesized * to an IPv6 address using the preferred NAT64 prefix from the network data. - * The command will return `InvalidState` when the DNS * server IP is an IPv4 + * The command will return `InvalidState` when the DNS server IP is an IPv4 * address but the preferred NAT64 prefix is unavailable. - * 'OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE' is required. + * `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is required. */ else if (aArgs[0] == "service") { @@ -3203,6 +3203,39 @@ template <> otError Interpreter::Process(Arg aArgs[]) &Interpreter::HandleDnsServiceResponse, this, config)); error = OT_ERROR_PENDING; } + /** + * @cli dns servicehost + * @cparam dns servicehost @ca{service-instance-label} @ca{service-name} [@ca{DNS-server-IP}] [@ca{DNS-server-port}] [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] [@ca{recursion-desired-boolean}] + * @par api_copy + * #otDnsClientResolveServiceAndHostAddress + * @par + * Send a service instance resolution DNS query for a given service instance + * with potential follow-up host name resolution. + * Service instance label is provided first, followed by the service name + * (note that service instance label can contain dot '.' character). + * @par + * The parameters after `service-name` are optional. Any unspecified (or zero) + * value for these optional parameters is replaced by the value from the + * current default config (`dns config`). + * @par + * Note: The DNS server IP can be an IPv4 address, which will be synthesized + * to an IPv6 address using the preferred NAT64 prefix from the network data. + * The command will return `InvalidState` when the DNS server IP is an IPv4 + * address but the preferred NAT64 prefix is unavailable. + * `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is required. + */ + else if (aArgs[0] == "servicehost") + { + VerifyOrExit(!aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + SuccessOrExit(error = GetDnsConfig(aArgs + 3, config)); + SuccessOrExit(error = otDnsClientResolveServiceAndHostAddress( + GetInstancePtr(), aArgs[1].GetCString(), aArgs[2].GetCString(), + &Interpreter::HandleDnsServiceResponse, this, config)); + error = OT_ERROR_PENDING; + } #endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE #endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE #if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE diff --git a/src/core/api/dns_api.cpp b/src/core/api/dns_api.cpp index f1020ec58..80246ca93 100644 --- a/src/core/api/dns_api.cpp +++ b/src/core/api/dns_api.cpp @@ -188,6 +188,20 @@ otError otDnsClientResolveService(otInstance *aInstance, AsCoreTypePtr(aConfig)); } +otError otDnsClientResolveServiceAndHostAddress(otInstance *aInstance, + const char *aInstanceLabel, + const char *aServiceName, + otDnsServiceCallback aCallback, + void *aContext, + const otDnsQueryConfig *aConfig) +{ + AssertPointerIsNotNull(aInstanceLabel); + AssertPointerIsNotNull(aServiceName); + + return AsCoreType(aInstance).Get().ResolveServiceAndHostAddress( + aInstanceLabel, aServiceName, aCallback, aContext, AsCoreTypePtr(aConfig)); +} + otError otDnsServiceResponseGetServiceName(const otDnsServiceResponse *aResponse, char *aLabelBuffer, uint8_t aLabelBufferSize, diff --git a/src/core/net/dns_client.cpp b/src/core/net/dns_client.cpp index da1672535..2dd9531de 100644 --- a/src/core/net/dns_client.cpp +++ b/src/core/net/dns_client.cpp @@ -309,6 +309,8 @@ Error Client::Response::ReadServiceInfo(Section aSection, const Name &aName, Ser // Search in additional section for AAAA record for the host name. + VerifyOrExit(AsCoreType(&aServiceInfo.mHostAddress).IsUnspecified()); + error = FindHostAddress(kAdditionalDataSection, hostName, /* aIndex */ 0, AsCoreType(&aServiceInfo.mHostAddress), aServiceInfo.mHostAddressTtl); @@ -598,6 +600,27 @@ Error Client::ServiceResponse::GetServiceInfo(ServiceInfo &aServiceInfo) const info.ReadFrom(*response->mQuery); + switch (info.mQueryType) + { + case kIp6AddressQuery: +#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE + case kIp4AddressQuery: +#endif + IgnoreError(response->FindHostAddress(kAnswerSection, name, /* aIndex */ 0, + AsCoreType(&aServiceInfo.mHostAddress), + aServiceInfo.mHostAddressTtl)); + + continue; // to `for()` loop + + case kServiceQuerySrvTxt: + case kServiceQuerySrv: + case kServiceQueryTxt: + break; + + default: + continue; + } + // Determine from which section we should try to read the SRV and // TXT records based on the query type. // @@ -639,7 +662,25 @@ Error Client::ServiceResponse::GetHostAddress(const char *aHostName, for (const Response *response = this; response != nullptr; response = response->mNext) { - error = response->FindHostAddress(kAdditionalDataSection, Name(aHostName), aIndex, aAddress, aTtl); + Section section = kAdditionalDataSection; + QueryInfo info; + + info.ReadFrom(*response->mQuery); + + switch (info.mQueryType) + { + case kIp6AddressQuery: +#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE + case kIp4AddressQuery: +#endif + section = kAnswerSection; + break; + + default: + break; + } + + error = response->FindHostAddress(section, Name(aHostName), aIndex, aAddress, aTtl); if (error == kErrorNone) { @@ -863,6 +904,25 @@ Error Client::ResolveService(const char *aInstanceLabel, ServiceCallback aCallback, void *aContext, const QueryConfig *aConfig) +{ + return Resolve(aInstanceLabel, aServiceName, aCallback, aContext, aConfig, false); +} + +Error Client::ResolveServiceAndHostAddress(const char *aInstanceLabel, + const char *aServiceName, + ServiceCallback aCallback, + void *aContext, + const QueryConfig *aConfig) +{ + return Resolve(aInstanceLabel, aServiceName, aCallback, aContext, aConfig, true); +} + +Error Client::Resolve(const char *aInstanceLabel, + const char *aServiceName, + ServiceCallback aCallback, + void *aContext, + const QueryConfig *aConfig, + bool aShouldResolveHostAddr) { QueryInfo info; Error error; @@ -873,6 +933,7 @@ Error Client::ResolveService(const char *aInstanceLabel, info.Clear(); info.mConfig.SetFrom(aConfig, mDefaultConfig); + info.mShouldResolveHostAddr = aShouldResolveHostAddr; switch (info.mConfig.GetServiceMode()) { @@ -887,6 +948,7 @@ Error Client::ResolveService(const char *aInstanceLabel, case QueryConfig::kServiceModeTxt: info.mQueryType = kServiceQueryTxt; + VerifyOrExit(!info.mShouldResolveHostAddr, error = kErrorInvalidArgs); break; case QueryConfig::kServiceModeSrvTxt: @@ -1274,6 +1336,10 @@ void Client::ProcessResponse(const Message &aResponseMessage) // Received successful response from server. +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + ResolveHostAddressIfNeeded(*query, aResponseMessage); +#endif + if (!CanFinalizeQuery(*query)) { SaveQueryResponse(*query, aResponseMessage); @@ -1551,6 +1617,51 @@ exit: return error; } +void Client::ResolveHostAddressIfNeeded(Query &aQuery, const Message &aResponseMessage) +{ + QueryInfo info; + Response response; + ServiceInfo serviceInfo; + char hostName[Name::kMaxNameSize]; + + info.ReadFrom(aQuery); + + VerifyOrExit(info.mQueryType == kServiceQuerySrvTxt || info.mQueryType == kServiceQuerySrv); + VerifyOrExit(info.mShouldResolveHostAddr); + + PopulateResponse(response, aQuery, aResponseMessage); + + memset(&serviceInfo, 0, sizeof(serviceInfo)); + serviceInfo.mHostNameBuffer = hostName; + serviceInfo.mHostNameBufferSize = sizeof(hostName); + SuccessOrExit(response.ReadServiceInfo(Response::kAnswerSection, Name(aQuery, kNameOffsetInQuery), serviceInfo)); + + // Check whether AAAA record for host address is provided in the SRV query response + + if (AsCoreType(&serviceInfo.mHostAddress).IsUnspecified()) + { + Query *newQuery; + + info.mQueryType = kIp6AddressQuery; + info.mMessageId = 0; + info.mTransmissionCount = 0; + info.mMainQuery = &FindMainQuery(aQuery); + + SuccessOrExit(AllocateQuery(info, nullptr, hostName, newQuery)); + IgnoreError(SendQuery(*newQuery, info, /* aUpdateTimer */ true)); + + // Update `aQuery` to be linked with new query (inserting + // the `newQuery` into the linked-list after `aQuery`). + + info.ReadFrom(aQuery); + info.mNextQuery = newQuery; + UpdateQuery(aQuery, info); + } + +exit: + return; +} + #endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE #if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE diff --git a/src/core/net/dns_client.hpp b/src/core/net/dns_client.hpp index 12c63b9a9..d8b7f1397 100644 --- a/src/core/net/dns_client.hpp +++ b/src/core/net/dns_client.hpp @@ -718,7 +718,7 @@ public: const QueryConfig *aConfig = nullptr); /** - * This method sends a DNS service instance resolution query for a given service instance. + * This method starts a DNS service instance resolution for a given service instance. * * The @p aConfig can be `nullptr`. In this case the default config (from `GetDefaultConfig()`) will be used as * the config for this query. In a non-`nullptr` @p aConfig, some of the fields can be left unspecified (value @@ -741,6 +741,32 @@ public: void *aContext, const QueryConfig *aConfig = nullptr); + /** + * This method starts a DNS service instance resolution for a given service instance, with a potential follow-up + * host name resolution (if the server/resolver does not provide AAAA/A records for the host name in the response + * to SRV query). + * + * The @p aConfig can be `nullptr`. In this case the default config (from `GetDefaultConfig()`) will be used as + * the config for this query. In a non-`nullptr` @p aConfig, some of the fields can be left unspecified (value + * zero). The unspecified fields are then replaced by the values from the default config. + * + * @param[in] aInstanceLabel The service instance label. + * @param[in] aServiceName The service name (together with @p aInstanceLabel form full instance name). + * @param[in] aCallback A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aConfig The config to use for this query. + * + * @retval kErrorNone Query sent successfully. @p aCallback will be invoked to report the status. + * @retval kErrorNoBufs Insufficient buffer to prepare and send query. + * @retval kErrorInvalidArgs @p aInstanceLabel is `nullptr` or the @p aConfig is invalid. + * + */ + Error ResolveServiceAndHostAddress(const char *aInstanceLabel, + const char *aServiceName, + ServiceCallback aCallback, + void *aContext, + const QueryConfig *aConfig = nullptr); + #endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE private: @@ -791,6 +817,7 @@ private: TimeMilli mRetransmissionTime; QueryConfig mConfig; uint8_t mTransmissionCount; + bool mShouldResolveHostAddr; Query *mMainQuery; Query *mNextQuery; Message *mSavedResponse; @@ -823,7 +850,14 @@ private: Error ReplaceWithIp4Query(Query &aQuery); #endif #if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + Error Resolve(const char *aInstanceLabel, + const char *aServiceName, + ServiceCallback aCallback, + void *aContext, + const QueryConfig *aConfig, + bool aShouldResolveHostAddr); Error ReplaceWithSeparateSrvTxtQueries(Query &aQuery); + void ResolveHostAddressIfNeeded(Query &aQuery, const Message &aResponseMessage); #endif #if OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE diff --git a/src/core/net/dnssd_server.cpp b/src/core/net/dnssd_server.cpp index 8f93096ae..478e4d8bf 100644 --- a/src/core/net/dnssd_server.cpp +++ b/src/core/net/dnssd_server.cpp @@ -204,13 +204,9 @@ void Server::ProcessQuery(const Header &aRequestHeader, Message &aRequestMessage VerifyOrExit(!aRequestHeader.IsTruncationFlagSet(), response = Header::kResponseFormatError); VerifyOrExit(aRequestHeader.GetQuestionCount() > 0, response = Header::kResponseFormatError); - switch (mTestMode) + if (mTestMode & kTestModeSingleQuestionOnly) { - case kTestModeDisabled: - break; - case kTestModeSingleQuestionOnly: VerifyOrExit(aRequestHeader.GetQuestionCount() == 1, response = Header::kResponseFormatError); - break; } response = AddQuestions(aRequestHeader, aRequestMessage, responseHeader, *responseMessage, compressInfo); @@ -729,6 +725,8 @@ Header::Response Server::ResolveBySrp(Header &aResponseHeader, // Answer the questions with additional RRs if required if (aResponseHeader.GetAnswerCount() > 0) { + VerifyOrExit(!(mTestMode & kTestModeEmptyAdditionalSection)); + readOffset = sizeof(Header); for (uint16_t i = 0; i < aResponseHeader.GetQuestionCount(); i++) { @@ -1046,6 +1044,11 @@ void Server::AnswerQuery(QueryTransaction &aQuery, for (uint8_t additional = 0; additional <= 1; additional++) { + if (additional == 1) + { + VerifyOrExit(!(mTestMode & kTestModeEmptyAdditionalSection)); + } + if (HasQuestion(aQuery.GetResponseHeader(), aQuery.GetResponseMessage(), aInstanceInfo.mFullName, ResourceRecord::kTypeSrv) == !additional) { diff --git a/src/core/net/dnssd_server.hpp b/src/core/net/dnssd_server.hpp index bebcf41c5..cf708f5f2 100644 --- a/src/core/net/dnssd_server.hpp +++ b/src/core/net/dnssd_server.hpp @@ -263,25 +263,27 @@ public: const Counters &GetCounters(void) const { return mCounters; }; /** - * This enumeration represents different test modes. - * - * The test mode is intended for testing the client by having server behave in certain ways, e.g., reject messages - * with certain format (e.g., more than one question in query). + * This enumeration represents different test mode flags for use in `SetTestMode()`. * */ - enum TestMode : uint8_t + enum TestModeFlags : uint8_t { - kTestModeDisabled, ///< Test mode is disabled. - kTestModeSingleQuestionOnly, ///< Allow single question in query message, send `FormatError` for two or more. + kTestModeSingleQuestionOnly = 1 << 0, ///< Allow single question in query, send `FormatError` otherwise. + kTestModeEmptyAdditionalSection = 1 << 1, ///< Do not include any RR in additional section. }; + static constexpr uint8_t kTestModeDisabled = 0; ///< Test mode is disabled (no flags). + /** * This method sets the test mode for `Server`. * - * @param[in] aTestMode The new test mode. + * The test mode flags are intended for testing the client by having server behave in certain ways, e.g., reject + * messages with certain format (e.g., more than one question in query). + * + * @param[in] aTestMode The new test mode (combination of `TestModeFlags`). * */ - void SetTestMode(TestMode aTestMode) { mTestMode = aTestMode; } + void SetTestMode(uint8_t aTestMode) { mTestMode = aTestMode; } private: class NameCompressInfo : public Clearable @@ -553,7 +555,7 @@ private: ServerTimer mTimer; Counters mCounters; - TestMode mTestMode; + uint8_t mTestMode; }; } // namespace ServiceDiscovery diff --git a/tests/unit/test_dns_client.cpp b/tests/unit/test_dns_client.cpp index 1b27a5904..88f56299c 100644 --- a/tests/unit/test_dns_client.cpp +++ b/tests/unit/test_dns_client.cpp @@ -392,6 +392,9 @@ exit: //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static constexpr uint8_t kMaxHostAddresses = 10; +static constexpr uint16_t kMaxTxtBuffer = 256; + struct ResolveServiceInfo { void Reset(void) @@ -407,7 +410,9 @@ struct ResolveServiceInfo Error mError; Dns::Client::ServiceInfo mInfo; char mNameBuffer[Dns::Name::kMaxNameSize]; - uint8_t mTxtBuffer[256]; + uint8_t mTxtBuffer[kMaxTxtBuffer]; + Ip6::Address mHostAddresses[kMaxHostAddresses]; + uint8_t mNumHostAddresses; }; static ResolveServiceInfo sResolveServiceInfo; @@ -432,7 +437,31 @@ void ServiceCallback(otError aError, const otDnsServiceResponse *aResponse, void SuccessOrExit(aError); SuccessOrQuit(response.GetServiceInfo(sResolveServiceInfo.mInfo)); + + for (uint8_t index = 0; index < kMaxHostAddresses; index++) + { + Error error; + uint32_t ttl; + + error = response.GetHostAddress(sResolveServiceInfo.mInfo.mHostNameBuffer, index, + sResolveServiceInfo.mHostAddresses[index], ttl); + + if (error == kErrorNotFound) + { + sResolveServiceInfo.mNumHostAddresses = index; + break; + } + + SuccessOrQuit(error); + } + LogServiceInfo(sResolveServiceInfo.mInfo); + Log(" NumHostAddresses: %u", sResolveServiceInfo.mNumHostAddresses); + + for (uint8_t index = 0; index < sResolveServiceInfo.mNumHostAddresses; index++) + { + Log(" %s", sResolveServiceInfo.mHostAddresses[index].ToString().AsCString()); + } exit: return; @@ -442,6 +471,10 @@ exit: void TestDnsClient(void) { + static constexpr uint8_t kNumAddresses = 2; + + static const char *const kAddresses[kNumAddresses] = {"2001::beef:cafe", "fd00:1234:5678:9abc::1"}; + const Dns::Client::QueryConfig::ServiceMode kServiceModes[] = { Dns::Client::QueryConfig::kServiceModeSrv, Dns::Client::QueryConfig::kServiceModeTxt, @@ -450,20 +483,36 @@ void TestDnsClient(void) Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize, }; - Srp::Server *srpServer; - Srp::Client *srpClient; - Srp::Client::Service service1; - Srp::Client::Service service2; - Dns::Client *dnsClient; - Dns::Client::QueryConfig queryConfig; - Dns::ServiceDiscovery::Server *dnsServer; - uint16_t heapAllocations; + Array addresses; + Srp::Server *srpServer; + Srp::Client *srpClient; + Srp::Client::Service service1; + Srp::Client::Service service2; + Dns::Client *dnsClient; + Dns::Client::QueryConfig queryConfig; + Dns::ServiceDiscovery::Server *dnsServer; + uint16_t heapAllocations; Log("--------------------------------------------------------------------------------------------"); Log("TestDnsClient"); InitTest(); + for (const char *addrString : kAddresses) + { + otNetifAddress netifAddr; + + memset(&netifAddr, 0, sizeof(netifAddr)); + SuccessOrQuit(AsCoreType(&netifAddr.mAddress).FromString(addrString)); + netifAddr.mPrefixLength = 64; + netifAddr.mAddressOrigin = OT_ADDRESS_ORIGIN_MANUAL; + netifAddr.mPreferred = true; + netifAddr.mValid = true; + SuccessOrQuit(otIp6AddUnicastAddress(sInstance, &netifAddr)); + + SuccessOrQuit(addresses.PushBack(AsCoreType(&netifAddr.mAddress))); + } + srpServer = &sInstance->Get(); srpClient = &sInstance->Get(); dnsClient = &sInstance->Get(); @@ -580,6 +629,14 @@ void TestDnsClient(void) VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == service1.mPort); VerifyOrQuit(sResolveServiceInfo.mInfo.mWeight == service1.mWeight); VerifyOrQuit(strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, kHostFullName) == 0); + + VerifyOrQuit(sResolveServiceInfo.mNumHostAddresses == kNumAddresses); + VerifyOrQuit(AsCoreType(&sResolveServiceInfo.mInfo.mHostAddress) == sResolveServiceInfo.mHostAddresses[0]); + + for (uint8_t index = 0; index < kNumAddresses; index++) + { + VerifyOrQuit(addresses.Contains(sResolveServiceInfo.mHostAddresses[index])); + } } if (mode != Dns::Client::QueryConfig::kServiceModeSrv) @@ -624,16 +681,179 @@ void TestDnsClient(void) VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); VerifyOrQuit(sResolveServiceInfo.mError != kErrorNone); + dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeDisabled); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Validate DNS Client `ResolveService()` using all service modes + // when sever does not provide any RR in the addition data section. + + for (Dns::Client::QueryConfig::ServiceMode mode : kServiceModes) + { + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Set TestMode on server to not include any RR in additional section"); + dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeEmptyAdditionalSection); + Log("ResolveService(%s,%s) with ServiceMode: %s", kInstance1Label, kService1FullName, + ServiceModeToString(mode)); + + queryConfig.Clear(); + queryConfig.mServiceMode = static_cast(mode); + + sResolveServiceInfo.Reset(); + SuccessOrQuit( + dnsClient->ResolveService(kInstance1Label, kService1FullName, ServiceCallback, sInstance, &queryConfig)); + AdvanceTime(100); + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); + SuccessOrQuit(sResolveServiceInfo.mError); + + if (mode != Dns::Client::QueryConfig::kServiceModeTxt) + { + VerifyOrQuit(sResolveServiceInfo.mInfo.mTtl != 0); + VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == service1.mPort); + VerifyOrQuit(sResolveServiceInfo.mInfo.mWeight == service1.mWeight); + VerifyOrQuit(strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, kHostFullName) == 0); + } + + if (mode != Dns::Client::QueryConfig::kServiceModeSrv) + { + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataTtl != 0); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataSize != 0); + } + + // Since server is using `kTestModeEmptyAdditionalSection`, there + // should be no AAAA records for host address. + + VerifyOrQuit(AsCoreType(&sResolveServiceInfo.mInfo.mHostAddress).IsUnspecified()); + VerifyOrQuit(sResolveServiceInfo.mNumHostAddresses == 0); + } + + dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeDisabled); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Validate DNS Client `ResolveServiceAndHostAddress()` using all service modes + // with different TestMode configs on server: + // - Normal behavior when server provides AAAA records for host in + // additional section. + // - Server provides no records in additional section. We validate that + // client will send separate query to resolve host address. + + for (Dns::Client::QueryConfig::ServiceMode mode : kServiceModes) + { + for (uint8_t testIter = 0; testIter <= 1; testIter++) + { + Error error; + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + if (testIter == 1) + { + Log("Set TestMode on server to not include any RR in additional section"); + dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeEmptyAdditionalSection); + } + else + { + dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeDisabled); + } + + Log("ResolveServiceAndHostAddress(%s,%s) with ServiceMode: %s", kInstance1Label, kService1FullName, + ServiceModeToString(mode)); + + queryConfig.Clear(); + queryConfig.mServiceMode = static_cast(mode); + + sResolveServiceInfo.Reset(); + error = dnsClient->ResolveServiceAndHostAddress(kInstance1Label, kService1FullName, ServiceCallback, + sInstance, &queryConfig); + + if (mode == Dns::Client::QueryConfig::kServiceModeTxt) + { + Log("ResolveServiceAndHostAddress() with ServiceMode: %s failed correctly", ServiceModeToString(mode)); + VerifyOrQuit(error == kErrorInvalidArgs); + continue; + } + + SuccessOrQuit(error); + + AdvanceTime(100); + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); + SuccessOrQuit(sResolveServiceInfo.mError); + + if (mode != Dns::Client::QueryConfig::kServiceModeTxt) + { + VerifyOrQuit(sResolveServiceInfo.mInfo.mTtl != 0); + VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == service1.mPort); + VerifyOrQuit(sResolveServiceInfo.mInfo.mWeight == service1.mWeight); + VerifyOrQuit(strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, kHostFullName) == 0); + + VerifyOrQuit(sResolveServiceInfo.mNumHostAddresses == kNumAddresses); + VerifyOrQuit(AsCoreType(&sResolveServiceInfo.mInfo.mHostAddress) == + sResolveServiceInfo.mHostAddresses[0]); + + for (uint8_t index = 0; index < kNumAddresses; index++) + { + VerifyOrQuit(addresses.Contains(sResolveServiceInfo.mHostAddresses[index])); + } + } + + if (mode != Dns::Client::QueryConfig::kServiceModeSrv) + { + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataTtl != 0); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataSize != 0); + } + } + } + + dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeDisabled); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Set TestMode on server to not include any RR in additional section AND to only accept single question"); + dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeEmptyAdditionalSection + + Dns::ServiceDiscovery::Server::kTestModeSingleQuestionOnly); + + Log("ResolveServiceAndHostAddress(%s,%s) with ServiceMode: %s", kInstance1Label, kService1FullName, + ServiceModeToString(Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize)); + + queryConfig.Clear(); + queryConfig.mServiceMode = static_cast(Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize); + + sResolveServiceInfo.Reset(); + SuccessOrQuit(dnsClient->ResolveServiceAndHostAddress(kInstance1Label, kService1FullName, ServiceCallback, + sInstance, &queryConfig)); + + AdvanceTime(100); + + VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); + SuccessOrQuit(sResolveServiceInfo.mError); + + VerifyOrQuit(sResolveServiceInfo.mInfo.mTtl != 0); + VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == service1.mPort); + VerifyOrQuit(sResolveServiceInfo.mInfo.mWeight == service1.mWeight); + VerifyOrQuit(strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, kHostFullName) == 0); + + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataTtl != 0); + VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataSize != 0); + + VerifyOrQuit(sResolveServiceInfo.mNumHostAddresses == kNumAddresses); + VerifyOrQuit(AsCoreType(&sResolveServiceInfo.mInfo.mHostAddress) == sResolveServiceInfo.mHostAddresses[0]); + + for (uint8_t index = 0; index < kNumAddresses; index++) + { + VerifyOrQuit(addresses.Contains(sResolveServiceInfo.mHostAddresses[index])); + } + + dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeDisabled); + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); Log("Stop DNS-SD server"); dnsServer->Stop(); Log("ResolveService(%s,%s) with ServiceMode %s", kInstance1Label, kService1FullName, - ServiceModeToString(Dns::Client::QueryConfig::kServiceModeSrv)); + ServiceModeToString(Dns::Client::QueryConfig::kServiceModeSrvTxtSeparate)); queryConfig.Clear(); - queryConfig.mServiceMode = static_cast(Dns::Client::QueryConfig::kServiceModeSrv); + queryConfig.mServiceMode = static_cast(Dns::Client::QueryConfig::kServiceModeSrvTxtSeparate); sResolveServiceInfo.Reset(); SuccessOrQuit(