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 = 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      * ServiceMode: srv_txt_opt
165      * Nat64Mode: allow
166      * TransportProtocol: udp
167      * Done
168      * @endcode
169      * @code
170      * dns config fd00::2
171      * Done
172      * @endcode
173      * @code
174      * dns config
175      * Server: [fd00:0:0:0:0:0:0:2]:53
176      * ResponseTimeout: 6000 ms
177      * MaxTxAttempts: 3
178      * RecursionDesired: yes
179      * ServiceMode: srv_txt_opt
180      * Nat64Mode: allow
181      * TransportProtocol: udp
182      * Done
183      * @endcode
184      * @par api_copy
185      * #otDnsClientSetDefaultConfig
186      * @cparam dns config [@ca{dns-server-IP}] [@ca{dns-server-port}] <!--
187      * -->                [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] <!--
188      * -->                [@ca{recursion-desired-boolean}] [@ca{service-mode}] <!--
189      * -->                [@ca{protocol}]
190      * @par
191      * We can leave some of the fields as unspecified (or use value zero). The
192      * unspecified fields are replaced by the corresponding OT config option
193      * definitions `OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT` to form the default
194      * query config.
195      * `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` is required.
196      */
197     else
198     {
199         otDnsQueryConfig  queryConfig;
200         otDnsQueryConfig *config = &queryConfig;
201 
202         SuccessOrExit(error = GetDnsConfig(aArgs, config));
203         otDnsClientSetDefaultConfig(GetInstancePtr(), config);
204     }
205 
206 exit:
207     return error;
208 }
209 
210 /**
211  * @cli dns resolve
212  * @code
213  * dns resolve ipv6.google.com
214  * DNS response for ipv6.google.com - 2a00:1450:401b:801:0:0:0:200e TTL: 300
215  * @endcode
216  * @code
217  * dns resolve example.com 8.8.8.8
218  * Synthesized IPv6 DNS server address: fdde:ad00:beef:2:0:0:808:808
219  * DNS response for example.com. - fd4c:9574:3720:2:0:0:5db8:d822 TTL:20456
220  * Done
221  * @endcode
222  * @cparam dns resolve @ca{hostname} [@ca{dns-server-IP}] <!--
223  * -->                 [@ca{dns-server-port}] [@ca{response-timeout-ms}] <!--
224  * -->                 [@ca{max-tx-attempts}] [@ca{recursion-desired-boolean}]
225  * @par api_copy
226  * #otDnsClientResolveAddress
227  * @par
228  * Send DNS Query to obtain IPv6 address for given hostname.
229  * @par
230  * The parameters after hostname are optional. Any unspecified (or zero) value
231  * for these optional parameters is replaced by the value from the current default
232  * config (dns config).
233  * @par
234  * The DNS server IP can be an IPv4 address, which will be synthesized to an
235  * IPv6 address using the preferred NAT64 prefix from the network data.
236  * @par
237  * Note: The command will return InvalidState when the DNS server IP is an IPv4
238  * address but the preferred NAT64 prefix is unavailable.
239  * `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` is required.
240  */
Process(Arg aArgs[])241 template <> otError Dns::Process<Cmd("resolve")>(Arg aArgs[])
242 {
243     otError           error = OT_ERROR_NONE;
244     otDnsQueryConfig  queryConfig;
245     otDnsQueryConfig *config = &queryConfig;
246 
247     VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
248     SuccessOrExit(error = GetDnsConfig(aArgs + 1, config));
249     SuccessOrExit(error = otDnsClientResolveAddress(GetInstancePtr(), aArgs[0].GetCString(), &HandleDnsAddressResponse,
250                                                     this, config));
251     error = OT_ERROR_PENDING;
252 
253 exit:
254     return error;
255 }
256 
257 #if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
Process(Arg aArgs[])258 template <> otError Dns::Process<Cmd("resolve4")>(Arg aArgs[])
259 {
260     otError           error = OT_ERROR_NONE;
261     otDnsQueryConfig  queryConfig;
262     otDnsQueryConfig *config = &queryConfig;
263 
264     VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
265     SuccessOrExit(error = GetDnsConfig(aArgs + 1, config));
266     SuccessOrExit(error = otDnsClientResolveIp4Address(GetInstancePtr(), aArgs[0].GetCString(),
267                                                        &HandleDnsAddressResponse, this, config));
268     error = OT_ERROR_PENDING;
269 
270 exit:
271     return error;
272 }
273 #endif
274 
275 #if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
276 
277 /**
278  * @cli dns browse
279  * @code
280  * dns browse _service._udp.example.com
281  * DNS browse response for _service._udp.example.com.
282  * inst1
283  *     Port:1234, Priority:1, Weight:2, TTL:7200
284  *     Host:host.example.com.
285  *     HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200
286  *     TXT:[a=6531, b=6c12] TTL:7300
287  * instance2
288  *     Port:1234, Priority:1, Weight:2, TTL:7200
289  *     Host:host.example.com.
290  *     HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200
291  *     TXT:[a=1234] TTL:7300
292  * Done
293  * @endcode
294  * @code
295  * dns browse _airplay._tcp.default.service.arpa
296  * DNS browse response for _airplay._tcp.default.service.arpa.
297  * Mac mini
298  *     Port:7000, Priority:0, Weight:0, TTL:10
299  *     Host:Mac-mini.default.service.arpa.
300  *     HostAddress:fd97:739d:386a:1:1c2e:d83c:fcbe:9cf4 TTL:10
301  * Done
302  * @endcode
303  * @cparam dns browse @ca{service-name} [@ca{dns-server-IP}] [@ca{dns-server-port}] <!--
304  * -->                [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] <!--
305  * -->                [@ca{recursion-desired-boolean}]
306  * @sa otDnsClientBrowse
307  * @par
308  * Send a browse (service instance enumeration) DNS query to get the list of services for
309  * given service-name
310  * @par
311  * The parameters after `service-name` are optional. Any unspecified (or zero) value
312  * for these optional parameters is replaced by the value from the current default
313  * config (`dns config`).
314  * @par
315  * Note: The DNS server IP can be an IPv4 address, which will be synthesized to an IPv6
316  * address using the preferred NAT64 prefix from the network data. The command will return
317  * `InvalidState` when the DNS server IP is an IPv4 address but the preferred NAT64 prefix
318  * is unavailable. When testing DNS-SD discovery proxy, the zone is not `local` and
319  * instead should be `default.service.arpa`.
320  * `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is required.
321  */
Process(Arg aArgs[])322 template <> otError Dns::Process<Cmd("browse")>(Arg aArgs[])
323 {
324     otError           error = OT_ERROR_NONE;
325     otDnsQueryConfig  queryConfig;
326     otDnsQueryConfig *config = &queryConfig;
327 
328     VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
329     SuccessOrExit(error = GetDnsConfig(aArgs + 1, config));
330     SuccessOrExit(
331         error = otDnsClientBrowse(GetInstancePtr(), aArgs[0].GetCString(), &HandleDnsBrowseResponse, this, config));
332     error = OT_ERROR_PENDING;
333 
334 exit:
335     return error;
336 }
337 
338 /**
339  * @cli dns service
340  * @cparam dns service @ca{service-instance-label} @ca{service-name} <!--
341  * -->                 [@ca{DNS-server-IP}] [@ca{DNS-server-port}] <!--
342  * -->                 [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] <!--
343  * -->                 [@ca{recursion-desired-boolean}]
344  * @par api_copy
345  * #otDnsClientResolveService
346  * @par
347  * Send a service instance resolution DNS query for a given service instance.
348  * Service instance label is provided first, followed by the service name
349  * (note that service instance label can contain dot '.' character).
350  * @par
351  * The parameters after `service-name` are optional. Any unspecified (or zero)
352  * value for these optional parameters is replaced by the value from the
353  * current default config (`dns config`).
354  * @par
355  * Note: The DNS server IP can be an IPv4 address, which will be synthesized
356  * to an IPv6 address using the preferred NAT64 prefix from the network data.
357  * The command will return `InvalidState` when the DNS server IP is an IPv4
358  * address but the preferred NAT64 prefix is unavailable.
359  * `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is required.
360  */
Process(Arg aArgs[])361 template <> otError Dns::Process<Cmd("service")>(Arg aArgs[])
362 {
363     return ProcessService(aArgs, otDnsClientResolveService);
364 }
365 
366 /**
367  * @cli dns servicehost
368  * @cparam dns servicehost @ca{service-instance-label} @ca{service-name} <!--
369  * -->                 [@ca{DNS-server-IP}] [@ca{DNS-server-port}] <!--
370  * -->                 [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] <!--
371  * -->                 [@ca{recursion-desired-boolean}]
372  * @par api_copy
373  * #otDnsClientResolveServiceAndHostAddress
374  * @par
375  * Send a service instance resolution DNS query for a given service instance
376  * with potential follow-up host name resolution.
377  * Service instance label is provided first, followed by the service name
378  * (note that service instance label can contain dot '.' character).
379  * @par
380  * The parameters after `service-name` are optional. Any unspecified (or zero)
381  * value for these optional parameters is replaced by the value from the
382  * current default config (`dns config`).
383  * @par
384  * Note: The DNS server IP can be an IPv4 address, which will be synthesized
385  * to an IPv6 address using the preferred NAT64 prefix from the network data.
386  * The command will return `InvalidState` when the DNS server IP is an IPv4
387  * address but the preferred NAT64 prefix is unavailable.
388  * `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is required.
389  */
Process(Arg aArgs[])390 template <> otError Dns::Process<Cmd("servicehost")>(Arg aArgs[])
391 {
392     return ProcessService(aArgs, otDnsClientResolveServiceAndHostAddress);
393 }
394 
ProcessService(Arg aArgs[],ResolveServiceFn aResolveServiceFn)395 otError Dns::ProcessService(Arg aArgs[], ResolveServiceFn aResolveServiceFn)
396 {
397     otError           error = OT_ERROR_NONE;
398     otDnsQueryConfig  queryConfig;
399     otDnsQueryConfig *config = &queryConfig;
400 
401     VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
402     SuccessOrExit(error = GetDnsConfig(aArgs + 2, config));
403     SuccessOrExit(error = aResolveServiceFn(GetInstancePtr(), aArgs[0].GetCString(), aArgs[1].GetCString(),
404                                             &HandleDnsServiceResponse, this, config));
405     error = OT_ERROR_PENDING;
406 
407 exit:
408     return error;
409 }
410 
411 #endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
412 
413 //----------------------------------------------------------------------------------------------------------------------
414 
OutputResult(otError aError)415 void Dns::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); }
416 
GetDnsConfig(Arg aArgs[],otDnsQueryConfig * & aConfig)417 otError Dns::GetDnsConfig(Arg aArgs[], otDnsQueryConfig *&aConfig)
418 {
419     // This method gets the optional DNS config from `aArgs[]`.
420     // The format: `[server IP address] [server port] [timeout]
421     // [max tx attempt] [recursion desired] [service mode]
422     // [transport]`
423 
424     otError error = OT_ERROR_NONE;
425     bool    recursionDesired;
426     bool    nat64Synth;
427 
428     ClearAllBytes(*aConfig);
429 
430     VerifyOrExit(!aArgs[0].IsEmpty(), aConfig = nullptr);
431 
432     SuccessOrExit(error = ParseToIp6Address(GetInstancePtr(), aArgs[0], aConfig->mServerSockAddr.mAddress, nat64Synth));
433     if (nat64Synth)
434     {
435         OutputFormat("Synthesized IPv6 DNS server address: ");
436         OutputIp6AddressLine(aConfig->mServerSockAddr.mAddress);
437     }
438 
439     VerifyOrExit(!aArgs[1].IsEmpty());
440     SuccessOrExit(error = aArgs[1].ParseAsUint16(aConfig->mServerSockAddr.mPort));
441 
442     VerifyOrExit(!aArgs[2].IsEmpty());
443     SuccessOrExit(error = aArgs[2].ParseAsUint32(aConfig->mResponseTimeout));
444 
445     VerifyOrExit(!aArgs[3].IsEmpty());
446     SuccessOrExit(error = aArgs[3].ParseAsUint8(aConfig->mMaxTxAttempts));
447 
448     VerifyOrExit(!aArgs[4].IsEmpty());
449     SuccessOrExit(error = aArgs[4].ParseAsBool(recursionDesired));
450     aConfig->mRecursionFlag = recursionDesired ? OT_DNS_FLAG_RECURSION_DESIRED : OT_DNS_FLAG_NO_RECURSION;
451 
452     VerifyOrExit(!aArgs[5].IsEmpty());
453     SuccessOrExit(error = ParseDnsServiceMode(aArgs[5], aConfig->mServiceMode));
454 
455     VerifyOrExit(!aArgs[6].IsEmpty());
456 
457     if (aArgs[6] == "tcp")
458     {
459         aConfig->mTransportProto = OT_DNS_TRANSPORT_TCP;
460     }
461     else if (aArgs[6] == "udp")
462     {
463         aConfig->mTransportProto = OT_DNS_TRANSPORT_UDP;
464     }
465     else
466     {
467         error = OT_ERROR_INVALID_ARGS;
468     }
469 
470 exit:
471     return error;
472 }
473 
474 const char *const Dns::kServiceModeStrings[] = {
475     "unspec",      // OT_DNS_SERVICE_MODE_UNSPECIFIED      (0)
476     "srv",         // OT_DNS_SERVICE_MODE_SRV              (1)
477     "txt",         // OT_DNS_SERVICE_MODE_TXT              (2)
478     "srv_txt",     // OT_DNS_SERVICE_MODE_SRV_TXT          (3)
479     "srv_txt_sep", // OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE (4)
480     "srv_txt_opt", // OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE (5)
481 };
482 
483 static_assert(OT_DNS_SERVICE_MODE_UNSPECIFIED == 0, "OT_DNS_SERVICE_MODE_UNSPECIFIED value is incorrect");
484 static_assert(OT_DNS_SERVICE_MODE_SRV == 1, "OT_DNS_SERVICE_MODE_SRV value is incorrect");
485 static_assert(OT_DNS_SERVICE_MODE_TXT == 2, "OT_DNS_SERVICE_MODE_TXT value is incorrect");
486 static_assert(OT_DNS_SERVICE_MODE_SRV_TXT == 3, "OT_DNS_SERVICE_MODE_SRV_TXT value is incorrect");
487 static_assert(OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE == 4, "OT_DNS_SERVICE_MODE_SRV_TXT_SEPARATE value is incorrect");
488 static_assert(OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE == 5, "OT_DNS_SERVICE_MODE_SRV_TXT_OPTIMIZE value is incorrect");
489 
DnsConfigServiceModeToString(otDnsServiceMode aMode) const490 const char *Dns::DnsConfigServiceModeToString(otDnsServiceMode aMode) const
491 {
492     return Stringify(aMode, kServiceModeStrings);
493 }
494 
ParseDnsServiceMode(const Arg & aArg,otDnsServiceMode & aMode) const495 otError Dns::ParseDnsServiceMode(const Arg &aArg, otDnsServiceMode &aMode) const
496 {
497     otError error = OT_ERROR_NONE;
498 
499     if (aArg == "def")
500     {
501         aMode = OT_DNS_SERVICE_MODE_UNSPECIFIED;
502         ExitNow();
503     }
504 
505     for (size_t index = 0; index < OT_ARRAY_LENGTH(kServiceModeStrings); index++)
506     {
507         if (aArg == kServiceModeStrings[index])
508         {
509             aMode = static_cast<otDnsServiceMode>(index);
510             ExitNow();
511         }
512     }
513 
514     error = OT_ERROR_INVALID_ARGS;
515 
516 exit:
517     return error;
518 }
519 
HandleDnsAddressResponse(otError aError,const otDnsAddressResponse * aResponse,void * aContext)520 void Dns::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext)
521 {
522     static_cast<Dns *>(aContext)->HandleDnsAddressResponse(aError, aResponse);
523 }
524 
HandleDnsAddressResponse(otError aError,const otDnsAddressResponse * aResponse)525 void Dns::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse)
526 {
527     char         hostName[OT_DNS_MAX_NAME_SIZE];
528     otIp6Address address;
529     uint32_t     ttl;
530 
531     IgnoreError(otDnsAddressResponseGetHostName(aResponse, hostName, sizeof(hostName)));
532 
533     OutputFormat("DNS response for %s - ", hostName);
534 
535     if (aError == OT_ERROR_NONE)
536     {
537         uint16_t index = 0;
538 
539         while (otDnsAddressResponseGetAddress(aResponse, index, &address, &ttl) == OT_ERROR_NONE)
540         {
541             OutputIp6Address(address);
542             OutputFormat(" TTL:%lu ", ToUlong(ttl));
543             index++;
544         }
545     }
546 
547     OutputNewLine();
548     OutputResult(aError);
549 }
550 
551 #if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
552 
OutputDnsServiceInfo(uint8_t aIndentSize,const otDnsServiceInfo & aServiceInfo)553 void Dns::OutputDnsServiceInfo(uint8_t aIndentSize, const otDnsServiceInfo &aServiceInfo)
554 {
555     OutputLine(aIndentSize, "Port:%d, Priority:%d, Weight:%d, TTL:%lu", aServiceInfo.mPort, aServiceInfo.mPriority,
556                aServiceInfo.mWeight, ToUlong(aServiceInfo.mTtl));
557     OutputLine(aIndentSize, "Host:%s", aServiceInfo.mHostNameBuffer);
558     OutputFormat(aIndentSize, "HostAddress:");
559     OutputIp6Address(aServiceInfo.mHostAddress);
560     OutputLine(" TTL:%lu", ToUlong(aServiceInfo.mHostAddressTtl));
561     OutputFormat(aIndentSize, "TXT:");
562 
563     if (!aServiceInfo.mTxtDataTruncated)
564     {
565         OutputDnsTxtData(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize);
566     }
567     else
568     {
569         OutputFormat("[");
570         OutputBytes(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize);
571         OutputFormat("...]");
572     }
573 
574     OutputLine(" TTL:%lu", ToUlong(aServiceInfo.mTxtDataTtl));
575 }
576 
HandleDnsBrowseResponse(otError aError,const otDnsBrowseResponse * aResponse,void * aContext)577 void Dns::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext)
578 {
579     static_cast<Dns *>(aContext)->HandleDnsBrowseResponse(aError, aResponse);
580 }
581 
HandleDnsBrowseResponse(otError aError,const otDnsBrowseResponse * aResponse)582 void Dns::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse)
583 {
584     char             name[OT_DNS_MAX_NAME_SIZE];
585     char             label[OT_DNS_MAX_LABEL_SIZE];
586     uint8_t          txtBuffer[kMaxTxtDataSize];
587     otDnsServiceInfo serviceInfo;
588 
589     IgnoreError(otDnsBrowseResponseGetServiceName(aResponse, name, sizeof(name)));
590 
591     OutputLine("DNS browse response for %s", name);
592 
593     if (aError == OT_ERROR_NONE)
594     {
595         uint16_t index = 0;
596 
597         while (otDnsBrowseResponseGetServiceInstance(aResponse, index, label, sizeof(label)) == OT_ERROR_NONE)
598         {
599             OutputLine("%s", label);
600             index++;
601 
602             serviceInfo.mHostNameBuffer     = name;
603             serviceInfo.mHostNameBufferSize = sizeof(name);
604             serviceInfo.mTxtData            = txtBuffer;
605             serviceInfo.mTxtDataSize        = sizeof(txtBuffer);
606 
607             if (otDnsBrowseResponseGetServiceInfo(aResponse, label, &serviceInfo) == OT_ERROR_NONE)
608             {
609                 OutputDnsServiceInfo(kIndentSize, serviceInfo);
610             }
611 
612             OutputNewLine();
613         }
614     }
615 
616     OutputResult(aError);
617 }
618 
HandleDnsServiceResponse(otError aError,const otDnsServiceResponse * aResponse,void * aContext)619 void Dns::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext)
620 {
621     static_cast<Dns *>(aContext)->HandleDnsServiceResponse(aError, aResponse);
622 }
623 
HandleDnsServiceResponse(otError aError,const otDnsServiceResponse * aResponse)624 void Dns::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse)
625 {
626     char             name[OT_DNS_MAX_NAME_SIZE];
627     char             label[OT_DNS_MAX_LABEL_SIZE];
628     uint8_t          txtBuffer[kMaxTxtDataSize];
629     otDnsServiceInfo serviceInfo;
630 
631     IgnoreError(otDnsServiceResponseGetServiceName(aResponse, label, sizeof(label), name, sizeof(name)));
632 
633     OutputLine("DNS service resolution response for %s for service %s", label, name);
634 
635     if (aError == OT_ERROR_NONE)
636     {
637         serviceInfo.mHostNameBuffer     = name;
638         serviceInfo.mHostNameBufferSize = sizeof(name);
639         serviceInfo.mTxtData            = txtBuffer;
640         serviceInfo.mTxtDataSize        = sizeof(txtBuffer);
641 
642         if (otDnsServiceResponseGetServiceInfo(aResponse, &serviceInfo) == OT_ERROR_NONE)
643         {
644             OutputDnsServiceInfo(/* aIndentSize */ 0, serviceInfo);
645             OutputNewLine();
646         }
647     }
648 
649     OutputResult(aError);
650 }
651 
652 #endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
653 #endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
654 
655 #if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
656 
Process(Arg aArgs[])657 template <> otError Dns::Process<Cmd("server")>(Arg aArgs[])
658 {
659     otError error = OT_ERROR_NONE;
660 
661     if (aArgs[0].IsEmpty())
662     {
663         error = OT_ERROR_INVALID_ARGS;
664     }
665 #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
666     /**
667      * @cli dns server upstream
668      * @code
669      * dns server upstream
670      * Enabled
671      * Done
672      * @endcode
673      * @par api_copy
674      * #otDnssdUpstreamQueryIsEnabled
675      */
676     else if (aArgs[0] == "upstream")
677     {
678         /**
679          * @cli dns server upstream {enable|disable}
680          * @code
681          * dns server upstream enable
682          * Done
683          * @endcode
684          * @cparam dns server upstream @ca{enable|disable}
685          * @par api_copy
686          * #otDnssdUpstreamQuerySetEnabled
687          */
688         error = ProcessEnableDisable(aArgs + 1, otDnssdUpstreamQueryIsEnabled, otDnssdUpstreamQuerySetEnabled);
689     }
690 #endif // OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
691     else
692     {
693         ExitNow(error = OT_ERROR_INVALID_COMMAND);
694     }
695 
696 exit:
697     return error;
698 }
699 
700 #endif // OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
701 
Process(Arg aArgs[])702 otError Dns::Process(Arg aArgs[])
703 {
704 #define CmdEntry(aCommandString)                           \
705     {                                                      \
706         aCommandString, &Dns::Process<Cmd(aCommandString)> \
707     }
708 
709     static constexpr Command kCommands[] = {
710 
711 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
712         CmdEntry("browse"),
713 #endif
714 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
715         CmdEntry("compression"),
716 #endif
717 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
718         CmdEntry("config"),
719         CmdEntry("resolve"),
720 #if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
721         CmdEntry("resolve4"),
722 #endif
723 #endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
724 #if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
725         CmdEntry("server"),
726 #endif
727 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
728         CmdEntry("service"),
729         CmdEntry("servicehost"),
730 #endif
731     };
732 
733 #undef CmdEntry
734 
735     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
736 
737     otError        error = OT_ERROR_INVALID_COMMAND;
738     const Command *command;
739 
740     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
741     {
742         OutputCommandTable(kCommands);
743         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
744     }
745 
746     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
747     VerifyOrExit(command != nullptr);
748 
749     error = (this->*command->mHandler)(aArgs + 1);
750 
751 exit:
752     return error;
753 }
754 
755 } // namespace Cli
756 } // namespace ot
757 
758 #endif // OPENTHREAD_CLI_DNS_ENABLE
759