| /* |
| * Copyright (c) 2016 Intel Corporation |
| * Copyright (c) 2023 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdio.h> |
| #include <strings.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_DECLARE(net_shell); |
| |
| #include <zephyr/net/socket.h> |
| #include <zephyr/net/dns_resolve.h> |
| #include <zephyr/net/dns_sd.h> |
| #include "dns/dns_sd.h" |
| |
| #include "net_shell_private.h" |
| |
| #define DNS_TIMEOUT CONFIG_NET_SOCKETS_DNS_TIMEOUT |
| |
| #if defined(CONFIG_DNS_RESOLVER) |
| static void dns_result_cb(enum dns_resolve_status status, |
| struct dns_addrinfo *info, |
| void *user_data) |
| { |
| const struct shell *sh = user_data; |
| |
| if (status == DNS_EAI_CANCELED) { |
| PR_WARNING("dns: Timeout while resolving name.\n"); |
| return; |
| } |
| |
| if (status == DNS_EAI_INPROGRESS && info) { |
| #define MAX_STR_LEN CONFIG_DNS_RESOLVER_MAX_NAME_LEN |
| char str[MAX_STR_LEN + 1]; |
| |
| switch (info->ai_family) { |
| case NET_AF_INET: |
| net_addr_ntop(NET_AF_INET, |
| &net_sin(net_sad(&info->ai_addr_storage))->sin_addr, |
| str, sizeof(str)); |
| break; |
| |
| case NET_AF_INET6: |
| net_addr_ntop(NET_AF_INET6, |
| &net_sin6(net_sad(&info->ai_addr_storage))->sin6_addr, |
| str, sizeof(str)); |
| break; |
| |
| case NET_AF_LOCAL: |
| /* service discovery */ |
| memset(str, 0, MAX_STR_LEN); |
| memcpy(str, info->ai_canonname, |
| MIN(info->ai_addrlen, MAX_STR_LEN)); |
| break; |
| |
| case NET_AF_UNSPEC: |
| if (info->ai_extension == DNS_RESOLVE_TXT) { |
| memset(str, 0, MAX_STR_LEN); |
| memcpy(str, info->ai_txt.text, |
| MIN(info->ai_txt.textlen, MAX_STR_LEN)); |
| break; |
| } else if (info->ai_extension == DNS_RESOLVE_SRV) { |
| snprintf(str, sizeof(str), "%d %d %d %.*s", |
| info->ai_srv.priority, |
| info->ai_srv.weight, |
| info->ai_srv.port, |
| (int)info->ai_srv.targetlen, |
| info->ai_srv.target); |
| break; |
| } |
| |
| __fallthrough; |
| default: |
| strncpy(str, "Invalid proto family", MAX_STR_LEN + 1); |
| break; |
| } |
| |
| str[MAX_STR_LEN] = '\0'; |
| |
| PR("dns: %s\n", str); |
| return; |
| } |
| |
| if (status == DNS_EAI_ALLDONE) { |
| PR("dns: All results received\n"); |
| return; |
| } |
| |
| if (status == DNS_EAI_FAIL) { |
| PR_WARNING("dns: No such name found.\n"); |
| return; |
| } |
| |
| PR_WARNING("dns: Unhandled status %d received (errno %d)\n", status, errno); |
| } |
| |
| K_MSGQ_DEFINE_STATIC_TYPE(dns_infoq, struct dns_addrinfo, CONFIG_NET_SHELL_DNS_RESOLVER_QUEUE_SIZE); |
| |
| static void dns_service_cb(enum dns_resolve_status status, |
| struct dns_addrinfo *info, |
| void *user_data) |
| { |
| int r; |
| const struct shell *sh = user_data; |
| |
| if (status == DNS_EAI_CANCELED) { |
| PR_WARNING("dns: Timeout while resolving service.\n"); |
| return; |
| } |
| |
| if ((status == DNS_EAI_INPROGRESS) && (info != NULL)) { |
| /* |
| * Only queue results that can be further processed. |
| */ |
| r = k_msgq_put(&dns_infoq, info, K_NO_WAIT); |
| if (r < 0) { |
| PR_WARNING("dns: k_msgq_put error %d", r); |
| } |
| } |
| } |
| |
| static const char *printable_iface(const char *iface_name, |
| const char *found, |
| const char *not_found) |
| { |
| if (iface_name[0] != '\0') { |
| return found; |
| } |
| |
| return not_found; |
| } |
| |
| static void print_dns_info(const struct shell *sh, |
| struct dns_resolve_context *ctx) |
| { |
| int i, ret; |
| |
| PR("DNS servers:\n"); |
| |
| for (i = 0; i < CONFIG_DNS_RESOLVER_MAX_SERVERS + |
| DNS_MAX_MCAST_SERVERS; i++) { |
| struct net_sockaddr *server_addr = |
| net_sad(&ctx->servers[i].dns_server_addr); |
| char iface_name[NET_IFNAMSIZ] = { 0 }; |
| |
| if (ctx->servers[i].if_index > 0) { |
| ret = net_if_get_name( |
| net_if_get_by_index(ctx->servers[i].if_index), |
| iface_name, sizeof(iface_name)); |
| if (ret < 0) { |
| snprintk(iface_name, sizeof(iface_name), "%d", |
| ctx->servers[i].if_index); |
| } |
| } |
| |
| if (ctx->servers[i].dns_server_addr.ss_family == NET_AF_INET) { |
| PR("\t%s:%u%s%s%s%s%s\n", |
| net_sprint_ipv4_addr( |
| &net_sin(server_addr)->sin_addr), |
| net_ntohs(net_sin(server_addr)->sin_port), |
| printable_iface(iface_name, " via ", ""), |
| printable_iface(iface_name, iface_name, ""), |
| ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? " (" : "", |
| ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? |
| dns_get_source_str(ctx->servers[i].source) : "", |
| ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? ")" : ""); |
| |
| } else if (ctx->servers[i].dns_server_addr.ss_family == NET_AF_INET6) { |
| PR("\t[%s]:%u%s%s%s%s%s\n", |
| net_sprint_ipv6_addr( |
| &net_sin6(server_addr)->sin6_addr), |
| net_ntohs(net_sin6(server_addr)->sin6_port), |
| printable_iface(iface_name, " via ", ""), |
| printable_iface(iface_name, iface_name, ""), |
| ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? " (" : "", |
| ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? |
| dns_get_source_str(ctx->servers[i].source) : "", |
| ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? ")" : ""); |
| } |
| } |
| |
| PR("Pending queries:\n"); |
| |
| for (i = 0; i < CONFIG_DNS_NUM_CONCUR_QUERIES; i++) { |
| int32_t remaining; |
| |
| if (!ctx->queries[i].cb || !ctx->queries[i].query) { |
| continue; |
| } |
| |
| remaining = k_ticks_to_ms_ceil32( |
| k_work_delayable_remaining_get(&ctx->queries[i].timer)); |
| |
| if (ctx->queries[i].query_type == DNS_QUERY_TYPE_A) { |
| PR("\t%s[%u]: %s remaining %d\n", "IPv4", |
| ctx->queries[i].id, |
| ctx->queries[i].query, |
| remaining); |
| } else if (ctx->queries[i].query_type == DNS_QUERY_TYPE_AAAA) { |
| PR("\t%s[%u]: %s remaining %d\n", "IPv6", |
| ctx->queries[i].id, |
| ctx->queries[i].query, |
| remaining); |
| } else if (ctx->queries[i].query_type == DNS_QUERY_TYPE_PTR) { |
| PR("\t%s[%u]: %s remaining %d\n", "PTR", |
| ctx->queries[i].id, |
| ctx->queries[i].query, |
| remaining); |
| } else { |
| PR_WARNING("\tUnknown query type %d for query %s[%u] " |
| "remaining %d\n", |
| ctx->queries[i].query_type, |
| ctx->queries[i].query, ctx->queries[i].id, |
| remaining); |
| } |
| } |
| } |
| #endif |
| |
| static int cmd_net_dns_cancel(const struct shell *sh, size_t argc, char *argv[]) |
| { |
| #if defined(CONFIG_DNS_RESOLVER) |
| struct dns_resolve_context *ctx; |
| int ret, i; |
| #endif |
| |
| ARG_UNUSED(argc); |
| ARG_UNUSED(argv); |
| |
| #if defined(CONFIG_DNS_RESOLVER) |
| ctx = dns_resolve_get_default(); |
| if (!ctx) { |
| PR_WARNING("No default DNS context found.\n"); |
| return -ENOEXEC; |
| } |
| |
| for (ret = 0, i = 0; i < CONFIG_DNS_NUM_CONCUR_QUERIES; i++) { |
| if (!ctx->queries[i].cb) { |
| continue; |
| } |
| |
| if (!dns_resolve_cancel(ctx, ctx->queries[i].id)) { |
| ret++; |
| } |
| } |
| |
| if (ret) { |
| PR("Cancelled %d pending requests.\n", ret); |
| } else { |
| PR("No pending DNS requests.\n"); |
| } |
| #else |
| PR_INFO("Set %s to enable %s support.\n", "CONFIG_DNS_RESOLVER", |
| "DNS resolver"); |
| #endif |
| |
| return 0; |
| } |
| |
| static int cmd_net_dns_query(const struct shell *sh, size_t argc, char *argv[]) |
| { |
| |
| #if defined(CONFIG_DNS_RESOLVER) |
| struct dns_resolve_context *ctx; |
| enum dns_query_type qtype = DNS_QUERY_TYPE_A; |
| char *host, *type = NULL; |
| int ret, arg = 1; |
| |
| host = argv[arg++]; |
| if (!host) { |
| PR_WARNING("Hostname not specified.\n"); |
| return -ENOEXEC; |
| } |
| |
| if (argv[arg]) { |
| type = argv[arg]; |
| } |
| |
| if (type) { |
| if (strcasecmp(type, "A") == 0) { |
| qtype = DNS_QUERY_TYPE_A; |
| PR("IPv4 address query type\n"); |
| } else if (strcasecmp(type, "CNAME") == 0) { |
| qtype = DNS_QUERY_TYPE_CNAME; |
| PR("CNAME query type\n"); |
| } else if (strcasecmp(type, "PTR") == 0) { |
| qtype = DNS_QUERY_TYPE_PTR; |
| PR("Pointer query type\n"); |
| } else if (strcasecmp(type, "TXT") == 0) { |
| qtype = DNS_QUERY_TYPE_TXT; |
| PR("Text query type\n"); |
| } else if (strcasecmp(type, "AAAA") == 0) { |
| qtype = DNS_QUERY_TYPE_AAAA; |
| PR("IPv6 address query type\n"); |
| } else if (strcasecmp(type, "SRV") == 0) { |
| qtype = DNS_QUERY_TYPE_SRV; |
| PR("Service query type\n"); |
| } else { |
| PR_WARNING("Unknown query type, specify either " |
| "A, CNAME, PTR, TXT, AAAA, or SRV\n"); |
| return -ENOEXEC; |
| } |
| } |
| |
| ctx = dns_resolve_get_default(); |
| if (!ctx) { |
| PR_WARNING("No default DNS context found.\n"); |
| return -ENOEXEC; |
| } |
| |
| ret = dns_resolve_name(ctx, host, qtype, NULL, dns_result_cb, |
| (void *)sh, DNS_TIMEOUT); |
| if (ret < 0) { |
| PR_WARNING("Cannot resolve '%s' (%d)\n", host, ret); |
| } else { |
| PR("Query for '%s' sent.\n", host); |
| } |
| #else |
| PR_INFO("Set %s to enable %s support.\n", "CONFIG_DNS_RESOLVER", |
| "DNS resolver"); |
| #endif |
| |
| return 0; |
| } |
| |
| static int cmd_net_dns(const struct shell *sh, size_t argc, char *argv[]) |
| { |
| #if defined(CONFIG_DNS_RESOLVER) |
| struct dns_resolve_context *ctx; |
| #endif |
| |
| #if defined(CONFIG_DNS_RESOLVER) |
| if (argv[1]) { |
| /* So this is a query then */ |
| cmd_net_dns_query(sh, argc, argv); |
| return 0; |
| } |
| |
| /* DNS status */ |
| ctx = dns_resolve_get_default(); |
| if (!ctx) { |
| PR_WARNING("No default DNS context found.\n"); |
| return -ENOEXEC; |
| } |
| |
| print_dns_info(sh, ctx); |
| #else |
| PR_INFO("Set %s to enable %s support.\n", "CONFIG_DNS_RESOLVER", |
| "DNS resolver"); |
| #endif |
| |
| return 0; |
| } |
| |
| static int cmd_net_dns_list(const struct shell *sh, size_t argc, char *argv[]) |
| { |
| #if defined(CONFIG_DNS_SD) |
| #define MAX_PORT_LEN 6 |
| char buf[MAX_PORT_LEN]; |
| int n_records = 0; |
| |
| DNS_SD_FOREACH(record) { |
| if (!dns_sd_rec_is_valid(record)) { |
| continue; |
| } |
| |
| if (n_records == 0) { |
| PR(" DNS service records\n"); |
| } |
| |
| ++n_records; |
| |
| if (record->port != NULL) { |
| snprintk(buf, sizeof(buf), "%u", net_ntohs(*record->port)); |
| } |
| |
| PR("[%2d] %s.%s%s%s%s%s%s%s\n", |
| n_records, |
| record->instance != NULL ? record->instance : "", |
| record->service != NULL ? record->service : "", |
| record->proto != NULL ? "." : "", |
| record->proto != NULL ? record->proto : "", |
| record->domain != NULL ? "." : "", |
| record->domain != NULL ? record->domain : "", |
| record->port != NULL ? ":" : "", |
| record->port != NULL ? buf : ""); |
| } |
| |
| if (n_records == 0) { |
| PR("No DNS service records found.\n"); |
| return 0; |
| } |
| #else |
| PR_INFO("Set %s to enable %s support.\n", "CONFIG_DNS_SD", |
| "DNS service discovery"); |
| #endif |
| |
| return 0; |
| } |
| |
| static int cmd_net_dns_browse(const struct shell *sh, size_t argc, char *argv[]) |
| { |
| #if defined(CONFIG_DNS_RESOLVER) |
| struct dns_resolve_context *ctx; |
| char *query; |
| uint16_t dns_id; |
| int ret, count = 0; |
| |
| if (argc < 2) { |
| /* Browse for all service types */ |
| query = "_services._dns-sd._udp.local"; |
| } else { |
| query = argv[1]; |
| } |
| |
| /* remove any lingering info data */ |
| k_msgq_purge(&dns_infoq); |
| |
| ctx = dns_resolve_get_default(); |
| if (ctx == NULL) { |
| PR_WARNING("No default DNS context found.\n"); |
| return -ENOEXEC; |
| } |
| |
| ret = dns_resolve_service(ctx, query, &dns_id, dns_service_cb, |
| (void *)sh, DNS_TIMEOUT); |
| if (ret < 0) { |
| PR_WARNING("Cannot browse '%s' (%d)\n", query, ret); |
| return ret; |
| } |
| |
| PR("Browsing for '%s'...\n", query); |
| |
| for (;;) { |
| struct dns_addrinfo info; |
| |
| ret = k_msgq_get(&dns_infoq, &info, K_MSEC(DNS_TIMEOUT)); |
| if (ret < 0) { |
| break; |
| } |
| |
| if (info.ai_family == NET_AF_LOCAL) { |
| PR(" %.*s\n", (int)info.ai_addrlen, info.ai_canonname); |
| count++; |
| } |
| } |
| |
| PR("Found %d service(s).\n", count); |
| #else |
| PR_INFO("Set %s to enable %s support.\n", "CONFIG_DNS_RESOLVER", |
| "DNS resolver"); |
| #endif |
| |
| return 0; |
| } |
| |
| static int cmd_net_dns_service(const struct shell *sh, size_t argc, char *argv[]) |
| { |
| #if defined(CONFIG_DNS_RESOLVER) |
| struct dns_resolve_context *ctx; |
| char *cp; |
| char *service; |
| uint16_t port; |
| uint16_t dns_id; |
| int ret, arg = 1; |
| |
| service = argv[arg++]; |
| if (service == NULL) { |
| PR_WARNING("Service not specified.\n"); |
| return -ENOEXEC; |
| } |
| |
| /* remove any lingering info data */ |
| k_msgq_purge(&dns_infoq); |
| |
| ctx = dns_resolve_get_default(); |
| if (ctx == NULL) { |
| PR_WARNING("No default DNS context found.\n"); |
| return -ENOEXEC; |
| } |
| |
| ret = dns_resolve_service(ctx, service, &dns_id, dns_service_cb, |
| (void *)sh, DNS_TIMEOUT); |
| if (ret < 0) { |
| PR_WARNING("Cannot resolve '%s' (%d)\n", service, ret); |
| return ret; |
| } |
| |
| PR("Resolve for '%s' service sent.\n", service); |
| port = 0; |
| for (;;) { |
| struct dns_addrinfo info; |
| enum dns_query_type qtype; |
| char query[DNS_MAX_NAME_SIZE + 1]; |
| union { |
| char in4[NET_INET_ADDRSTRLEN]; |
| char in6[NET_INET6_ADDRSTRLEN]; |
| } str; |
| |
| ret = k_msgq_get(&dns_infoq, &info, K_MSEC(DNS_TIMEOUT)); |
| if (ret < 0) { |
| /* just assume a timeout so no more data to process */ |
| break; |
| } |
| |
| switch (info.ai_family) { |
| case NET_AF_INET: |
| cp = net_addr_ntop(NET_AF_INET, |
| &net_sin(net_sad(&info.ai_addr_storage))->sin_addr, |
| str.in4, sizeof(str.in4)); |
| PR("AF_INET %s:%u\n", cp ? cp : "<invalid>", port); |
| break; |
| |
| case NET_AF_INET6: |
| cp = net_addr_ntop(NET_AF_INET6, |
| &net_sin6(net_sad(&info.ai_addr_storage))->sin6_addr, |
| str.in6, sizeof(str.in6)); |
| PR("AF_INET6 [%s]:%u\n", cp ? cp : "<invalid>", port); |
| break; |
| |
| case NET_AF_LOCAL: |
| PR("AF_LOCAL %.*s\n", |
| (int)info.ai_addrlen, info.ai_canonname); |
| |
| snprintf(query, sizeof(query), "%.*s", |
| info.ai_addrlen, info.ai_canonname); |
| |
| qtype = DNS_QUERY_TYPE_SRV; |
| ret = dns_resolve_name(ctx, query, qtype, |
| &dns_id, |
| dns_service_cb, (void *)sh, |
| DNS_TIMEOUT); |
| if (ret < 0) { |
| return ret; |
| } |
| break; |
| |
| case NET_AF_UNSPEC: |
| if (info.ai_extension == DNS_RESOLVE_SRV) { |
| PR("SRV %d %d %d %.*s\n", |
| info.ai_srv.priority, |
| info.ai_srv.weight, |
| info.ai_srv.port, |
| (int)info.ai_srv.targetlen, |
| info.ai_srv.target); |
| |
| port = info.ai_srv.port; |
| |
| snprintf(query, sizeof(query), "%.*s", |
| (int)info.ai_srv.targetlen, |
| info.ai_srv.target); |
| |
| /* |
| * Sending a query for both AAAA and A records |
| * should be ok, but the resolver doesn't |
| * gracefully handle the query for different |
| * types. |
| */ |
| qtype = DNS_QUERY_TYPE_AAAA; |
| ret = dns_resolve_name(ctx, query, qtype, |
| &dns_id, |
| dns_service_cb, |
| (void *)sh, |
| DNS_TIMEOUT); |
| if (ret < 0) { |
| return ret; |
| } |
| break; |
| } |
| |
| __fallthrough; |
| default: |
| PR_WARNING("dns: unhandled info %u on msgq\n", info.ai_family); |
| break; |
| } |
| } |
| #else |
| PR_INFO("Set %s to enable %s support.\n", "CONFIG_DNS_RESOLVER", |
| "DNS resolver"); |
| #endif |
| |
| return 0; |
| } |
| |
| SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_dns, |
| SHELL_CMD(browse, NULL, |
| SHELL_HELP("Browse DNS services", "[<service-description>]"), |
| cmd_net_dns_browse), |
| SHELL_CMD(cancel, NULL, |
| SHELL_HELP("Cancel all pending requests", ""), |
| cmd_net_dns_cancel), |
| SHELL_CMD(query, NULL, |
| SHELL_HELP("Queries IPv4 address (default) or IPv6 address for a host name", |
| "<hostname> [A or AAAA]"), |
| cmd_net_dns_query), |
| SHELL_CMD(list, NULL, |
| SHELL_HELP("List local DNS service records", ""), |
| cmd_net_dns_list), |
| SHELL_CMD(service, NULL, |
| SHELL_HELP("Execute DNS service discovery query", |
| "<service-description>"), |
| cmd_net_dns_service), |
| SHELL_SUBCMD_SET_END |
| ); |
| |
| SHELL_SUBCMD_ADD((net), dns, &net_cmd_dns, |
| "Show how DNS is configured. Optionally do a query using a given name.", |
| cmd_net_dns, 1, 2); |