1 /*
2  *  Copyright (c) 2023, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file implements CLI for DNS (client and server/resolver).
32  */
33 
34 #include "cli_dns.hpp"
35 
36 #include "cli/cli.hpp"
37 
38 #if OPENTHREAD_CLI_DNS_ENABLE
39 
40 namespace ot {
41 namespace Cli {
42 
43 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
44 
Process(Arg aArgs[])45 template <> otError Dns::Process<Cmd("compression")>(Arg aArgs[])
46 {
47     otError error = OT_ERROR_NONE;
48 
49     /**
50      * @cli dns compression
51      * @code
52      * dns compression
53      * Enabled
54      * @endcode
55      * @cparam dns compression [@ca{enable|disable}]
56      * @par api_copy
57      * #otDnsIsNameCompressionEnabled
58      * @par
59      * By default DNS name compression is enabled. When disabled,
60      * DNS names are appended as full and never compressed. This
61      * is applicable to OpenThread's DNS and SRP client/server
62      * modules."
63      * `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is required.
64      */
65     if (aArgs[0].IsEmpty())
66     {
67         OutputEnabledDisabledStatus(otDnsIsNameCompressionEnabled());
68     }
69     /**
70      * @cli dns compression (enable,disable)
71      * @code
72      * dns compression enable
73      * Enabled
74      * @endcode
75      * @code
76      * dns compression disable
77      * Done
78      * dns compression
79      * Disabled
80      * Done
81      * @endcode
82      * @cparam dns compression [@ca{enable|disable}]
83      * @par
84      * Set the "DNS name compression" mode.
85      * @par
86      * By default DNS name compression is enabled. When disabled,
87      * DNS names are appended as full and never compressed. This
88      * is applicable to OpenThread's DNS and SRP client/server
89      * modules."
90      * `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is required.
91      * @sa otDnsSetNameCompressionEnabled
92      */
93     else
94     {
95         bool enable;
96 
97         SuccessOrExit(error = Interpreter::ParseEnableOrDisable(aArgs[0], enable));
98         otDnsSetNameCompressionEnabled(enable);
99     }
100 
101 exit:
102     return error;
103 }
104 
105 #endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
106 
107 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
108 
Process(Arg aArgs[])109 template <> otError Dns::Process<Cmd("config")>(Arg aArgs[])
110 {
111     otError error = OT_ERROR_NONE;
112 
113     /**
114      * @cli dns config
115      * @code
116      * dns config
117      * Server: [fd00:0:0:0:0:0:0:1]:1234
118      * ResponseTimeout: 5000 ms
119      * MaxTxAttempts: 2
120      * RecursionDesired: no
121      * ServiceMode: srv
122      * Nat64Mode: allow
123      * Done
124      * @endcode
125      * @par api_copy
126      * #otDnsClientGetDefaultConfig
127      * @par
128      * The config includes the server IPv6 address and port, response
129      * timeout in msec (wait time to rx response), maximum tx attempts
130      * before reporting failure, boolean flag to indicate whether the server
131      * can resolve the query recursively or not.
132      * `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` is required.
133      */
134     if (aArgs[0].IsEmpty())
135     {
136         const otDnsQueryConfig *defaultConfig = otDnsClientGetDefaultConfig(GetInstancePtr());
137 
138         OutputFormat("Server: ");
139         OutputSockAddrLine(defaultConfig->mServerSockAddr);
140         OutputLine("ResponseTimeout: %lu ms", ToUlong(defaultConfig->mResponseTimeout));
141         OutputLine("MaxTxAttempts: %u", defaultConfig->mMaxTxAttempts);
142         OutputLine("RecursionDesired: %s",
143                    (defaultConfig->mRecursionFlag == OT_DNS_FLAG_RECURSION_DESIRED) ? "yes" : "no");
144         OutputLine("ServiceMode: %s", DnsConfigServiceModeToString(defaultConfig->mServiceMode));
145 #if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
146         OutputLine("Nat64Mode: %s", (defaultConfig->mNat64Mode == OT_DNS_NAT64_ALLOW) ? "allow" : "disallow");
147 #endif
148 #if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE
149         OutputLine("TransportProtocol: %s", (defaultConfig->mTransportProto == OT_DNS_TRANSPORT_UDP) ? "udp" : "tcp");
150 #endif
151     }
152     /**
153      * @cli dns config (set)
154      * @code
155      * dns config fd00::1 1234 5000 2 0
156      * Done
157      * @endcode
158      * @code
159      * dns config
160      * Server: [fd00:0:0:0:0:0:0:1]:1234
161      * ResponseTimeout: 5000 ms
162      * MaxTxAttempts: 2
163      * RecursionDesired: no
164      * Done
165      * @endcode
166      * @code
167      * dns config fd00::2
168      * Done
169      * @endcode
170      * @code
171      * dns config
172      * Server: [fd00:0:0:0:0:0:0:2]:53
173      * ResponseTimeout: 3000 ms
174      * MaxTxAttempts: 3
175      * RecursionDesired: yes
176      * Done
177      * @endcode
178      * @par api_copy
179      * #otDnsClientSetDefaultConfig
180      * @cparam dns config [@ca{dns-server-IP}] [@ca{dns-server-port}] <!--
181      * -->                [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] <!--
182      * -->                [@ca{recursion-desired-boolean}] [@ca{service-mode}]
183      * @par
184      * We can leave some of the fields as unspecified (or use value zero). The
185      * unspecified fields are replaced by the corresponding OT config option
186      * definitions `OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT` to form the default
187      * query config.
188      * `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` is required.
189      */
190     else
191     {
192         otDnsQueryConfig  queryConfig;
193         otDnsQueryConfig *config = &queryConfig;
194 
195         SuccessOrExit(error = GetDnsConfig(aArgs, config));
196         otDnsClientSetDefaultConfig(GetInstancePtr(), config);
197     }
198 
199 exit:
200     return error;
201 }
202 
203 /**
204  * @cli dns resolve
205  * @code
206  * dns resolve ipv6.google.com
207  * DNS response for ipv6.google.com - 2a00:1450:401b:801:0:0:0:200e TTL: 300
208  * @endcode
209  * @code
210  * dns resolve example.com 8.8.8.8
211  * Synthesized IPv6 DNS server address: fdde:ad00:beef:2:0:0:808:808
212  * DNS response for example.com. - fd4c:9574:3720:2:0:0:5db8:d822 TTL:20456
213  * Done
214  * @endcode
215  * @cparam dns resolve @ca{hostname} [@ca{dns-server-IP}] <!--
216  * -->                 [@ca{dns-server-port}] [@ca{response-timeout-ms}] <!--
217  * -->                 [@ca{max-tx-attempts}] [@ca{recursion-desired-boolean}]
218  * @par api_copy
219  * #otDnsClientResolveAddress
220  * @par
221  * Send DNS Query to obtain IPv6 address for given hostname.
222  * @par
223  * The parameters after hostname are optional. Any unspecified (or zero) value
224  * for these optional parameters is replaced by the value from the current default
225  * config (dns config).
226  * @par
227  * The DNS server IP can be an IPv4 address, which will be synthesized to an
228  * IPv6 address using the preferred NAT64 prefix from the network data.
229  * @par
230  * Note: The command will return InvalidState when the DNS server IP is an IPv4
231  * address but the preferred NAT64 prefix is unavailable.
232  * `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` is required.
233  */
Process(Arg aArgs[])234 template <> otError Dns::Process<Cmd("resolve")>(Arg aArgs[])
235 {
236     otError           error = OT_ERROR_NONE;
237     otDnsQueryConfig  queryConfig;
238     otDnsQueryConfig *config = &queryConfig;
239 
240     VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
241     SuccessOrExit(error = GetDnsConfig(aArgs + 1, config));
242     SuccessOrExit(error = otDnsClientResolveAddress(GetInstancePtr(), aArgs[0].GetCString(), &HandleDnsAddressResponse,
243                                                     this, config));
244     error = OT_ERROR_PENDING;
245 
246 exit:
247     return error;
248 }
249 
250 #if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
Process(Arg aArgs[])251 template <> otError Dns::Process<Cmd("resolve4")>(Arg aArgs[])
252 {
253     otError           error = OT_ERROR_NONE;
254     otDnsQueryConfig  queryConfig;
255     otDnsQueryConfig *config = &queryConfig;
256 
257     VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
258     SuccessOrExit(error = GetDnsConfig(aArgs + 1, config));
259     SuccessOrExit(error = otDnsClientResolveIp4Address(GetInstancePtr(), aArgs[0].GetCString(),
260                                                        &HandleDnsAddressResponse, this, config));
261     error = OT_ERROR_PENDING;
262 
263 exit:
264     return error;
265 }
266 #endif
267 
268 #if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
269 
270 /**
271  * @cli dns browse
272  * @code
273  * dns browse _service._udp.example.com
274  * DNS browse response for _service._udp.example.com.
275  * inst1
276  *     Port:1234, Priority:1, Weight:2, TTL:7200
277  *     Host:host.example.com.
278  *     HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200
279  *     TXT:[a=6531, b=6c12] TTL:7300
280  * instance2
281  *     Port:1234, Priority:1, Weight:2, TTL:7200
282  *     Host:host.example.com.
283  *     HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200
284  *     TXT:[a=1234] TTL:7300
285  * Done
286  * @endcode
287  * @code
288  * dns browse _airplay._tcp.default.service.arpa
289  * DNS browse response for _airplay._tcp.default.service.arpa.
290  * Mac mini
291  *     Port:7000, Priority:0, Weight:0, TTL:10
292  *     Host:Mac-mini.default.service.arpa.
293  *     HostAddress:fd97:739d:386a:1:1c2e:d83c:fcbe:9cf4 TTL:10
294  * Done
295  * @endcode
296  * @cparam dns browse @ca{service-name} [@ca{dns-server-IP}] [@ca{dns-server-port}] <!--
297  * -->                [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] <!--
298  * -->                [@ca{recursion-desired-boolean}]
299  * @sa otDnsClientBrowse
300  * @par
301  * Send a browse (service instance enumeration) DNS query to get the list of services for
302  * given service-name
303  * @par
304  * The parameters after `service-name` are optional. Any unspecified (or zero) value
305  * for these optional parameters is replaced by the value from the current default
306  * config (`dns config`).
307  * @par
308  * Note: The DNS server IP can be an IPv4 address, which will be synthesized to an IPv6
309  * address using the preferred NAT64 prefix from the network data. The command will return
310  * `InvalidState` when the DNS server IP is an IPv4 address but the preferred NAT64 prefix
311  * is unavailable. When testing DNS-SD discovery proxy, the zone is not `local` and
312  * instead should be `default.service.arpa`.
313  * `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is required.
314  */
Process(Arg aArgs[])315 template <> otError Dns::Process<Cmd("browse")>(Arg aArgs[])
316 {
317     otError           error = OT_ERROR_NONE;
318     otDnsQueryConfig  queryConfig;
319     otDnsQueryConfig *config = &queryConfig;
320 
321     VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
322     SuccessOrExit(error = GetDnsConfig(aArgs + 1, config));
323     SuccessOrExit(
324         error = otDnsClientBrowse(GetInstancePtr(), aArgs[0].GetCString(), &HandleDnsBrowseResponse, this, config));
325     error = OT_ERROR_PENDING;
326 
327 exit:
328     return error;
329 }
330 
331 /**
332  * @cli dns service
333  * @cparam dns service @ca{service-instance-label} @ca{service-name} <!--
334  * -->                 [@ca{DNS-server-IP}] [@ca{DNS-server-port}] <!--
335  * -->                 [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] <!--
336  * -->                 [@ca{recursion-desired-boolean}]
337  * @par api_copy
338  * #otDnsClientResolveService
339  * @par
340  * Send a service instance resolution DNS query for a given service instance.
341  * Service instance label is provided first, followed by the service name
342  * (note that service instance label can contain dot '.' character).
343  * @par
344  * The parameters after `service-name` are optional. Any unspecified (or zero)
345  * value for these optional parameters is replaced by the value from the
346  * current default config (`dns config`).
347  * @par
348  * Note: The DNS server IP can be an IPv4 address, which will be synthesized
349  * to an IPv6 address using the preferred NAT64 prefix from the network data.
350  * The command will return `InvalidState` when the DNS server IP is an IPv4
351  * address but the preferred NAT64 prefix is unavailable.
352  * `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is required.
353  */
Process(Arg aArgs[])354 template <> otError Dns::Process<Cmd("service")>(Arg aArgs[])
355 {
356     return ProcessService(aArgs, otDnsClientResolveService);
357 }
358 
359 /**
360  * @cli dns servicehost
361  * @cparam dns servicehost @ca{service-instance-label} @ca{service-name} <!--
362  * -->                 [@ca{DNS-server-IP}] [@ca{DNS-server-port}] <!--
363  * -->                 [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] <!--
364  * -->                 [@ca{recursion-desired-boolean}]
365  * @par api_copy
366  * #otDnsClientResolveServiceAndHostAddress
367  * @par
368  * Send a service instance resolution DNS query for a given service instance
369  * with potential follow-up host name resolution.
370  * Service instance label is provided first, followed by the service name
371  * (note that service instance label can contain dot '.' character).
372  * @par
373  * The parameters after `service-name` are optional. Any unspecified (or zero)
374  * value for these optional parameters is replaced by the value from the
375  * current default config (`dns config`).
376  * @par
377  * Note: The DNS server IP can be an IPv4 address, which will be synthesized
378  * to an IPv6 address using the preferred NAT64 prefix from the network data.
379  * The command will return `InvalidState` when the DNS server IP is an IPv4
380  * address but the preferred NAT64 prefix is unavailable.
381  * `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is required.
382  */
Process(Arg aArgs[])383 template <> otError Dns::Process<Cmd("servicehost")>(Arg aArgs[])
384 {
385     return ProcessService(aArgs, otDnsClientResolveServiceAndHostAddress);
386 }
387 
ProcessService(Arg aArgs[],ResolveServiceFn aResolveServiceFn)388 otError Dns::ProcessService(Arg aArgs[], ResolveServiceFn aResolveServiceFn)
389 {
390     otError           error = OT_ERROR_NONE;
391     otDnsQueryConfig  queryConfig;
392     otDnsQueryConfig *config = &queryConfig;
393 
394     VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
395     SuccessOrExit(error = GetDnsConfig(aArgs + 2, config));
396     SuccessOrExit(error = aResolveServiceFn(GetInstancePtr(), aArgs[0].GetCString(), aArgs[1].GetCString(),
397                                             &HandleDnsServiceResponse, this, config));
398     error = OT_ERROR_PENDING;
399 
400 exit:
401     return error;
402 }
403 
404 #endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
405 
406 //----------------------------------------------------------------------------------------------------------------------
407 
OutputResult(otError aError)408 void Dns::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); }
409 
GetDnsConfig(Arg aArgs[],otDnsQueryConfig * & aConfig)410 otError Dns::GetDnsConfig(Arg aArgs[], otDnsQueryConfig *&aConfig)
411 {
412     // This method gets the optional DNS config from `aArgs[]`.
413     // The format: `[server IP address] [server port] [timeout]
414     // [max tx attempt] [recursion desired] [service mode]
415     // [transport]`
416 
417     otError error = OT_ERROR_NONE;
418     bool    recursionDesired;
419     bool    nat64SynthesizedAddress;
420 
421     memset(aConfig, 0, sizeof(otDnsQueryConfig));
422 
423     VerifyOrExit(!aArgs[0].IsEmpty(), aConfig = nullptr);
424 
425     SuccessOrExit(error = Interpreter::ParseToIp6Address(GetInstancePtr(), aArgs[0], aConfig->mServerSockAddr.mAddress,
426                                                          nat64SynthesizedAddress));
427     if (nat64SynthesizedAddress)
428     {
429         OutputFormat("Synthesized IPv6 DNS server address: ");
430         OutputIp6AddressLine(aConfig->mServerSockAddr.mAddress);
431     }
432 
433     VerifyOrExit(!aArgs[1].IsEmpty());
434     SuccessOrExit(error = aArgs[1].ParseAsUint16(aConfig->mServerSockAddr.mPort));
435 
436     VerifyOrExit(!aArgs[2].IsEmpty());
437     SuccessOrExit(error = aArgs[2].ParseAsUint32(aConfig->mResponseTimeout));
438 
439     VerifyOrExit(!aArgs[3].IsEmpty());
440     SuccessOrExit(error = aArgs[3].ParseAsUint8(aConfig->mMaxTxAttempts));
441 
442     VerifyOrExit(!aArgs[4].IsEmpty());
443     SuccessOrExit(error = aArgs[4].ParseAsBool(recursionDesired));
444     aConfig->mRecursionFlag = recursionDesired ? OT_DNS_FLAG_RECURSION_DESIRED : OT_DNS_FLAG_NO_RECURSION;
445 
446     VerifyOrExit(!aArgs[5].IsEmpty());
447     SuccessOrExit(error = ParseDnsServiceMode(aArgs[5], aConfig->mServiceMode));
448 
449     VerifyOrExit(!aArgs[6].IsEmpty());
450 
451     if (aArgs[6] == "tcp")
452     {
453         aConfig->mTransportProto = OT_DNS_TRANSPORT_TCP;
454     }
455     else if (aArgs[6] == "udp")
456     {
457         aConfig->mTransportProto = OT_DNS_TRANSPORT_UDP;
458     }
459     else
460     {
461         error = OT_ERROR_INVALID_ARGS;
462     }
463 
464 exit:
465     return error;
466 }
467 
468 const char *const Dns::kServiceModeStrings[] = {
469     "unspec",      // OT_DNS_SERVICE_MODE_UNSPECIFIED      (0)
470     "srv",         // OT_DNS_SERVICE_MODE_SRV              (1)
471     "txt",         // OT_DNS_SERVICE_MODE_TXT              (2)
472     "srv_txt",     // OT_DNS_SERVICE_MODE_SRV_TXT          (3)
473     "srv_txt_sep", // OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE (4)
474     "srv_txt_opt", // OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE (5)
475 };
476 
477 static_assert(OT_DNS_SERVICE_MODE_UNSPECIFIED == 0, "OT_DNS_SERVICE_MODE_UNSPECIFIED value is incorrect");
478 static_assert(OT_DNS_SERVICE_MODE_SRV == 1, "OT_DNS_SERVICE_MODE_SRV value is incorrect");
479 static_assert(OT_DNS_SERVICE_MODE_TXT == 2, "OT_DNS_SERVICE_MODE_TXT value is incorrect");
480 static_assert(OT_DNS_SERVICE_MODE_SRV_TXT == 3, "OT_DNS_SERVICE_MODE_SRV_TXT value is incorrect");
481 static_assert(OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE == 4, "OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE value is incorrect");
482 static_assert(OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE == 5, "OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE value is incorrect");
483 
DnsConfigServiceModeToString(otDnsServiceMode aMode) const484 const char *Dns::DnsConfigServiceModeToString(otDnsServiceMode aMode) const
485 {
486     return Stringify(aMode, kServiceModeStrings);
487 }
488 
ParseDnsServiceMode(const Arg & aArg,otDnsServiceMode & aMode) const489 otError Dns::ParseDnsServiceMode(const Arg &aArg, otDnsServiceMode &aMode) const
490 {
491     otError error = OT_ERROR_NONE;
492 
493     if (aArg == "def")
494     {
495         aMode = OT_DNS_SERVICE_MODE_UNSPECIFIED;
496         ExitNow();
497     }
498 
499     for (size_t index = 0; index < OT_ARRAY_LENGTH(kServiceModeStrings); index++)
500     {
501         if (aArg == kServiceModeStrings[index])
502         {
503             aMode = static_cast<otDnsServiceMode>(index);
504             ExitNow();
505         }
506     }
507 
508     error = OT_ERROR_INVALID_ARGS;
509 
510 exit:
511     return error;
512 }
513 
HandleDnsAddressResponse(otError aError,const otDnsAddressResponse * aResponse,void * aContext)514 void Dns::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext)
515 {
516     static_cast<Dns *>(aContext)->HandleDnsAddressResponse(aError, aResponse);
517 }
518 
HandleDnsAddressResponse(otError aError,const otDnsAddressResponse * aResponse)519 void Dns::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse)
520 {
521     char         hostName[OT_DNS_MAX_NAME_SIZE];
522     otIp6Address address;
523     uint32_t     ttl;
524 
525     IgnoreError(otDnsAddressResponseGetHostName(aResponse, hostName, sizeof(hostName)));
526 
527     OutputFormat("DNS response for %s - ", hostName);
528 
529     if (aError == OT_ERROR_NONE)
530     {
531         uint16_t index = 0;
532 
533         while (otDnsAddressResponseGetAddress(aResponse, index, &address, &ttl) == OT_ERROR_NONE)
534         {
535             OutputIp6Address(address);
536             OutputFormat(" TTL:%lu ", ToUlong(ttl));
537             index++;
538         }
539     }
540 
541     OutputNewLine();
542     OutputResult(aError);
543 }
544 
545 #if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
546 
OutputDnsServiceInfo(uint8_t aIndentSize,const otDnsServiceInfo & aServiceInfo)547 void Dns::OutputDnsServiceInfo(uint8_t aIndentSize, const otDnsServiceInfo &aServiceInfo)
548 {
549     OutputLine(aIndentSize, "Port:%d, Priority:%d, Weight:%d, TTL:%lu", aServiceInfo.mPort, aServiceInfo.mPriority,
550                aServiceInfo.mWeight, ToUlong(aServiceInfo.mTtl));
551     OutputLine(aIndentSize, "Host:%s", aServiceInfo.mHostNameBuffer);
552     OutputFormat(aIndentSize, "HostAddress:");
553     OutputIp6Address(aServiceInfo.mHostAddress);
554     OutputLine(" TTL:%lu", ToUlong(aServiceInfo.mHostAddressTtl));
555     OutputFormat(aIndentSize, "TXT:");
556 
557     if (!aServiceInfo.mTxtDataTruncated)
558     {
559         OutputDnsTxtData(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize);
560     }
561     else
562     {
563         OutputFormat("[");
564         OutputBytes(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize);
565         OutputFormat("...]");
566     }
567 
568     OutputLine(" TTL:%lu", ToUlong(aServiceInfo.mTxtDataTtl));
569 }
570 
HandleDnsBrowseResponse(otError aError,const otDnsBrowseResponse * aResponse,void * aContext)571 void Dns::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext)
572 {
573     static_cast<Dns *>(aContext)->HandleDnsBrowseResponse(aError, aResponse);
574 }
575 
HandleDnsBrowseResponse(otError aError,const otDnsBrowseResponse * aResponse)576 void Dns::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse)
577 {
578     char             name[OT_DNS_MAX_NAME_SIZE];
579     char             label[OT_DNS_MAX_LABEL_SIZE];
580     uint8_t          txtBuffer[kMaxTxtDataSize];
581     otDnsServiceInfo serviceInfo;
582 
583     IgnoreError(otDnsBrowseResponseGetServiceName(aResponse, name, sizeof(name)));
584 
585     OutputLine("DNS browse response for %s", name);
586 
587     if (aError == OT_ERROR_NONE)
588     {
589         uint16_t index = 0;
590 
591         while (otDnsBrowseResponseGetServiceInstance(aResponse, index, label, sizeof(label)) == OT_ERROR_NONE)
592         {
593             OutputLine("%s", label);
594             index++;
595 
596             serviceInfo.mHostNameBuffer     = name;
597             serviceInfo.mHostNameBufferSize = sizeof(name);
598             serviceInfo.mTxtData            = txtBuffer;
599             serviceInfo.mTxtDataSize        = sizeof(txtBuffer);
600 
601             if (otDnsBrowseResponseGetServiceInfo(aResponse, label, &serviceInfo) == OT_ERROR_NONE)
602             {
603                 OutputDnsServiceInfo(kIndentSize, serviceInfo);
604             }
605 
606             OutputNewLine();
607         }
608     }
609 
610     OutputResult(aError);
611 }
612 
HandleDnsServiceResponse(otError aError,const otDnsServiceResponse * aResponse,void * aContext)613 void Dns::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext)
614 {
615     static_cast<Dns *>(aContext)->HandleDnsServiceResponse(aError, aResponse);
616 }
617 
HandleDnsServiceResponse(otError aError,const otDnsServiceResponse * aResponse)618 void Dns::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse)
619 {
620     char             name[OT_DNS_MAX_NAME_SIZE];
621     char             label[OT_DNS_MAX_LABEL_SIZE];
622     uint8_t          txtBuffer[kMaxTxtDataSize];
623     otDnsServiceInfo serviceInfo;
624 
625     IgnoreError(otDnsServiceResponseGetServiceName(aResponse, label, sizeof(label), name, sizeof(name)));
626 
627     OutputLine("DNS service resolution response for %s for service %s", label, name);
628 
629     if (aError == OT_ERROR_NONE)
630     {
631         serviceInfo.mHostNameBuffer     = name;
632         serviceInfo.mHostNameBufferSize = sizeof(name);
633         serviceInfo.mTxtData            = txtBuffer;
634         serviceInfo.mTxtDataSize        = sizeof(txtBuffer);
635 
636         if (otDnsServiceResponseGetServiceInfo(aResponse, &serviceInfo) == OT_ERROR_NONE)
637         {
638             OutputDnsServiceInfo(/* aIndentSize */ 0, serviceInfo);
639             OutputNewLine();
640         }
641     }
642 
643     OutputResult(aError);
644 }
645 
646 #endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
647 #endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
648 
649 #if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
650 
Process(Arg aArgs[])651 template <> otError Dns::Process<Cmd("server")>(Arg aArgs[])
652 {
653     otError error = OT_ERROR_NONE;
654 
655     if (aArgs[0].IsEmpty())
656     {
657         error = OT_ERROR_INVALID_ARGS;
658     }
659 #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
660     /**
661      * @cli dns server upstream
662      * @code
663      * dns server upstream
664      * Enabled
665      * Done
666      * @endcode
667      * @par api_copy
668      * #otDnssdUpstreamQueryIsEnabled
669      */
670     else if (aArgs[0] == "upstream")
671     {
672         /**
673          * @cli dns server upstream {enable|disable}
674          * @code
675          * dns server upstream enable
676          * Done
677          * @endcode
678          * @cparam dns server upstream @ca{enable|disable}
679          * @par api_copy
680          * #otDnssdUpstreamQuerySetEnabled
681          */
682         error = Interpreter::GetInterpreter().ProcessEnableDisable(aArgs + 1, otDnssdUpstreamQueryIsEnabled,
683                                                                    otDnssdUpstreamQuerySetEnabled);
684     }
685 #endif // OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
686     else
687     {
688         ExitNow(error = OT_ERROR_INVALID_COMMAND);
689     }
690 
691 exit:
692     return error;
693 }
694 
695 #endif // OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
696 
Process(Arg aArgs[])697 otError Dns::Process(Arg aArgs[])
698 {
699 #define CmdEntry(aCommandString)                           \
700     {                                                      \
701         aCommandString, &Dns::Process<Cmd(aCommandString)> \
702     }
703 
704     static constexpr Command kCommands[] = {
705 
706 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
707         CmdEntry("browse"),
708 #endif
709 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
710         CmdEntry("compression"),
711 #endif
712 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
713         CmdEntry("config"),
714         CmdEntry("resolve"),
715 #if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
716         CmdEntry("resolve4"),
717 #endif
718 #endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
719 #if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
720         CmdEntry("server"),
721 #endif
722 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
723         CmdEntry("service"),
724         CmdEntry("servicehost"),
725 #endif
726     };
727 
728 #undef CmdEntry
729 
730     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
731 
732     otError        error = OT_ERROR_INVALID_COMMAND;
733     const Command *command;
734 
735     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
736     {
737         OutputCommandTable(kCommands);
738         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
739     }
740 
741     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
742     VerifyOrExit(command != nullptr);
743 
744     error = (this->*command->mHandler)(aArgs + 1);
745 
746 exit:
747     return error;
748 }
749 
750 } // namespace Cli
751 } // namespace ot
752 
753 #endif // OPENTHREAD_CLI_DNS_ENABLE
754