blob: 4e04f4580ef3e5f2d18adbd4fc42993d6b5fe20a [file] [log] [blame]
/*
* 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"
#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 AF_INET:
net_addr_ntop(AF_INET,
&net_sin(&info->ai_addr)->sin_addr,
str, sizeof(str));
break;
case AF_INET6:
net_addr_ntop(AF_INET6,
&net_sin6(&info->ai_addr)->sin6_addr,
str, sizeof(str));
break;
case AF_LOCAL:
/* service discovery */
memset(str, 0, MAX_STR_LEN);
memcpy(str, info->ai_canonname,
MIN(info->ai_addrlen, MAX_STR_LEN));
break;
case 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;
}
/* fallthru */
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);
}
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++) {
char iface_name[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.sa_family == AF_INET) {
PR("\t%s:%u%s%s%s%s%s\n",
net_sprint_ipv4_addr(
&net_sin(&ctx->servers[i].dns_server)->
sin_addr),
ntohs(net_sin(&ctx->servers[i].dns_server)->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.sa_family == AF_INET6) {
PR("\t[%s]:%u%s%s%s%s%s\n",
net_sprint_ipv6_addr(
&net_sin6(&ctx->servers[i].dns_server)->
sin6_addr),
ntohs(net_sin6(&ctx->servers[i].dns_server)->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)
#define DNS_TIMEOUT (MSEC_PER_SEC * 2) /* ms */
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("DNS resolver not supported. Set CONFIG_DNS_RESOLVER to "
"enable it.\n");
#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("DNS resolver not supported. Set CONFIG_DNS_RESOLVER to "
"enable it.\n");
#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", 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("DNS service discovery not supported. Set CONFIG_DNS_SD to "
"enable it.\n");
#endif
return 0;
}
static int cmd_net_dns_service(const struct shell *sh, size_t argc, char *argv[])
{
#if defined(CONFIG_DNS_RESOLVER)
#define DNS_TIMEOUT (MSEC_PER_SEC * 2) /* ms */
struct dns_resolve_context *ctx;
char *service;
uint16_t dns_id;
int ret, arg = 1;
service = argv[arg++];
if (service == NULL) {
PR_WARNING("Service not specified.\n");
return -ENOEXEC;
}
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_result_cb,
(void *)sh, DNS_TIMEOUT);
if (ret < 0) {
PR_WARNING("Cannot resolve '%s' (%d)\n", service, ret);
} else {
PR("Query for '%s' sent.\n", service);
}
#else
PR_INFO("DNS resolver not supported. Set CONFIG_DNS_RESOLVER to "
"enable it.\n");
#endif
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_dns,
SHELL_CMD(cancel, NULL, "Cancel all pending requests.",
cmd_net_dns_cancel),
SHELL_CMD(query, NULL,
"'net dns <hostname> [A or AAAA]' queries IPv4 address "
"(default) or IPv6 address for a host name.",
cmd_net_dns_query),
SHELL_CMD(list, NULL,
"List local DNS service records.",
cmd_net_dns_list),
SHELL_CMD(service, NULL,
"'net dns service <service-description>\n"
"Execute DNS service discovery query.",
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);