From 05d8e81988039fd5da053aa91be93b6242915862 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 8 Mar 2022 17:18:44 -0800 Subject: [PATCH] [dns-client] add support address resolution query for IPv4 directly (#7441) This commit adds a new mechanism in `Dns::Client` along with a new public OT API `otDnsClientResolveIp4Address()` and its related CLI sub-command to allow a user to request an address resolution DNS query specifically for A records or IPv4 addresses for a given host name. This API requires `OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE` to be enabled. Upon receiving a successful query response the addresses are reported to user (in callback) as NAT64 IPv6 translated version of the IPv4 addresses from the received query response. The new API is added in addition to the existing behavior where address resolution query from `otDnsClientResolveAddress()` first queries for IPv6 addresses and only if no IPv6 address is found and NAT64 is enabled and allowed by the query config, `Dns::Client` implementation tries to perform an IPv4 query. The new API directly starts with IPv4 query and can be useful in situations where Thread devices are connected through BRs to a network without global IPv6 connectivity. --- include/openthread/dns_client.h | 39 ++++++++++++-- include/openthread/instance.h | 2 +- src/cli/cli.cpp | 10 ++++ src/core/api/dns_api.cpp | 12 +++++ src/core/net/dns_client.cpp | 69 ++++++++++++------------- src/core/net/dns_client.hpp | 35 +++++++++++-- src/core/thread/network_data_leader.cpp | 23 +++++++++ src/core/thread/network_data_leader.hpp | 14 +++++ tests/scripts/expect/tun-dns-client.exp | 10 ++++ 9 files changed, 171 insertions(+), 43 deletions(-) diff --git a/include/openthread/dns_client.h b/include/openthread/dns_client.h index e212062ad..17817c147 100644 --- a/include/openthread/dns_client.h +++ b/include/openthread/dns_client.h @@ -199,6 +199,8 @@ typedef void (*otDnsAddressCallback)(otError aError, const otDnsAddressResponse * * @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 The host name is not valid format. + * @retval OT_ERROR_INVALID_STATE Cannot send query since Thread interface is not up. * */ otError otDnsClientResolveAddress(otInstance * aInstance, @@ -207,6 +209,36 @@ otError otDnsClientResolveAddress(otInstance * aInstance, void * aContext, const otDnsQueryConfig *aConfig); +/** + * This function sends an address resolution DNS query for A (IPv4) record(s) for a given host name. + * + * This function requires and is available when `OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE` is enabled. + * + * When a successful response is received, the addresses are returned from @p aCallback as NAT64 IPv6 translated + * versions of the IPv4 addresses from the query response. + * + * 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. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aHostName The host name for which to query the address (MUST NOT be NULL). + * @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 The host name is not valid format or NAT64 is not enabled in config. + * @retval OT_ERROR_INVALID_STATE Cannot send query since Thread interface is not up. + * + */ +otError otDnsClientResolveIp4Address(otInstance * aInstance, + const char * aHostName, + otDnsAddressCallback aCallback, + void * aContext, + const otDnsQueryConfig *aConfig); + /** * This function gets the full host name associated with an address resolution DNS response. * @@ -239,9 +271,10 @@ otError otDnsAddressResponseGetHostName(const otDnsAddressResponse *aResponse, * @param[out] aTtl A pointer to an `uint32_t` to output TTL for the address. It can be NULL if caller does not * want to get the TTL. * - * @retval OT_ERROR_NONE The address was read successfully. - * @retval OT_ERROR_NOT_FOUND No address record in @p aResponse at @p aIndex. - * @retval OT_ERROR_PARSE Could not parse the records in the @p aResponse. + * @retval OT_ERROR_NONE The address was read successfully. + * @retval OT_ERROR_NOT_FOUND No address record in @p aResponse at @p aIndex. + * @retval OT_ERROR_PARSE Could not parse the records in the @p aResponse. + * @retval OT_ERROR_INVALID_STATE No NAT64 prefix (applicable only when NAT64 is allowed). * */ otError otDnsAddressResponseGetAddress(const otDnsAddressResponse *aResponse, diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 200e58482..352224bb3 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 (195) +#define OPENTHREAD_API_VERSION (196) /** * @addtogroup api-instance diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index b5394f948..988836650 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -1544,6 +1544,16 @@ template <> otError Interpreter::Process(Arg aArgs[]) &Interpreter::HandleDnsAddressResponse, this, config)); error = OT_ERROR_PENDING; } +#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE + else if (aArgs[0] == "resolve4") + { + VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + SuccessOrExit(error = GetDnsConfig(aArgs + 2, config)); + SuccessOrExit(error = otDnsClientResolveIp4Address(GetInstancePtr(), aArgs[1].GetCString(), + &Interpreter::HandleDnsAddressResponse, this, config)); + error = OT_ERROR_PENDING; + } +#endif #if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE else if (aArgs[0] == "browse") { diff --git a/src/core/api/dns_api.cpp b/src/core/api/dns_api.cpp index ed3bfcf55..d9bdda700 100644 --- a/src/core/api/dns_api.cpp +++ b/src/core/api/dns_api.cpp @@ -91,6 +91,18 @@ otError otDnsClientResolveAddress(otInstance * aInstance, AsCoreTypePtr(aConfig)); } +#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE +otError otDnsClientResolveIp4Address(otInstance * aInstance, + const char * aHostName, + otDnsAddressCallback aCallback, + void * aContext, + const otDnsQueryConfig *aConfig) +{ + return AsCoreType(aInstance).Get().ResolveIp4Address(aHostName, aCallback, aContext, + AsCoreTypePtr(aConfig)); +} +#endif + otError otDnsAddressResponseGetHostName(const otDnsAddressResponse *aResponse, char * aNameBuffer, uint16_t aNameBufferSize) diff --git a/src/core/net/dns_client.cpp b/src/core/net/dns_client.cpp index 136ae118a..a26c0a34e 100644 --- a/src/core/net/dns_client.cpp +++ b/src/core/net/dns_client.cpp @@ -328,16 +328,17 @@ Error Client::AddressResponse::GetAddress(uint16_t aIndex, Ip6::Address &aAddres if ((info.mQueryType == kIp4AddressQuery) || mIp6QueryResponseRequiresNat64) { - Section section; - ARecord aRecord; - Ip6::Prefix nat64Prefix; + Section section; + ARecord aRecord; + NetworkData::ExternalRouteConfig nat64Prefix; - SuccessOrExit(error = GetNat64Prefix(nat64Prefix)); + VerifyOrExit(mInstance->Get().GetPreferredNat64Prefix(nat64Prefix) == kErrorNone, + error = kErrorInvalidState); section = (info.mQueryType == kIp4AddressQuery) ? kAnswerSection : kAdditionalDataSection; SuccessOrExit(error = FindARecord(section, name, aIndex, aRecord)); - aAddress.SynthesizeFromIp4Address(nat64Prefix, aRecord.GetAddress()); + aAddress.SynthesizeFromIp4Address(nat64Prefix.GetPrefix(), aRecord.GetAddress()); aTtl = aRecord.GetTtl(); ExitNow(); @@ -351,37 +352,6 @@ exit: return error; } -#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE - -Error Client::AddressResponse::GetNat64Prefix(Ip6::Prefix &aPrefix) const -{ - Error error = kErrorNotFound; - NetworkData::Iterator iterator = NetworkData::kIteratorInit; - signed int preference = NetworkData::kRoutePreferenceLow; - NetworkData::ExternalRouteConfig config; - - aPrefix.Clear(); - - while (mInstance->Get().GetNextExternalRoute(iterator, config) == kErrorNone) - { - if (!config.mNat64 || !config.GetPrefix().IsValidNat64()) - { - continue; - } - - if ((aPrefix.GetLength() == 0) || (config.mPreference > preference)) - { - aPrefix = config.GetPrefix(); - preference = config.mPreference; - error = kErrorNone; - } - } - - return error; -} - -#endif // OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE - #if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE //--------------------------------------------------------------------------------------------------------------------- @@ -651,6 +621,22 @@ Error Client::ResolveAddress(const char * aHostName, return StartQuery(info, aConfig, nullptr, aHostName, aContext); } +#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE +Error Client::ResolveIp4Address(const char * aHostName, + AddressCallback aCallback, + void * aContext, + const QueryConfig *aConfig) +{ + QueryInfo info; + + info.Clear(); + info.mQueryType = kIp4AddressQuery; + info.mCallback.mAddressCallback = aCallback; + + return StartQuery(info, aConfig, nullptr, aHostName, aContext); +} +#endif + #if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE Error Client::Browse(const char *aServiceName, BrowseCallback aCallback, void *aContext, const QueryConfig *aConfig) @@ -718,6 +704,17 @@ Error Client::StartQuery(QueryInfo & aInfo, aInfo.mCallbackContext = aContext; +#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE + if (aInfo.mQueryType == kIp4AddressQuery) + { + NetworkData::ExternalRouteConfig nat64Prefix; + + VerifyOrExit(aInfo.mConfig.GetNat64Mode() == QueryConfig::kNat64Allow, error = kErrorInvalidArgs); + VerifyOrExit(Get().GetPreferredNat64Prefix(nat64Prefix) == kErrorNone, + error = kErrorInvalidState); + } +#endif + SuccessOrExit(error = AllocateQuery(aInfo, aLabel, aName, query)); mQueries.Enqueue(*query); diff --git a/src/core/net/dns_client.hpp b/src/core/net/dns_client.hpp index f7d927f46..85721030f 100644 --- a/src/core/net/dns_client.hpp +++ b/src/core/net/dns_client.hpp @@ -331,9 +331,10 @@ public: * @param[out] aAddress A reference to an IPv6 address to output the address. * @param[out] aTtl A reference to a `uint32_t` to output TTL for the address. * - * @retval kErrorNone The address was read successfully. - * @retval kErrorNotFound No address record at @p aIndex. - * @retval kErrorParse Could not parse the records. + * @retval kErrorNone The address was read successfully. + * @retval kErrorNotFound No address record at @p aIndex. + * @retval kErrorParse Could not parse the records. + * @retval kErrorInvalidState No NAT64 prefix (applicable only when NAT64 is allowed). * */ Error GetAddress(uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const; @@ -609,6 +610,34 @@ public: void * aContext, const QueryConfig *aConfig = nullptr); +#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE + /** + * This method sends an address resolution DNS query for A (IPv4) record for a given host name. + * + * When a successful response is received, the addresses are returned from @p aCallback as NAT64 IPv6 translated + * versions of the IPv4 addresses from the query response. + * + * 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] aHostName The host name for which to query the address (MUST NOT be `nullptr`). + * @param[in] aCallback A callback function pointer to report the result of query. + * @param[in] aContext A pointer to arbitrary context information passed to @p aCallback. + * @param[in] aConfig The config to use for this query. + * + * @retval kErrorNone Successfully sent DNS query. + * @retval kErrorNoBufs Failed to allocate retransmission data. + * @retval kErrorInvalidArgs The host name is not valid format or NAT64 is not enabled in config. + * @retval kErrorInvalidState Cannot send query since Thread interface is not up, or there is no NAT64 prefix. + * + */ + Error ResolveIp4Address(const char * aHostName, + AddressCallback aCallback, + void * aContext, + const QueryConfig *aConfig = nullptr); +#endif + #if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE /** diff --git a/src/core/thread/network_data_leader.cpp b/src/core/thread/network_data_leader.cpp index 80c358cf9..8c2884201 100644 --- a/src/core/thread/network_data_leader.cpp +++ b/src/core/thread/network_data_leader.cpp @@ -89,6 +89,29 @@ exit: return error; } +Error LeaderBase::GetPreferredNat64Prefix(ExternalRouteConfig &aConfig) const +{ + Error error = kErrorNotFound; + Iterator iterator = kIteratorInit; + ExternalRouteConfig config; + + while (GetNextExternalRoute(iterator, config) == kErrorNone) + { + if (!config.mNat64 || !config.GetPrefix().IsValidNat64()) + { + continue; + } + + if ((error == kErrorNotFound) || (config.mPreference > aConfig.mPreference)) + { + aConfig = config; + error = kErrorNone; + } + } + + return error; +} + const PrefixTlv *LeaderBase::FindNextMatchingPrefix(const Ip6::Address &aAddress, const PrefixTlv *aPrevTlv) const { const PrefixTlv *prefixTlv; diff --git a/src/core/thread/network_data_leader.hpp b/src/core/thread/network_data_leader.hpp index b9a712417..54765ef73 100644 --- a/src/core/thread/network_data_leader.hpp +++ b/src/core/thread/network_data_leader.hpp @@ -267,6 +267,20 @@ public: bool aServerStable, uint8_t & aServiceId) const; + /** + * This methods gets the preferred NAT64 prefix from network data. + * + * The returned prefix is the highest preference external route entry in Network Data with NAT64 flag set. If there + * are multiple such entries the first one is returned. + * + * @param[out] aConfig A reference to an `ExternalRouteConfig` to return the prefix. + * + * @retval kErrorNone Found the NAT64 prefix and updated @p aConfig. + * @retval kErrorNotFound Could not find any NAT64 entry. + * + */ + Error GetPreferredNat64Prefix(ExternalRouteConfig &aConfig) const; + protected: uint8_t mStableVersion; uint8_t mVersion; diff --git a/tests/scripts/expect/tun-dns-client.exp b/tests/scripts/expect/tun-dns-client.exp index 36909ba57..3a2ac644d 100755 --- a/tests/scripts/expect/tun-dns-client.exp +++ b/tests/scripts/expect/tun-dns-client.exp @@ -40,8 +40,18 @@ expect_line "Done" wait_for "state" "leader" expect_line "Done" +send "route add 2001:dead:beef:cafe::/64 s n med\n" +expect_line "Done" + +send "netdata register\n" +expect_line "Done" + send "dns resolve ipv6.google.com ::1 53\n" expect "DNS response for ipv6.google.com" expect_line "Done" +send "dns resolve4 ipv6.google.com ::1 53\n" +expect "DNS response for ipv6.google.com" +expect_line "Done" + dispose_all