1 /*
2  * Copyright (c) 2016 Intel Corporation
3  * Copyright (c) 2023 Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <stdio.h>
9 #include <strings.h>
10 
11 #include <zephyr/logging/log.h>
12 LOG_MODULE_DECLARE(net_shell);
13 
14 #include <zephyr/net/socket.h>
15 #include <zephyr/net/dns_resolve.h>
16 #include <zephyr/net/dns_sd.h>
17 #include "dns/dns_sd.h"
18 
19 #include "net_shell_private.h"
20 
21 #define DNS_TIMEOUT CONFIG_NET_SOCKETS_DNS_TIMEOUT
22 
23 #if defined(CONFIG_DNS_RESOLVER)
dns_result_cb(enum dns_resolve_status status,struct dns_addrinfo * info,void * user_data)24 static void dns_result_cb(enum dns_resolve_status status,
25 			  struct dns_addrinfo *info,
26 			  void *user_data)
27 {
28 	const struct shell *sh = user_data;
29 
30 	if (status == DNS_EAI_CANCELED) {
31 		PR_WARNING("dns: Timeout while resolving name.\n");
32 		return;
33 	}
34 
35 	if (status == DNS_EAI_INPROGRESS && info) {
36 #define MAX_STR_LEN CONFIG_DNS_RESOLVER_MAX_NAME_LEN
37 		char str[MAX_STR_LEN + 1];
38 
39 		switch (info->ai_family) {
40 		case NET_AF_INET:
41 			net_addr_ntop(NET_AF_INET,
42 				      &net_sin(&info->ai_addr)->sin_addr,
43 				      str, sizeof(str));
44 			break;
45 
46 		case NET_AF_INET6:
47 			net_addr_ntop(NET_AF_INET6,
48 				      &net_sin6(&info->ai_addr)->sin6_addr,
49 				      str, sizeof(str));
50 			break;
51 
52 		case NET_AF_LOCAL:
53 			/* service discovery */
54 			memset(str, 0, MAX_STR_LEN);
55 			memcpy(str, info->ai_canonname,
56 			       MIN(info->ai_addrlen, MAX_STR_LEN));
57 			break;
58 
59 		case NET_AF_UNSPEC:
60 			if (info->ai_extension == DNS_RESOLVE_TXT) {
61 				memset(str, 0, MAX_STR_LEN);
62 				memcpy(str, info->ai_txt.text,
63 				       MIN(info->ai_txt.textlen, MAX_STR_LEN));
64 				break;
65 			} else if (info->ai_extension == DNS_RESOLVE_SRV) {
66 				snprintf(str, sizeof(str), "%d %d %d %.*s",
67 					 info->ai_srv.priority,
68 					 info->ai_srv.weight,
69 					 info->ai_srv.port,
70 					 (int)info->ai_srv.targetlen,
71 					 info->ai_srv.target);
72 				break;
73 			}
74 
75 			__fallthrough;
76 		default:
77 			strncpy(str, "Invalid proto family", MAX_STR_LEN + 1);
78 			break;
79 		}
80 
81 		str[MAX_STR_LEN] = '\0';
82 
83 		PR("dns: %s\n", str);
84 		return;
85 	}
86 
87 	if (status == DNS_EAI_ALLDONE) {
88 		PR("dns: All results received\n");
89 		return;
90 	}
91 
92 	if (status == DNS_EAI_FAIL) {
93 		PR_WARNING("dns: No such name found.\n");
94 		return;
95 	}
96 
97 	PR_WARNING("dns: Unhandled status %d received (errno %d)\n", status, errno);
98 }
99 
100 K_MSGQ_DEFINE(dns_infoq, sizeof(struct dns_addrinfo), 3, 1);
101 
dns_service_cb(enum dns_resolve_status status,struct dns_addrinfo * info,void * user_data)102 static void dns_service_cb(enum dns_resolve_status status,
103 			   struct dns_addrinfo *info,
104 			   void *user_data)
105 {
106 	int r;
107 	const struct shell *sh = user_data;
108 
109 	if (status == DNS_EAI_CANCELED) {
110 		PR_WARNING("dns: Timeout while resolving service.\n");
111 		return;
112 	}
113 
114 	if ((status == DNS_EAI_INPROGRESS) && (info != NULL)) {
115 		/*
116 		 * Only queue results that can be further processed.
117 		 */
118 		r = k_msgq_put(&dns_infoq, info, K_NO_WAIT);
119 		if (r < 0) {
120 			PR_WARNING("dns: k_msgq_put error %d", r);
121 		}
122 	}
123 }
124 
printable_iface(const char * iface_name,const char * found,const char * not_found)125 static const char *printable_iface(const char *iface_name,
126 				   const char *found,
127 				   const char *not_found)
128 {
129 	if (iface_name[0] != '\0') {
130 		return found;
131 	}
132 
133 	return not_found;
134 }
135 
print_dns_info(const struct shell * sh,struct dns_resolve_context * ctx)136 static void print_dns_info(const struct shell *sh,
137 			   struct dns_resolve_context *ctx)
138 {
139 	int i, ret;
140 
141 	PR("DNS servers:\n");
142 
143 	for (i = 0; i < CONFIG_DNS_RESOLVER_MAX_SERVERS +
144 		     DNS_MAX_MCAST_SERVERS; i++) {
145 		char iface_name[NET_IFNAMSIZ] = { 0 };
146 
147 		if (ctx->servers[i].if_index > 0) {
148 			ret = net_if_get_name(
149 				net_if_get_by_index(ctx->servers[i].if_index),
150 				iface_name, sizeof(iface_name));
151 			if (ret < 0) {
152 				snprintk(iface_name, sizeof(iface_name), "%d",
153 					 ctx->servers[i].if_index);
154 			}
155 		}
156 
157 		if (ctx->servers[i].dns_server.sa_family == NET_AF_INET) {
158 			PR("\t%s:%u%s%s%s%s%s\n",
159 			   net_sprint_ipv4_addr(
160 				   &net_sin(&ctx->servers[i].dns_server)->
161 				   sin_addr),
162 			   net_ntohs(net_sin(&ctx->servers[i].dns_server)->sin_port),
163 			   printable_iface(iface_name, " via ", ""),
164 			   printable_iface(iface_name, iface_name, ""),
165 			   ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? " (" : "",
166 			   ctx->servers[i].source != DNS_SOURCE_UNKNOWN ?
167 					dns_get_source_str(ctx->servers[i].source) : "",
168 			   ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? ")" : "");
169 
170 		} else if (ctx->servers[i].dns_server.sa_family == NET_AF_INET6) {
171 			PR("\t[%s]:%u%s%s%s%s%s\n",
172 			   net_sprint_ipv6_addr(
173 				   &net_sin6(&ctx->servers[i].dns_server)->
174 				   sin6_addr),
175 			   net_ntohs(net_sin6(&ctx->servers[i].dns_server)->sin6_port),
176 			   printable_iface(iface_name, " via ", ""),
177 			   printable_iface(iface_name, iface_name, ""),
178 			   ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? " (" : "",
179 			   ctx->servers[i].source != DNS_SOURCE_UNKNOWN ?
180 					dns_get_source_str(ctx->servers[i].source) : "",
181 			   ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? ")" : "");
182 		}
183 	}
184 
185 	PR("Pending queries:\n");
186 
187 	for (i = 0; i < CONFIG_DNS_NUM_CONCUR_QUERIES; i++) {
188 		int32_t remaining;
189 
190 		if (!ctx->queries[i].cb || !ctx->queries[i].query) {
191 			continue;
192 		}
193 
194 		remaining = k_ticks_to_ms_ceil32(
195 			k_work_delayable_remaining_get(&ctx->queries[i].timer));
196 
197 		if (ctx->queries[i].query_type == DNS_QUERY_TYPE_A) {
198 			PR("\t%s[%u]: %s remaining %d\n", "IPv4",
199 			   ctx->queries[i].id,
200 			   ctx->queries[i].query,
201 			   remaining);
202 		} else if (ctx->queries[i].query_type == DNS_QUERY_TYPE_AAAA) {
203 			PR("\t%s[%u]: %s remaining %d\n", "IPv6",
204 			   ctx->queries[i].id,
205 			   ctx->queries[i].query,
206 			   remaining);
207 		} else if (ctx->queries[i].query_type == DNS_QUERY_TYPE_PTR) {
208 			PR("\t%s[%u]: %s remaining %d\n", "PTR",
209 			   ctx->queries[i].id,
210 			   ctx->queries[i].query,
211 			   remaining);
212 		} else {
213 			PR_WARNING("\tUnknown query type %d for query %s[%u] "
214 				   "remaining %d\n",
215 				   ctx->queries[i].query_type,
216 				   ctx->queries[i].query, ctx->queries[i].id,
217 				   remaining);
218 		}
219 	}
220 }
221 #endif
222 
cmd_net_dns_cancel(const struct shell * sh,size_t argc,char * argv[])223 static int cmd_net_dns_cancel(const struct shell *sh, size_t argc, char *argv[])
224 {
225 #if defined(CONFIG_DNS_RESOLVER)
226 	struct dns_resolve_context *ctx;
227 	int ret, i;
228 #endif
229 
230 	ARG_UNUSED(argc);
231 	ARG_UNUSED(argv);
232 
233 #if defined(CONFIG_DNS_RESOLVER)
234 	ctx = dns_resolve_get_default();
235 	if (!ctx) {
236 		PR_WARNING("No default DNS context found.\n");
237 		return -ENOEXEC;
238 	}
239 
240 	for (ret = 0, i = 0; i < CONFIG_DNS_NUM_CONCUR_QUERIES; i++) {
241 		if (!ctx->queries[i].cb) {
242 			continue;
243 		}
244 
245 		if (!dns_resolve_cancel(ctx, ctx->queries[i].id)) {
246 			ret++;
247 		}
248 	}
249 
250 	if (ret) {
251 		PR("Cancelled %d pending requests.\n", ret);
252 	} else {
253 		PR("No pending DNS requests.\n");
254 	}
255 #else
256 	PR_INFO("Set %s to enable %s support.\n", "CONFIG_DNS_RESOLVER",
257 		"DNS resolver");
258 #endif
259 
260 	return 0;
261 }
262 
cmd_net_dns_query(const struct shell * sh,size_t argc,char * argv[])263 static int cmd_net_dns_query(const struct shell *sh, size_t argc, char *argv[])
264 {
265 
266 #if defined(CONFIG_DNS_RESOLVER)
267 	struct dns_resolve_context *ctx;
268 	enum dns_query_type qtype = DNS_QUERY_TYPE_A;
269 	char *host, *type = NULL;
270 	int ret, arg = 1;
271 
272 	host = argv[arg++];
273 	if (!host) {
274 		PR_WARNING("Hostname not specified.\n");
275 		return -ENOEXEC;
276 	}
277 
278 	if (argv[arg]) {
279 		type = argv[arg];
280 	}
281 
282 	if (type) {
283 		if (strcasecmp(type, "A") == 0) {
284 			qtype = DNS_QUERY_TYPE_A;
285 			PR("IPv4 address query type\n");
286 		} else if (strcasecmp(type, "CNAME") == 0) {
287 			qtype = DNS_QUERY_TYPE_CNAME;
288 			PR("CNAME query type\n");
289 		} else if (strcasecmp(type, "PTR") == 0) {
290 			qtype = DNS_QUERY_TYPE_PTR;
291 			PR("Pointer query type\n");
292 		} else if (strcasecmp(type, "TXT") == 0) {
293 			qtype = DNS_QUERY_TYPE_TXT;
294 			PR("Text query type\n");
295 		} else if (strcasecmp(type, "AAAA") == 0) {
296 			qtype = DNS_QUERY_TYPE_AAAA;
297 			PR("IPv6 address query type\n");
298 		} else if (strcasecmp(type, "SRV") == 0) {
299 			qtype = DNS_QUERY_TYPE_SRV;
300 			PR("Service query type\n");
301 		} else {
302 			PR_WARNING("Unknown query type, specify either "
303 				   "A, CNAME, PTR, TXT, AAAA, or SRV\n");
304 			return -ENOEXEC;
305 		}
306 	}
307 
308 	ctx = dns_resolve_get_default();
309 	if (!ctx) {
310 		PR_WARNING("No default DNS context found.\n");
311 		return -ENOEXEC;
312 	}
313 
314 	ret = dns_resolve_name(ctx, host, qtype, NULL, dns_result_cb,
315 				(void *)sh, DNS_TIMEOUT);
316 	if (ret < 0) {
317 		PR_WARNING("Cannot resolve '%s' (%d)\n", host, ret);
318 	} else {
319 		PR("Query for '%s' sent.\n", host);
320 	}
321 #else
322 	PR_INFO("DNS resolver not supported. Set CONFIG_DNS_RESOLVER to "
323 		"enable it.\n");
324 #endif
325 
326 	return 0;
327 }
328 
cmd_net_dns(const struct shell * sh,size_t argc,char * argv[])329 static int cmd_net_dns(const struct shell *sh, size_t argc, char *argv[])
330 {
331 #if defined(CONFIG_DNS_RESOLVER)
332 	struct dns_resolve_context *ctx;
333 #endif
334 
335 #if defined(CONFIG_DNS_RESOLVER)
336 	if (argv[1]) {
337 		/* So this is a query then */
338 		cmd_net_dns_query(sh, argc, argv);
339 		return 0;
340 	}
341 
342 	/* DNS status */
343 	ctx = dns_resolve_get_default();
344 	if (!ctx) {
345 		PR_WARNING("No default DNS context found.\n");
346 		return -ENOEXEC;
347 	}
348 
349 	print_dns_info(sh, ctx);
350 #else
351 	PR_INFO("DNS resolver not supported. Set CONFIG_DNS_RESOLVER to "
352 		"enable it.\n");
353 #endif
354 
355 	return 0;
356 }
357 
cmd_net_dns_list(const struct shell * sh,size_t argc,char * argv[])358 static int cmd_net_dns_list(const struct shell *sh, size_t argc, char *argv[])
359 {
360 #if defined(CONFIG_DNS_SD)
361 #define MAX_PORT_LEN 6
362 	char buf[MAX_PORT_LEN];
363 	int n_records = 0;
364 
365 	DNS_SD_FOREACH(record) {
366 		if (!dns_sd_rec_is_valid(record)) {
367 			continue;
368 		}
369 
370 		if (n_records == 0) {
371 			PR("     DNS service records\n");
372 		}
373 
374 		++n_records;
375 
376 		if (record->port != NULL) {
377 			snprintk(buf, sizeof(buf), "%u", net_ntohs(*record->port));
378 		}
379 
380 		PR("[%2d] %s.%s%s%s%s%s%s%s\n",
381 		   n_records,
382 		   record->instance != NULL ? record->instance : "",
383 		   record->service != NULL ? record->service : "",
384 		   record->proto != NULL ? "." : "",
385 		   record->proto != NULL ? record->proto : "",
386 		   record->domain != NULL ? "." : "",
387 		   record->domain != NULL ? record->domain : "",
388 		   record->port != NULL ? ":" : "",
389 		   record->port != NULL ? buf : "");
390 	}
391 
392 	if (n_records == 0) {
393 		PR("No DNS service records found.\n");
394 		return 0;
395 	}
396 #else
397 	PR_INFO("DNS service discovery not supported. Set CONFIG_DNS_SD to "
398 		"enable it.\n");
399 #endif
400 
401 	return 0;
402 }
403 
cmd_net_dns_service(const struct shell * sh,size_t argc,char * argv[])404 static int cmd_net_dns_service(const struct shell *sh, size_t argc, char *argv[])
405 {
406 #if defined(CONFIG_DNS_RESOLVER)
407 	struct dns_resolve_context *ctx;
408 	char *cp;
409 	char *service;
410 	uint16_t port;
411 	uint16_t dns_id;
412 	int ret, arg = 1;
413 
414 	service = argv[arg++];
415 	if (service == NULL) {
416 		PR_WARNING("Service not specified.\n");
417 		return -ENOEXEC;
418 	}
419 
420 	/* remove any lingering info data */
421 	k_msgq_purge(&dns_infoq);
422 
423 	ctx = dns_resolve_get_default();
424 	if (ctx == NULL) {
425 		PR_WARNING("No default DNS context found.\n");
426 		return -ENOEXEC;
427 	}
428 
429 	ret = dns_resolve_service(ctx, service, &dns_id, dns_service_cb,
430 				(void *)sh, DNS_TIMEOUT);
431 	if (ret < 0) {
432 		PR_WARNING("Cannot resolve '%s' (%d)\n", service, ret);
433 		return ret;
434 	}
435 
436 	PR("Resolve for '%s' service sent.\n", service);
437 	port = 0;
438 	for (;;) {
439 		struct dns_addrinfo info;
440 		enum dns_query_type qtype;
441 		char query[DNS_MAX_NAME_SIZE + 1];
442 		union {
443 			char in4[NET_INET_ADDRSTRLEN];
444 			char in6[NET_INET6_ADDRSTRLEN];
445 		} str;
446 
447 		ret = k_msgq_get(&dns_infoq, &info, K_MSEC(DNS_TIMEOUT));
448 		if (ret < 0) {
449 			/* just assume a timeout so no more data to process */
450 			break;
451 		}
452 
453 		switch (info.ai_family) {
454 		case NET_AF_INET:
455 			cp = net_addr_ntop(NET_AF_INET,
456 					   &net_sin(&info.ai_addr)->sin_addr,
457 					   str.in4, sizeof(str.in4));
458 			PR("AF_INET %s:%u\n", cp ? cp : "<invalid>", port);
459 			break;
460 
461 		case NET_AF_INET6:
462 			cp = net_addr_ntop(NET_AF_INET6,
463 					   &net_sin6(&info.ai_addr)->sin6_addr,
464 					   str.in6, sizeof(str.in6));
465 			PR("AF_INET6 [%s]:%u\n", cp ? cp : "<invalid>", port);
466 			break;
467 
468 		case NET_AF_LOCAL:
469 			PR("AF_LOCAL %.*s\n",
470 			   (int)info.ai_addrlen, info.ai_canonname);
471 
472 			snprintf(query, sizeof(query), "%.*s",
473 				 info.ai_addrlen, info.ai_canonname);
474 
475 			qtype = DNS_QUERY_TYPE_SRV;
476 			ret = dns_resolve_name(ctx, query, qtype,
477 					       &dns_id,
478 					       dns_service_cb, (void *)sh,
479 					       DNS_TIMEOUT);
480 			if (ret < 0) {
481 				return ret;
482 			}
483 			break;
484 
485 		case NET_AF_UNSPEC:
486 			if (info.ai_extension == DNS_RESOLVE_SRV) {
487 				PR("SRV %d %d %d %.*s\n",
488 				   info.ai_srv.priority,
489 				   info.ai_srv.weight,
490 				   info.ai_srv.port,
491 				   (int)info.ai_srv.targetlen,
492 				   info.ai_srv.target);
493 
494 				port = info.ai_srv.port;
495 
496 				snprintf(query, sizeof(query), "%.*s",
497 					(int)info.ai_srv.targetlen,
498 					info.ai_srv.target);
499 
500 				/*
501 				 * Sending a query for both AAAA and A records
502 				 * should be ok, but the resolver doesn't
503 				 * gracefully handle the query for different
504 				 * types.
505 				 */
506 				qtype = DNS_QUERY_TYPE_AAAA;
507 				ret = dns_resolve_name(ctx, query, qtype,
508 						       &dns_id,
509 						       dns_service_cb,
510 						       (void *)sh,
511 						       DNS_TIMEOUT);
512 				if (ret < 0) {
513 					return ret;
514 				}
515 				break;
516 			}
517 
518 			__fallthrough;
519 		default:
520 			PR_WARNING("dns: unhandled info %u on msgq\n", info.ai_family);
521 			break;
522 		}
523 	}
524 #else
525 	PR_INFO("DNS resolver not supported. Set CONFIG_DNS_RESOLVER to "
526 		"enable it.\n");
527 #endif
528 
529 	return 0;
530 }
531 
532 SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_dns,
533 	SHELL_CMD(cancel, NULL, "Cancel all pending requests.",
534 		  cmd_net_dns_cancel),
535 	SHELL_CMD(query, NULL,
536 		  "'net dns <hostname> [A or AAAA]' queries IPv4 address "
537 		  "(default) or IPv6 address for a host name.",
538 		  cmd_net_dns_query),
539 	SHELL_CMD(list, NULL,
540 		  "List local DNS service records.",
541 		  cmd_net_dns_list),
542 	SHELL_CMD(service, NULL,
543 		  "'net dns service <service-description>\n"
544 		  "Execute DNS service discovery query.",
545 		  cmd_net_dns_service),
546 	SHELL_SUBCMD_SET_END
547 );
548 
549 SHELL_SUBCMD_ADD((net), dns, &net_cmd_dns,
550 		 "Show how DNS is configured. Optionally do a query using a given name.",
551 		 cmd_net_dns, 1, 2);
552