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