diff --git a/src/cli/BUILD.gn b/src/cli/BUILD.gn index 86e06af76..01be88e16 100644 --- a/src/cli/BUILD.gn +++ b/src/cli/BUILD.gn @@ -41,6 +41,8 @@ openthread_cli_sources = [ "cli_config.h", "cli_dataset.cpp", "cli_dataset.hpp", + "cli_dns.cpp", + "cli_dns.hpp", "cli_history.cpp", "cli_history.hpp", "cli_joiner.cpp", diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index d01dc8934..2768689b8 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -38,6 +38,7 @@ set(COMMON_SOURCES cli_coap_secure.cpp cli_commissioner.cpp cli_dataset.cpp + cli_dns.cpp cli_history.cpp cli_joiner.cpp cli_network_data.cpp diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am index 6df2c08e1..e5d006288 100644 --- a/src/cli/Makefile.am +++ b/src/cli/Makefile.am @@ -158,6 +158,7 @@ SOURCES_COMMON = \ cli_coap_secure.cpp \ cli_commissioner.cpp \ cli_dataset.cpp \ + cli_dns.cpp \ cli_history.cpp \ cli_joiner.cpp \ cli_network_data.cpp \ @@ -189,6 +190,7 @@ noinst_HEADERS = \ cli_commissioner.hpp \ cli_config.h \ cli_dataset.hpp \ + cli_dns.hpp \ cli_history.hpp \ cli_joiner.hpp \ cli_network_data.hpp \ diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index a3a3b05c5..7c384a820 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -109,6 +109,9 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi , mDataset(aInstance, *this) , mNetworkData(aInstance, *this) , mUdp(aInstance, *this) +#if OPENTHREAD_CLI_DNS_ENABLE + , mDns(aInstance, *this) +#endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE , mBr(aInstance, *this) #endif @@ -2950,643 +2953,10 @@ exit: return error; } -template <> otError Interpreter::Process(Arg aArgs[]) -{ - OT_UNUSED_VARIABLE(aArgs); - - otError error = OT_ERROR_NONE; -#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - otDnsQueryConfig queryConfig; - otDnsQueryConfig *config = &queryConfig; +#if OPENTHREAD_CLI_DNS_ENABLE +template <> otError Interpreter::Process(Arg aArgs[]) { return mDns.Process(aArgs); } #endif - if (aArgs[0].IsEmpty()) - { - error = OT_ERROR_INVALID_ARGS; - } - /** - * @cli dns compression - * @code - * dns compression - * Enabled - * @endcode - * @cparam dns compression [@ca{enable|disable}] - * @par api_copy - * #otDnsIsNameCompressionEnabled - * @par - * By default DNS name compression is enabled. When disabled, - * DNS names are appended as full and never compressed. This - * is applicable to OpenThread's DNS and SRP client/server - * modules." - * 'OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE' is required. - */ -#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE - else if (aArgs[0] == "compression") - { - /** - * @cli dns compression (enable,disable) - * @code - * dns compression enable - * Enabled - * @endcode - * @code - * dns compression disable - * Done - * dns compression - * Disabled - * Done - * @endcode - * @cparam dns compression [@ca{enable|disable}] - * @par - * Set the "DNS name compression" mode. - * @par - * By default DNS name compression is enabled. When disabled, - * DNS names are appended as full and never compressed. This - * is applicable to OpenThread's DNS and SRP client/server - * modules." - * 'OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE' is required. - * @sa otDnsSetNameCompressionEnabled - */ - if (aArgs[1].IsEmpty()) - { - OutputEnabledDisabledStatus(otDnsIsNameCompressionEnabled()); - } - else - { - bool enable; - - SuccessOrExit(error = ParseEnableOrDisable(aArgs[1], enable)); - otDnsSetNameCompressionEnabled(enable); - } - } -#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE -#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - - else if (aArgs[0] == "config") - { - /** - * @cli dns config - * @code - * dns config - * Server: [fd00:0:0:0:0:0:0:1]:1234 - * ResponseTimeout: 5000 ms - * MaxTxAttempts: 2 - * RecursionDesired: no - * ServiceMode: srv - * Nat64Mode: allow - * Done - * @endcode - * @par api_copy - * #otDnsClientGetDefaultConfig - * @par - * The config includes the server IPv6 address and port, response - * timeout in msec (wait time to rx response), maximum tx attempts - * before reporting failure, boolean flag to indicate whether the server - * can resolve the query recursively or not. - * 'OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE' is required. - */ - if (aArgs[1].IsEmpty()) - { - const otDnsQueryConfig *defaultConfig = otDnsClientGetDefaultConfig(GetInstancePtr()); - - OutputFormat("Server: "); - OutputSockAddrLine(defaultConfig->mServerSockAddr); - OutputLine("ResponseTimeout: %lu ms", ToUlong(defaultConfig->mResponseTimeout)); - OutputLine("MaxTxAttempts: %u", defaultConfig->mMaxTxAttempts); - OutputLine("RecursionDesired: %s", - (defaultConfig->mRecursionFlag == OT_DNS_FLAG_RECURSION_DESIRED) ? "yes" : "no"); - OutputLine("ServiceMode: %s", DnsConfigServiceModeToString(defaultConfig->mServiceMode)); -#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE - OutputLine("Nat64Mode: %s", (defaultConfig->mNat64Mode == OT_DNS_NAT64_ALLOW) ? "allow" : "disallow"); -#endif -#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE - OutputLine("TransportProtocol: %s", - (defaultConfig->mTransportProto == OT_DNS_TRANSPORT_UDP) ? "udp" : "tcp"); -#endif - } - /** - * @cli dns config (set) - * @code - * dns config fd00::1 1234 5000 2 0 - * Done - * @endcode - * @code - * dns config - * Server: [fd00:0:0:0:0:0:0:1]:1234 - * ResponseTimeout: 5000 ms - * MaxTxAttempts: 2 - * RecursionDesired: no - * Done - * @endcode - * @code - * dns config fd00::2 - * Done - * @endcode - * @code - * dns config - * Server: [fd00:0:0:0:0:0:0:2]:53 - * ResponseTimeout: 3000 ms - * MaxTxAttempts: 3 - * RecursionDesired: yes - * Done - * @endcode - * @par api_copy - * #otDnsClientSetDefaultConfig - * @cparam dns config [@ca{dns-server-IP}] [@ca{dns-server-port}] [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] [@ca{recursion-desired-boolean}] [@ca{service-mode}] - * @par - * We can leave some of the fields as unspecified (or use value zero). The - * unspecified fields are replaced by the corresponding OT config option - * definitions OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT to form the default - * query config. - * 'OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE' is required. - */ - else - { - SuccessOrExit(error = GetDnsConfig(aArgs + 1, config)); - otDnsClientSetDefaultConfig(GetInstancePtr(), config); - } - } - /** - * @cli dns resolve - * @code - * dns resolve ipv6.google.com - * DNS response for ipv6.google.com - 2a00:1450:401b:801:0:0:0:200e TTL: 300 - * @endcode - * @code - * dns resolve example.com 8.8.8.8 - * Synthesized IPv6 DNS server address: fdde:ad00:beef:2:0:0:808:808 - * DNS response for example.com. - fd4c:9574:3720:2:0:0:5db8:d822 TTL:20456 - * Done - * @endcode - * @cparam dns resolve @ca{hostname} [@ca{dns-server-IP}] [@ca{dns-server-port}] [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] [@ca{recursion-desired-boolean}] - * @par api_copy - * #otDnsClientResolveAddress - * @par - * Send DNS Query to obtain IPv6 address for given hostname. - * @par - * The parameters after hostname are optional. Any unspecified (or zero) value - * for these optional parameters is replaced by the value from the current default - * config (dns config). - * @par - * 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. - * @par - * Note: 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_ENABLE' is required. - */ - else if (aArgs[0] == "resolve") - { - VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); - SuccessOrExit(error = GetDnsConfig(aArgs + 2, config)); - SuccessOrExit(error = otDnsClientResolveAddress(GetInstancePtr(), aArgs[1].GetCString(), - &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 - /** - * @cli dns browse - * @code - * dns browse _service._udp.example.com - * DNS browse response for _service._udp.example.com. - * inst1 - * Port:1234, Priority:1, Weight:2, TTL:7200 - * Host:host.example.com. - * HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200 - * TXT:[a=6531, b=6c12] TTL:7300 - * instance2 - * Port:1234, Priority:1, Weight:2, TTL:7200 - * Host:host.example.com. - * HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200 - * TXT:[a=1234] TTL:7300 - * Done - * @endcode - * @code - * dns browse _airplay._tcp.default.service.arpa - * DNS browse response for _airplay._tcp.default.service.arpa. - * Mac mini - * Port:7000, Priority:0, Weight:0, TTL:10 - * Host:Mac-mini.default.service.arpa. - * HostAddress:fd97:739d:386a:1:1c2e:d83c:fcbe:9cf4 TTL:10 - * Done - * @endcode - * @cparam dns browse @ca{service-name} [@ca{dns-server-IP}] [@ca{dns-server-port}] [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] [@ca{recursion-desired-boolean}] - * @sa otDnsClientBrowse - * @par - * Send a browse (service instance enumeration) DNS query to get the list of services for - * given service-name - * @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. 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. - */ - else if (aArgs[0] == "browse") - { - VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); - SuccessOrExit(error = GetDnsConfig(aArgs + 2, config)); - SuccessOrExit(error = otDnsClientBrowse(GetInstancePtr(), aArgs[1].GetCString(), - &Interpreter::HandleDnsBrowseResponse, this, config)); - error = OT_ERROR_PENDING; - } - /** - * @cli dns service - * @cparam dns service @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 - * #otDnsClientResolveService - * @par - * Send a service instance resolution DNS query for a given service instance. - * 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] == "service") - { - VerifyOrExit(!aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); - SuccessOrExit(error = GetDnsConfig(aArgs + 3, config)); - SuccessOrExit(error = otDnsClientResolveService(GetInstancePtr(), aArgs[1].GetCString(), aArgs[2].GetCString(), - &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 - else if (aArgs[0] == "server") - { - if (aArgs[1].IsEmpty()) - { - error = OT_ERROR_INVALID_ARGS; - } -#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE - else if (aArgs[1] == "upstream") - { - /** - * @cli dns server upstream - * @code - * dns server upstream - * Enabled - * Done - * @endcode - * @par api_copy - * #otDnssdUpstreamQueryIsEnabled - */ - if (aArgs[2].IsEmpty()) - { - OutputEnabledDisabledStatus(otDnssdUpstreamQueryIsEnabled(GetInstancePtr())); - } - /** - * @cli dns server upstream {enable|disable} - * @code - * dns server upstream enable - * Done - * @endcode - * @cparam dns server upstream @ca{enable|disable} - * @par api_copy - * #otDnssdUpstreamQuerySetEnabled - */ - else - { - bool enable; - - SuccessOrExit(error = ParseEnableOrDisable(aArgs[2], enable)); - otDnssdUpstreamQuerySetEnabled(GetInstancePtr(), enable); - } - } -#endif // OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE - else - { - ExitNow(error = OT_ERROR_INVALID_COMMAND); - } - } -#endif // OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE - else - { - ExitNow(error = OT_ERROR_INVALID_COMMAND); - } - -exit: - return error; -} - -#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - -const char *Interpreter::DnsConfigServiceModeToString(otDnsServiceMode aMode) const -{ - static const char *const kServiceModeStrings[] = { - "unspec", // OT_DNS_SERVICE_MODE_UNSPECIFIED (0) - "srv", // OT_DNS_SERVICE_MODE_SRV (1) - "txt", // OT_DNS_SERVICE_MODE_TXT (2) - "srv_txt", // OT_DNS_SERVICE_MODE_SRV_TXT (3) - "srv_txt_sep", // OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE (4) - "srv_txt_opt", // OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE (5) - }; - - static_assert(OT_DNS_SERVICE_MODE_UNSPECIFIED == 0, "OT_DNS_SERVICE_MODE_UNSPECIFIED value is incorrect"); - static_assert(OT_DNS_SERVICE_MODE_SRV == 1, "OT_DNS_SERVICE_MODE_SRV value is incorrect"); - static_assert(OT_DNS_SERVICE_MODE_TXT == 2, "OT_DNS_SERVICE_MODE_TXT value is incorrect"); - static_assert(OT_DNS_SERVICE_MODE_SRV_TXT == 3, "OT_DNS_SERVICE_MODE_SRV_TXT value is incorrect"); - static_assert(OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE == 4, "OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE value is incorrect"); - static_assert(OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE == 5, "OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE value is incorrect"); - - return Stringify(aMode, kServiceModeStrings); -} - -otError Interpreter::ParseDnsServiceMode(const Arg &aArg, otDnsServiceMode &aMode) const -{ - otError error = OT_ERROR_NONE; - - if (aArg == "def") - { - aMode = OT_DNS_SERVICE_MODE_UNSPECIFIED; - } - else if (aArg == "srv") - { - aMode = OT_DNS_SERVICE_MODE_SRV; - } - else if (aArg == "txt") - { - aMode = OT_DNS_SERVICE_MODE_TXT; - } - else if (aArg == "srv_txt") - { - aMode = OT_DNS_SERVICE_MODE_SRV_TXT; - } - else if (aArg == "srv_txt_sep") - { - aMode = OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE; - } - else if (aArg == "srv_txt_opt") - { - aMode = OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE; - } - else - { - error = OT_ERROR_INVALID_ARGS; - } - - return error; -} - -otError Interpreter::GetDnsConfig(Arg aArgs[], otDnsQueryConfig *&aConfig) -{ - // This method gets the optional DNS config from `aArgs[]`. - // The format: `[server IP address] [server port] [timeout] - // [max tx attempt] [recursion desired] [service mode] - // [transport]` - - otError error = OT_ERROR_NONE; - bool recursionDesired; - bool nat64SynthesizedAddress; - - memset(aConfig, 0, sizeof(otDnsQueryConfig)); - - VerifyOrExit(!aArgs[0].IsEmpty(), aConfig = nullptr); - - SuccessOrExit(error = Interpreter::ParseToIp6Address(GetInstancePtr(), aArgs[0], aConfig->mServerSockAddr.mAddress, - nat64SynthesizedAddress)); - if (nat64SynthesizedAddress) - { - OutputFormat("Synthesized IPv6 DNS server address: "); - OutputIp6AddressLine(aConfig->mServerSockAddr.mAddress); - } - - VerifyOrExit(!aArgs[1].IsEmpty()); - SuccessOrExit(error = aArgs[1].ParseAsUint16(aConfig->mServerSockAddr.mPort)); - - VerifyOrExit(!aArgs[2].IsEmpty()); - SuccessOrExit(error = aArgs[2].ParseAsUint32(aConfig->mResponseTimeout)); - - VerifyOrExit(!aArgs[3].IsEmpty()); - SuccessOrExit(error = aArgs[3].ParseAsUint8(aConfig->mMaxTxAttempts)); - - VerifyOrExit(!aArgs[4].IsEmpty()); - SuccessOrExit(error = aArgs[4].ParseAsBool(recursionDesired)); - aConfig->mRecursionFlag = recursionDesired ? OT_DNS_FLAG_RECURSION_DESIRED : OT_DNS_FLAG_NO_RECURSION; - - VerifyOrExit(!aArgs[5].IsEmpty()); - SuccessOrExit(error = ParseDnsServiceMode(aArgs[5], aConfig->mServiceMode)); - - VerifyOrExit(!aArgs[6].IsEmpty()); - - if (aArgs[6] == "tcp") - { - aConfig->mTransportProto = OT_DNS_TRANSPORT_TCP; - } - else if (aArgs[6] == "udp") - { - aConfig->mTransportProto = OT_DNS_TRANSPORT_UDP; - } - else - { - error = OT_ERROR_INVALID_ARGS; - } - -exit: - return error; -} - -void Interpreter::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext) -{ - static_cast(aContext)->HandleDnsAddressResponse(aError, aResponse); -} - -void Interpreter::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse) -{ - char hostName[OT_DNS_MAX_NAME_SIZE]; - otIp6Address address; - uint32_t ttl; - - IgnoreError(otDnsAddressResponseGetHostName(aResponse, hostName, sizeof(hostName))); - - OutputFormat("DNS response for %s - ", hostName); - - if (aError == OT_ERROR_NONE) - { - uint16_t index = 0; - - while (otDnsAddressResponseGetAddress(aResponse, index, &address, &ttl) == OT_ERROR_NONE) - { - OutputIp6Address(address); - OutputFormat(" TTL:%lu ", ToUlong(ttl)); - index++; - } - } - - OutputNewLine(); - OutputResult(aError); -} - -#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE - -void Interpreter::OutputDnsServiceInfo(uint8_t aIndentSize, const otDnsServiceInfo &aServiceInfo) -{ - OutputLine(aIndentSize, "Port:%d, Priority:%d, Weight:%d, TTL:%lu", aServiceInfo.mPort, aServiceInfo.mPriority, - aServiceInfo.mWeight, ToUlong(aServiceInfo.mTtl)); - OutputLine(aIndentSize, "Host:%s", aServiceInfo.mHostNameBuffer); - OutputFormat(aIndentSize, "HostAddress:"); - OutputIp6Address(aServiceInfo.mHostAddress); - OutputLine(" TTL:%lu", ToUlong(aServiceInfo.mHostAddressTtl)); - OutputFormat(aIndentSize, "TXT:"); - - if (!aServiceInfo.mTxtDataTruncated) - { - OutputDnsTxtData(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize); - } - else - { - OutputFormat("["); - OutputBytes(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize); - OutputFormat("...]"); - } - - OutputLine(" TTL:%lu", ToUlong(aServiceInfo.mTxtDataTtl)); -} - -void Interpreter::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext) -{ - static_cast(aContext)->HandleDnsBrowseResponse(aError, aResponse); -} - -void Interpreter::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse) -{ - char name[OT_DNS_MAX_NAME_SIZE]; - char label[OT_DNS_MAX_LABEL_SIZE]; - uint8_t txtBuffer[kMaxTxtDataSize]; - otDnsServiceInfo serviceInfo; - - IgnoreError(otDnsBrowseResponseGetServiceName(aResponse, name, sizeof(name))); - - OutputLine("DNS browse response for %s", name); - - if (aError == OT_ERROR_NONE) - { - uint16_t index = 0; - - while (otDnsBrowseResponseGetServiceInstance(aResponse, index, label, sizeof(label)) == OT_ERROR_NONE) - { - OutputLine("%s", label); - index++; - - serviceInfo.mHostNameBuffer = name; - serviceInfo.mHostNameBufferSize = sizeof(name); - serviceInfo.mTxtData = txtBuffer; - serviceInfo.mTxtDataSize = sizeof(txtBuffer); - - if (otDnsBrowseResponseGetServiceInfo(aResponse, label, &serviceInfo) == OT_ERROR_NONE) - { - OutputDnsServiceInfo(kIndentSize, serviceInfo); - } - - OutputNewLine(); - } - } - - OutputResult(aError); -} - -void Interpreter::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext) -{ - static_cast(aContext)->HandleDnsServiceResponse(aError, aResponse); -} - -void Interpreter::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse) -{ - char name[OT_DNS_MAX_NAME_SIZE]; - char label[OT_DNS_MAX_LABEL_SIZE]; - uint8_t txtBuffer[kMaxTxtDataSize]; - otDnsServiceInfo serviceInfo; - - IgnoreError(otDnsServiceResponseGetServiceName(aResponse, label, sizeof(label), name, sizeof(name))); - - OutputLine("DNS service resolution response for %s for service %s", label, name); - - if (aError == OT_ERROR_NONE) - { - serviceInfo.mHostNameBuffer = name; - serviceInfo.mHostNameBufferSize = sizeof(name); - serviceInfo.mTxtData = txtBuffer; - serviceInfo.mTxtDataSize = sizeof(txtBuffer); - - if (otDnsServiceResponseGetServiceInfo(aResponse, &serviceInfo) == OT_ERROR_NONE) - { - OutputDnsServiceInfo(/* aIndentSize */ 0, serviceInfo); - OutputNewLine(); - } - } - - OutputResult(aError); -} - -#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE -#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - #if OPENTHREAD_FTD const char *EidCacheStateToString(otCacheEntryState aState) { @@ -8604,7 +7974,10 @@ otError Interpreter::ProcessCommand(Arg aArgs[]) #endif #if OPENTHREAD_FTD || OPENTHREAD_MTD CmdEntry("discover"), +#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE || OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE || \ + OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE CmdEntry("dns"), +#endif #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) CmdEntry("domainname"), #endif diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 6c6cbbd8a..c8a7849e0 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -61,6 +61,7 @@ #include "cli/cli_br.hpp" #include "cli/cli_commissioner.hpp" #include "cli/cli_dataset.hpp" +#include "cli/cli_dns.hpp" #include "cli/cli_history.hpp" #include "cli/cli_joiner.hpp" #include "cli/cli_network_data.hpp" @@ -107,6 +108,7 @@ class Interpreter : public OutputImplementer, public Output #if OPENTHREAD_FTD || OPENTHREAD_MTD friend class Br; friend class Commissioner; + friend class Dns; friend class Joiner; friend class NetworkData; friend class SrpClient; @@ -460,21 +462,6 @@ private: void OutputChildTableEntry(uint8_t aIndentSize, const otNetworkDiagChildEntry &aChildEntry); #endif -#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - otError GetDnsConfig(Arg aArgs[], otDnsQueryConfig *&aConfig); - static void HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext); - void HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse); - const char *DnsConfigServiceModeToString(otDnsServiceMode aMode) const; - otError ParseDnsServiceMode(const Arg &aArg, otDnsServiceMode &aMode) const; -#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE - void OutputDnsServiceInfo(uint8_t aIndentSize, const otDnsServiceInfo &aServiceInfo); - static void HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext); - void HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse); - static void HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext); - void HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse); -#endif -#endif - #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE static void HandleSntpResponse(void *aContext, uint64_t aTime, otError aResult); #endif @@ -557,6 +544,10 @@ private: NetworkData mNetworkData; UdpExample mUdp; +#if OPENTHREAD_CLI_DNS_ENABLE + Dns mDns; +#endif + #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE Br mBr; #endif diff --git a/src/cli/cli_dns.cpp b/src/cli/cli_dns.cpp new file mode 100644 index 000000000..c85cb2477 --- /dev/null +++ b/src/cli/cli_dns.cpp @@ -0,0 +1,762 @@ +/* + * Copyright (c) 2023, 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 CLI for DNS (client and server/resolver). + */ + +#include "cli_dns.hpp" + +#include "cli/cli.hpp" + +#if OPENTHREAD_CLI_DNS_ENABLE + +namespace ot { +namespace Cli { + +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + +template <> otError Dns::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + + /** + * @cli dns compression + * @code + * dns compression + * Enabled + * @endcode + * @cparam dns compression [@ca{enable|disable}] + * @par api_copy + * #otDnsIsNameCompressionEnabled + * @par + * By default DNS name compression is enabled. When disabled, + * DNS names are appended as full and never compressed. This + * is applicable to OpenThread's DNS and SRP client/server + * modules." + * `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is required. + */ + if (aArgs[0].IsEmpty()) + { + OutputEnabledDisabledStatus(otDnsIsNameCompressionEnabled()); + } + /** + * @cli dns compression (enable,disable) + * @code + * dns compression enable + * Enabled + * @endcode + * @code + * dns compression disable + * Done + * dns compression + * Disabled + * Done + * @endcode + * @cparam dns compression [@ca{enable|disable}] + * @par + * Set the "DNS name compression" mode. + * @par + * By default DNS name compression is enabled. When disabled, + * DNS names are appended as full and never compressed. This + * is applicable to OpenThread's DNS and SRP client/server + * modules." + * `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is required. + * @sa otDnsSetNameCompressionEnabled + */ + else + { + bool enable; + + SuccessOrExit(error = Interpreter::ParseEnableOrDisable(aArgs[0], enable)); + otDnsSetNameCompressionEnabled(enable); + } + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + +#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE + +template <> otError Dns::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + + /** + * @cli dns config + * @code + * dns config + * Server: [fd00:0:0:0:0:0:0:1]:1234 + * ResponseTimeout: 5000 ms + * MaxTxAttempts: 2 + * RecursionDesired: no + * ServiceMode: srv + * Nat64Mode: allow + * Done + * @endcode + * @par api_copy + * #otDnsClientGetDefaultConfig + * @par + * The config includes the server IPv6 address and port, response + * timeout in msec (wait time to rx response), maximum tx attempts + * before reporting failure, boolean flag to indicate whether the server + * can resolve the query recursively or not. + * `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` is required. + */ + if (aArgs[0].IsEmpty()) + { + const otDnsQueryConfig *defaultConfig = otDnsClientGetDefaultConfig(GetInstancePtr()); + + OutputFormat("Server: "); + OutputSockAddrLine(defaultConfig->mServerSockAddr); + OutputLine("ResponseTimeout: %lu ms", ToUlong(defaultConfig->mResponseTimeout)); + OutputLine("MaxTxAttempts: %u", defaultConfig->mMaxTxAttempts); + OutputLine("RecursionDesired: %s", + (defaultConfig->mRecursionFlag == OT_DNS_FLAG_RECURSION_DESIRED) ? "yes" : "no"); + OutputLine("ServiceMode: %s", DnsConfigServiceModeToString(defaultConfig->mServiceMode)); +#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE + OutputLine("Nat64Mode: %s", (defaultConfig->mNat64Mode == OT_DNS_NAT64_ALLOW) ? "allow" : "disallow"); +#endif +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + OutputLine("TransportProtocol: %s", (defaultConfig->mTransportProto == OT_DNS_TRANSPORT_UDP) ? "udp" : "tcp"); +#endif + } + /** + * @cli dns config (set) + * @code + * dns config fd00::1 1234 5000 2 0 + * Done + * @endcode + * @code + * dns config + * Server: [fd00:0:0:0:0:0:0:1]:1234 + * ResponseTimeout: 5000 ms + * MaxTxAttempts: 2 + * RecursionDesired: no + * Done + * @endcode + * @code + * dns config fd00::2 + * Done + * @endcode + * @code + * dns config + * Server: [fd00:0:0:0:0:0:0:2]:53 + * ResponseTimeout: 3000 ms + * MaxTxAttempts: 3 + * RecursionDesired: yes + * Done + * @endcode + * @par api_copy + * #otDnsClientSetDefaultConfig + * @cparam dns config [@ca{dns-server-IP}] [@ca{dns-server-port}] [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] [@ca{recursion-desired-boolean}] [@ca{service-mode}] + * @par + * We can leave some of the fields as unspecified (or use value zero). The + * unspecified fields are replaced by the corresponding OT config option + * definitions `OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT` to form the default + * query config. + * `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` is required. + */ + else + { + otDnsQueryConfig queryConfig; + otDnsQueryConfig *config = &queryConfig; + + SuccessOrExit(error = GetDnsConfig(aArgs, config)); + otDnsClientSetDefaultConfig(GetInstancePtr(), config); + } + +exit: + return error; +} + +/** + * @cli dns resolve + * @code + * dns resolve ipv6.google.com + * DNS response for ipv6.google.com - 2a00:1450:401b:801:0:0:0:200e TTL: 300 + * @endcode + * @code + * dns resolve example.com 8.8.8.8 + * Synthesized IPv6 DNS server address: fdde:ad00:beef:2:0:0:808:808 + * DNS response for example.com. - fd4c:9574:3720:2:0:0:5db8:d822 TTL:20456 + * Done + * @endcode + * @cparam dns resolve @ca{hostname} [@ca{dns-server-IP}] [@ca{dns-server-port}] [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] [@ca{recursion-desired-boolean}] + * @par api_copy + * #otDnsClientResolveAddress + * @par + * Send DNS Query to obtain IPv6 address for given hostname. + * @par + * The parameters after hostname are optional. Any unspecified (or zero) value + * for these optional parameters is replaced by the value from the current default + * config (dns config). + * @par + * 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. + * @par + * Note: 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_ENABLE` is required. + */ +template <> otError Dns::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + otDnsQueryConfig queryConfig; + otDnsQueryConfig *config = &queryConfig; + + VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + SuccessOrExit(error = GetDnsConfig(aArgs + 1, config)); + SuccessOrExit(error = otDnsClientResolveAddress(GetInstancePtr(), aArgs[0].GetCString(), &HandleDnsAddressResponse, + this, config)); + error = OT_ERROR_PENDING; + +exit: + return error; +} + +#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE +template <> otError Dns::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + otDnsQueryConfig queryConfig; + otDnsQueryConfig *config = &queryConfig; + + VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + SuccessOrExit(error = GetDnsConfig(aArgs + 1, config)); + SuccessOrExit(error = otDnsClientResolveIp4Address(GetInstancePtr(), aArgs[0].GetCString(), + &HandleDnsAddressResponse, this, config)); + error = OT_ERROR_PENDING; + +exit: + return error; +} +#endif + +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +/** + * @cli dns browse + * @code + * dns browse _service._udp.example.com + * DNS browse response for _service._udp.example.com. + * inst1 + * Port:1234, Priority:1, Weight:2, TTL:7200 + * Host:host.example.com. + * HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200 + * TXT:[a=6531, b=6c12] TTL:7300 + * instance2 + * Port:1234, Priority:1, Weight:2, TTL:7200 + * Host:host.example.com. + * HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200 + * TXT:[a=1234] TTL:7300 + * Done + * @endcode + * @code + * dns browse _airplay._tcp.default.service.arpa + * DNS browse response for _airplay._tcp.default.service.arpa. + * Mac mini + * Port:7000, Priority:0, Weight:0, TTL:10 + * Host:Mac-mini.default.service.arpa. + * HostAddress:fd97:739d:386a:1:1c2e:d83c:fcbe:9cf4 TTL:10 + * Done + * @endcode + * @cparam dns browse @ca{service-name} [@ca{dns-server-IP}] [@ca{dns-server-port}] [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] [@ca{recursion-desired-boolean}] + * @sa otDnsClientBrowse + * @par + * Send a browse (service instance enumeration) DNS query to get the list of services for + * given service-name + * @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. 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. + */ +template <> otError Dns::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + otDnsQueryConfig queryConfig; + otDnsQueryConfig *config = &queryConfig; + + VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + SuccessOrExit(error = GetDnsConfig(aArgs + 1, config)); + SuccessOrExit( + error = otDnsClientBrowse(GetInstancePtr(), aArgs[0].GetCString(), &HandleDnsBrowseResponse, this, config)); + error = OT_ERROR_PENDING; + +exit: + return error; +} + +/** + * @cli dns service + * @cparam dns service @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 + * #otDnsClientResolveService + * @par + * Send a service instance resolution DNS query for a given service instance. + * 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. + */ +template <> otError Dns::Process(Arg aArgs[]) +{ + return ProcessService(aArgs, otDnsClientResolveService); +} + +/** + * @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. + */ +template <> otError Dns::Process(Arg aArgs[]) +{ + return ProcessService(aArgs, otDnsClientResolveServiceAndHostAddress); +} + +otError Dns::ProcessService(Arg aArgs[], ResolveServiceFn aResolveServiceFn) +{ + otError error = OT_ERROR_NONE; + otDnsQueryConfig queryConfig; + otDnsQueryConfig *config = &queryConfig; + + VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + SuccessOrExit(error = GetDnsConfig(aArgs + 2, config)); + SuccessOrExit(error = aResolveServiceFn(GetInstancePtr(), aArgs[0].GetCString(), aArgs[1].GetCString(), + &HandleDnsServiceResponse, this, config)); + error = OT_ERROR_PENDING; + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +//---------------------------------------------------------------------------------------------------------------------- + +void Dns::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); } + +otError Dns::GetDnsConfig(Arg aArgs[], otDnsQueryConfig *&aConfig) +{ + // This method gets the optional DNS config from `aArgs[]`. + // The format: `[server IP address] [server port] [timeout] + // [max tx attempt] [recursion desired] [service mode] + // [transport]` + + otError error = OT_ERROR_NONE; + bool recursionDesired; + bool nat64SynthesizedAddress; + + memset(aConfig, 0, sizeof(otDnsQueryConfig)); + + VerifyOrExit(!aArgs[0].IsEmpty(), aConfig = nullptr); + + SuccessOrExit(error = Interpreter::ParseToIp6Address(GetInstancePtr(), aArgs[0], aConfig->mServerSockAddr.mAddress, + nat64SynthesizedAddress)); + if (nat64SynthesizedAddress) + { + OutputFormat("Synthesized IPv6 DNS server address: "); + OutputIp6AddressLine(aConfig->mServerSockAddr.mAddress); + } + + VerifyOrExit(!aArgs[1].IsEmpty()); + SuccessOrExit(error = aArgs[1].ParseAsUint16(aConfig->mServerSockAddr.mPort)); + + VerifyOrExit(!aArgs[2].IsEmpty()); + SuccessOrExit(error = aArgs[2].ParseAsUint32(aConfig->mResponseTimeout)); + + VerifyOrExit(!aArgs[3].IsEmpty()); + SuccessOrExit(error = aArgs[3].ParseAsUint8(aConfig->mMaxTxAttempts)); + + VerifyOrExit(!aArgs[4].IsEmpty()); + SuccessOrExit(error = aArgs[4].ParseAsBool(recursionDesired)); + aConfig->mRecursionFlag = recursionDesired ? OT_DNS_FLAG_RECURSION_DESIRED : OT_DNS_FLAG_NO_RECURSION; + + VerifyOrExit(!aArgs[5].IsEmpty()); + SuccessOrExit(error = ParseDnsServiceMode(aArgs[5], aConfig->mServiceMode)); + + VerifyOrExit(!aArgs[6].IsEmpty()); + + if (aArgs[6] == "tcp") + { + aConfig->mTransportProto = OT_DNS_TRANSPORT_TCP; + } + else if (aArgs[6] == "udp") + { + aConfig->mTransportProto = OT_DNS_TRANSPORT_UDP; + } + else + { + error = OT_ERROR_INVALID_ARGS; + } + +exit: + return error; +} + +const char *const Dns::kServiceModeStrings[] = { + "unspec", // OT_DNS_SERVICE_MODE_UNSPECIFIED (0) + "srv", // OT_DNS_SERVICE_MODE_SRV (1) + "txt", // OT_DNS_SERVICE_MODE_TXT (2) + "srv_txt", // OT_DNS_SERVICE_MODE_SRV_TXT (3) + "srv_txt_sep", // OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE (4) + "srv_txt_opt", // OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE (5) +}; + +static_assert(OT_DNS_SERVICE_MODE_UNSPECIFIED == 0, "OT_DNS_SERVICE_MODE_UNSPECIFIED value is incorrect"); +static_assert(OT_DNS_SERVICE_MODE_SRV == 1, "OT_DNS_SERVICE_MODE_SRV value is incorrect"); +static_assert(OT_DNS_SERVICE_MODE_TXT == 2, "OT_DNS_SERVICE_MODE_TXT value is incorrect"); +static_assert(OT_DNS_SERVICE_MODE_SRV_TXT == 3, "OT_DNS_SERVICE_MODE_SRV_TXT value is incorrect"); +static_assert(OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE == 4, "OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE value is incorrect"); +static_assert(OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE == 5, "OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE value is incorrect"); + +const char *Dns::DnsConfigServiceModeToString(otDnsServiceMode aMode) const +{ + return Stringify(aMode, kServiceModeStrings); +} + +otError Dns::ParseDnsServiceMode(const Arg &aArg, otDnsServiceMode &aMode) const +{ + otError error = OT_ERROR_NONE; + + if (aArg == "def") + { + aMode = OT_DNS_SERVICE_MODE_UNSPECIFIED; + ExitNow(); + } + + for (uint8_t index = 0; index < OT_ARRAY_LENGTH(kServiceModeStrings); index++) + { + if (aArg == kServiceModeStrings[index]) + { + aMode = static_cast(index); + ExitNow(); + } + } + + error = OT_ERROR_INVALID_ARGS; + +exit: + return error; +} + +void Dns::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext) +{ + static_cast(aContext)->HandleDnsAddressResponse(aError, aResponse); +} + +void Dns::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse) +{ + char hostName[OT_DNS_MAX_NAME_SIZE]; + otIp6Address address; + uint32_t ttl; + + IgnoreError(otDnsAddressResponseGetHostName(aResponse, hostName, sizeof(hostName))); + + OutputFormat("DNS response for %s - ", hostName); + + if (aError == OT_ERROR_NONE) + { + uint16_t index = 0; + + while (otDnsAddressResponseGetAddress(aResponse, index, &address, &ttl) == OT_ERROR_NONE) + { + OutputIp6Address(address); + OutputFormat(" TTL:%lu ", ToUlong(ttl)); + index++; + } + } + + OutputNewLine(); + OutputResult(aError); +} + +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +void Dns::OutputDnsServiceInfo(uint8_t aIndentSize, const otDnsServiceInfo &aServiceInfo) +{ + OutputLine(aIndentSize, "Port:%d, Priority:%d, Weight:%d, TTL:%lu", aServiceInfo.mPort, aServiceInfo.mPriority, + aServiceInfo.mWeight, ToUlong(aServiceInfo.mTtl)); + OutputLine(aIndentSize, "Host:%s", aServiceInfo.mHostNameBuffer); + OutputFormat(aIndentSize, "HostAddress:"); + OutputIp6Address(aServiceInfo.mHostAddress); + OutputLine(" TTL:%lu", ToUlong(aServiceInfo.mHostAddressTtl)); + OutputFormat(aIndentSize, "TXT:"); + + if (!aServiceInfo.mTxtDataTruncated) + { + OutputDnsTxtData(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize); + } + else + { + OutputFormat("["); + OutputBytes(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize); + OutputFormat("...]"); + } + + OutputLine(" TTL:%lu", ToUlong(aServiceInfo.mTxtDataTtl)); +} + +void Dns::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext) +{ + static_cast(aContext)->HandleDnsBrowseResponse(aError, aResponse); +} + +void Dns::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse) +{ + char name[OT_DNS_MAX_NAME_SIZE]; + char label[OT_DNS_MAX_LABEL_SIZE]; + uint8_t txtBuffer[kMaxTxtDataSize]; + otDnsServiceInfo serviceInfo; + + IgnoreError(otDnsBrowseResponseGetServiceName(aResponse, name, sizeof(name))); + + OutputLine("DNS browse response for %s", name); + + if (aError == OT_ERROR_NONE) + { + uint16_t index = 0; + + while (otDnsBrowseResponseGetServiceInstance(aResponse, index, label, sizeof(label)) == OT_ERROR_NONE) + { + OutputLine("%s", label); + index++; + + serviceInfo.mHostNameBuffer = name; + serviceInfo.mHostNameBufferSize = sizeof(name); + serviceInfo.mTxtData = txtBuffer; + serviceInfo.mTxtDataSize = sizeof(txtBuffer); + + if (otDnsBrowseResponseGetServiceInfo(aResponse, label, &serviceInfo) == OT_ERROR_NONE) + { + OutputDnsServiceInfo(kIndentSize, serviceInfo); + } + + OutputNewLine(); + } + } + + OutputResult(aError); +} + +void Dns::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext) +{ + static_cast(aContext)->HandleDnsServiceResponse(aError, aResponse); +} + +void Dns::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse) +{ + char name[OT_DNS_MAX_NAME_SIZE]; + char label[OT_DNS_MAX_LABEL_SIZE]; + uint8_t txtBuffer[kMaxTxtDataSize]; + otDnsServiceInfo serviceInfo; + + IgnoreError(otDnsServiceResponseGetServiceName(aResponse, label, sizeof(label), name, sizeof(name))); + + OutputLine("DNS service resolution response for %s for service %s", label, name); + + if (aError == OT_ERROR_NONE) + { + serviceInfo.mHostNameBuffer = name; + serviceInfo.mHostNameBufferSize = sizeof(name); + serviceInfo.mTxtData = txtBuffer; + serviceInfo.mTxtDataSize = sizeof(txtBuffer); + + if (otDnsServiceResponseGetServiceInfo(aResponse, &serviceInfo) == OT_ERROR_NONE) + { + OutputDnsServiceInfo(/* aIndentSize */ 0, serviceInfo); + OutputNewLine(); + } + } + + OutputResult(aError); +} + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE + +#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE + +template <> otError Dns::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + + if (aArgs[0].IsEmpty()) + { + error = OT_ERROR_INVALID_ARGS; + } +#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE + else if (aArgs[0] == "upstream") + { + /** + * @cli dns server upstream + * @code + * dns server upstream + * Enabled + * Done + * @endcode + * @par api_copy + * #otDnssdUpstreamQueryIsEnabled + */ + if (aArgs[1].IsEmpty()) + { + OutputEnabledDisabledStatus(otDnssdUpstreamQueryIsEnabled(GetInstancePtr())); + } + /** + * @cli dns server upstream {enable|disable} + * @code + * dns server upstream enable + * Done + * @endcode + * @cparam dns server upstream @ca{enable|disable} + * @par api_copy + * #otDnssdUpstreamQuerySetEnabled + */ + else + { + bool enable; + + SuccessOrExit(error = Interpreter::ParseEnableOrDisable(aArgs[1], enable)); + otDnssdUpstreamQuerySetEnabled(GetInstancePtr(), enable); + } + } +#endif // OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE + else + { + ExitNow(error = OT_ERROR_INVALID_COMMAND); + } + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE + +otError Dns::Process(Arg aArgs[]) +{ +#define CmdEntry(aCommandString) \ + { \ + aCommandString, &Dns::Process \ + } + + static constexpr Command kCommands[] = { + +#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + CmdEntry("browse"), +#endif +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + CmdEntry("compression"), +#endif +#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE + CmdEntry("config"), + CmdEntry("resolve"), +#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE + CmdEntry("resolve4"), +#endif +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE +#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE + CmdEntry("server"), +#endif +#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + CmdEntry("service"), + CmdEntry("servicehost"), +#endif + }; + +#undef CmdEntry + + static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted"); + + otError error = OT_ERROR_INVALID_COMMAND; + const Command *command; + + if (aArgs[0].IsEmpty() || (aArgs[0] == "help")) + { + OutputCommandTable(kCommands); + ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE); + } + + command = BinarySearch::Find(aArgs[0].GetCString(), kCommands); + VerifyOrExit(command != nullptr); + + error = (this->*command->mHandler)(aArgs + 1); + +exit: + return error; +} + +} // namespace Cli +} // namespace ot + +#endif // OPENTHREAD_CLI_DNS_ENABLE diff --git a/src/cli/cli_dns.hpp b/src/cli/cli_dns.hpp new file mode 100644 index 000000000..b9eb8d892 --- /dev/null +++ b/src/cli/cli_dns.hpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2023, 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 contains definitions for CLI to DNS (client and resolver). + */ + +#ifndef CLI_DNS_HPP_ +#define CLI_DNS_HPP_ + +#include "openthread-core-config.h" + +#ifdef OPENTHREAD_CLI_DNS_ENABLE +#error "OPENTHREAD_CLI_DNS_ENABLE MUST not be defined directly, it is derived from `OPENTHREAD_CONFIG_*` configs". +#endif + +#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE || OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE || \ + OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +#define OPENTHREAD_CLI_DNS_ENABLE 1 +#else +#define OPENTHREAD_CLI_DNS_ENABLE 0 +#endif + +#if OPENTHREAD_CLI_DNS_ENABLE + +#include +#include +#include + +#include "cli/cli_config.h" +#include "cli/cli_output.hpp" + +namespace ot { +namespace Cli { + +/** + * This class implements the DNS CLI interpreter. + * + */ +class Dns : private Output +{ +public: + typedef Utils::CmdLineParser::Arg Arg; + + /** + * Constructor. + * + * @param[in] aInstance The OpenThread Instance. + * @param[in] aOutputImplementer An `OutputImplementer`. + * + */ + Dns(otInstance *aInstance, OutputImplementer &aOutputImplementer) + : Output(aInstance, aOutputImplementer) + { + } + + /** + * This method interprets a list of CLI arguments. + * + * @param[in] aArgs A pointer an array of command line arguments. + * + */ + otError Process(Arg aArgs[]); + +private: + static constexpr uint8_t kIndentSize = 4; + static constexpr uint16_t kMaxTxtDataSize = OPENTHREAD_CONFIG_CLI_TXT_RECORD_MAX_SIZE; + + using Command = CommandEntry; + + template otError Process(Arg aArgs[]); + +#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE + void OutputResult(otError aError); + otError GetDnsConfig(Arg aArgs[], otDnsQueryConfig *&aConfig); + const char *DnsConfigServiceModeToString(otDnsServiceMode aMode) const; + otError ParseDnsServiceMode(const Arg &aArg, otDnsServiceMode &aMode) const; + static void HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext); + void HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse); +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + static const char *const kServiceModeStrings[]; + + typedef otError (&ResolveServiceFn)(otInstance *, + const char *, + const char *, + otDnsServiceCallback, + void *, + const otDnsQueryConfig *); + + otError ProcessService(Arg aArgs[], ResolveServiceFn aResolveServiceFn); + void OutputDnsServiceInfo(uint8_t aIndentSize, const otDnsServiceInfo &aServiceInfo); + static void HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext); + void HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse); + static void HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext); + void HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse); +#endif +#endif +}; + +} // namespace Cli +} // namespace ot + +#endif // OPENTHREAD_CLI_DNS_ENABLE + +#endif // CLI_DNS_HPP_