From 4538a5fb7cdf06507f61a41b99f1672b97b81803 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 1 Feb 2021 11:22:41 -0800 Subject: [PATCH] [cli] add support for DNS-SD command (#6116) This commit adds new commands in CLI to support DNS-SD browse and service resolution. --- src/cli/README.md | 24 ++++ src/cli/cli.cpp | 173 ++++++++++++++++++++---- src/cli/cli.hpp | 12 +- tests/scripts/thread-cert/node.py | 11 +- tests/scripts/thread-cert/test_dnssd.py | 8 +- 5 files changed, 193 insertions(+), 35 deletions(-) diff --git a/src/cli/README.md b/src/cli/README.md index 8d61c66a4..cf432adf4 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -758,6 +758,30 @@ Send DNS Query to obtain IPv6 address for given hostname. The latter two paramet > DNS response for ipv6.google.com - 2a00:1450:401b:801:0:0:0:200e TTL: 300 ``` +### dns browse \ \[DNS server IP\] \[DNS server port\] + +Send a browse (service instance enumeration) DNS query to get the list of services for given service-name. + +```bash +> 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-Data:(len:11) [096b65793d76616c756500] 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-Data:(len:11) [096b65793d76616c756500] TTL:7300 +Done +``` + +### dns service \ \ \[DNS server IP\] \[DNS server port\] + +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). + ### domainname Get the Thread Domain Name for Thread 1.2 device. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 6be6466a8..66580256b 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -1335,37 +1335,70 @@ exit: } #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE + +otError Interpreter::GetDnsServerAddress(uint8_t aArgsLength, + char * aArgs[], + otSockAddr &aAddress, + uint8_t aStartArgsIndex) +{ + // This method gets the optional server address from given `aArgs` + // after the `aStartArgsIndex`. The format `[server IPv6 address] + // [server port]`. + + otError error = OT_ERROR_NONE; + + VerifyOrExit(aArgsLength >= aStartArgsIndex, error = OT_ERROR_INVALID_ARGS); + + if (aArgsLength > aStartArgsIndex) + { + SuccessOrExit(error = ParseAsIp6Address(aArgs[aStartArgsIndex], aAddress.mAddress)); + } + else + { + // Use IPv6 address of default DNS server. + SuccessOrExit(error = otIp6AddressFromString(OT_DNS_DEFAULT_SERVER_IP, &aAddress.mAddress)); + } + + if (aArgsLength > aStartArgsIndex + 1) + { + SuccessOrExit(error = ParseAsUint16(aArgs[aStartArgsIndex + 1], aAddress.mPort)); + } + else + { + aAddress.mPort = OT_DNS_DEFAULT_SERVER_PORT; + } + +exit: + return error; +} + otError Interpreter::ProcessDns(uint8_t aArgsLength, char *aArgs[]) { otError error = OT_ERROR_NONE; otSockAddr serverSockAddr; - serverSockAddr.mPort = OT_DNS_DEFAULT_SERVER_PORT; - VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS); if (strcmp(aArgs[0], "resolve") == 0) { - VerifyOrExit(aArgsLength > 1, error = OT_ERROR_INVALID_ARGS); - - if (aArgsLength > 2) - { - SuccessOrExit(error = ParseAsIp6Address(aArgs[2], serverSockAddr.mAddress)); - } - else - { - // Use IPv6 address of default DNS server. - SuccessOrExit(error = otIp6AddressFromString(OT_DNS_DEFAULT_SERVER_IP, &serverSockAddr.mAddress)); - } - - if (aArgsLength > 3) - { - SuccessOrExit(error = ParseAsUint16(aArgs[3], serverSockAddr.mPort)); - } - + SuccessOrExit(error = GetDnsServerAddress(aArgsLength, aArgs, serverSockAddr, 2)); SuccessOrExit(error = otDnsClientResolveAddress(mInstance, &serverSockAddr, aArgs[1], /* aNoRecursion */ false, - &Interpreter::HandleDnsResponse, this)); + &Interpreter::HandleDnsAddressResponse, this)); } +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + else if (strcmp(aArgs[0], "browse") == 0) + { + SuccessOrExit(error = GetDnsServerAddress(aArgsLength, aArgs, serverSockAddr, 2)); + SuccessOrExit(error = otDnsClientBrowse(mInstance, &serverSockAddr, aArgs[1], + &Interpreter::HandleDnsBrowseResponse, this)); + } + else if (strcmp(aArgs[0], "service") == 0) + { + SuccessOrExit(error = GetDnsServerAddress(aArgsLength, aArgs, serverSockAddr, 3)); + SuccessOrExit(error = otDnsClientResolveService(mInstance, &serverSockAddr, aArgs[1], aArgs[2], + &Interpreter::HandleDnsServiceResponse, this)); + } +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE else { ExitNow(error = OT_ERROR_INVALID_COMMAND); @@ -1377,12 +1410,12 @@ exit: return error; } -void Interpreter::HandleDnsResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext) +void Interpreter::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext) { - static_cast(aContext)->HandleDnsResponse(aError, aResponse); + static_cast(aContext)->HandleDnsAddressResponse(aError, aResponse); } -void Interpreter::HandleDnsResponse(otError aError, const otDnsAddressResponse *aResponse) +void Interpreter::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse) { char hostName[OT_DNS_MAX_NAME_SIZE]; otIp6Address address; @@ -1399,7 +1432,7 @@ void Interpreter::HandleDnsResponse(otError aError, const otDnsAddressResponse * while (otDnsAddressResponseGetAddress(aResponse, index, &address, &ttl) == OT_ERROR_NONE) { OutputIp6Address(address); - OutputFormat(" TTL: %u ", ttl); + OutputFormat(" TTL:%u ", ttl); index++; } @@ -1408,6 +1441,98 @@ void Interpreter::HandleDnsResponse(otError aError, const otDnsAddressResponse * 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:%u", aServiceInfo.mPort, aServiceInfo.mPriority, + aServiceInfo.mWeight, aServiceInfo.mTtl); + OutputLine(aIndentSize, "Host:%s", aServiceInfo.mHostNameBuffer); + OutputFormat(aIndentSize, "HostAddress:"); + OutputIp6Address(aServiceInfo.mHostAddress); + OutputLine(" TTL:%u", aServiceInfo.mHostAddressTtl); + OutputFormat(aIndentSize, "TXT-Data:(len:%d) [", aServiceInfo.mTxtDataSize); + OutputBytes(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize); + OutputFormat("] TTL:%u", 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[255]; + 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); + } + + OutputLine(""); + } + } + + 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[255]; + 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(/* aIndetSize */ 0, serviceInfo); + OutputLine(""); + } + } + + OutputResult(aError); +} + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE #endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE #if OPENTHREAD_FTD diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index a5ad143b0..cf0a9e5ef 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -554,8 +554,16 @@ private: #endif #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - static void HandleDnsResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext); - void HandleDnsResponse(otError aError, const otDnsAddressResponse *aResponse); + otError GetDnsServerAddress(uint8_t aArgsLength, char *aArgs[], otSockAddr &aAddress, uint8_t aStartArgsIndex); + 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 + 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 diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index eacf81b44..394095b88 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -2411,12 +2411,13 @@ class NodeImpl: self.simulator.go(10) output = self._expect_command_output(cmd) dns_resp = output[0] - # example output: DNS response for host1.default.service.arpa. - fd00:db8:0:0:ae43:4938:4c42:e6af TTL: 7190 - ip, ttl = dns_resp.split(' - ')[1].split(' TTL: ') - ip = ip.strip() - ttl = int(ttl) + # example output: "DNS response for host1.default.service.arpa. - fd00:db8:0:0:fd3d:d471:1e8c:b60 TTL:7190 " + # " fd00:db8:0:0:0:ff:fe00:9000 TTL:7190" + addrs = dns_resp.strip().split(' - ')[1].split(' ') + ip = [item.strip() for item in addrs[::2]] + ttl = [int(item.split('TTL:')[1]) for item in addrs[1::2]] - return (ip, ttl) + return list(zip(ip, ttl)) class Node(NodeImpl, OtCli): diff --git a/tests/scripts/thread-cert/test_dnssd.py b/tests/scripts/thread-cert/test_dnssd.py index 4e7d9624a..9158fc0a9 100755 --- a/tests/scripts/thread-cert/test_dnssd.py +++ b/tests/scripts/thread-cert/test_dnssd.py @@ -88,11 +88,11 @@ class TestDnssd(thread_cert.TestCase): self._config_srp_client_services(CLIENT2, 'ins2', 'host2', 22222, 2, 2, client2_addrs) # Test AAAA query using DNS client - ip, ttl = self.nodes[CLIENT1].dns_resolve(f"host1.{DOMAIN}", self.nodes[SERVER].get_mleid(), 53) - self.assertIn(ipaddress.IPv6Address(ip), map(ipaddress.IPv6Address, client1_addrs)) + response = self.nodes[CLIENT1].dns_resolve(f"host1.{DOMAIN}", self.nodes[SERVER].get_mleid(), 53) + self.assertIn(ipaddress.IPv6Address(response[0][0]), map(ipaddress.IPv6Address, client1_addrs)) - ip, ttl = self.nodes[CLIENT1].dns_resolve(f"host2.{DOMAIN}", self.nodes[SERVER].get_mleid(), 53) - self.assertIn(ipaddress.IPv6Address(ip), map(ipaddress.IPv6Address, client2_addrs)) + response = self.nodes[CLIENT1].dns_resolve(f"host2.{DOMAIN}", self.nodes[SERVER].get_mleid(), 53) + self.assertIn(ipaddress.IPv6Address(response[0][0]), map(ipaddress.IPv6Address, client2_addrs)) # TODO: test other query types using DNS-SD client