/** @file * @brief DNS resolve API * * An API for applications to do DNS query. */ /* * Copyright (c) 2017 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #if defined(CONFIG_NET_DEBUG_DNS_RESOLVE) #define SYS_LOG_DOMAIN "dns/resolve" #define NET_LOG_ENABLED 1 #endif #include #include #include #include #include #include #include #include "dns_pack.h" static int dns_write(struct dns_resolve_context *ctx, int server_idx, int query_idx, struct net_buf *dns_data, struct net_buf *dns_qname); #define DNS_BUF_TIMEOUT 500 /* ms */ /* RFC 1035, 3.1. Name space definitions * To simplify implementations, the total length of a domain name (i.e., * label octets and label length octets) is restricted to 255 octets or * less. */ #define DNS_MAX_NAME_LEN 255 #define DNS_QUERY_MAX_SIZE (DNS_MSG_HEADER_SIZE + DNS_MAX_NAME_LEN + \ DNS_QTYPE_LEN + DNS_QCLASS_LEN) /* This value is recommended by RFC 1035 */ #define DNS_RESOLVER_MAX_BUF_SIZE 512 #define DNS_RESOLVER_MIN_BUF 1 #define DNS_RESOLVER_BUF_CTR (DNS_RESOLVER_MIN_BUF + \ CONFIG_DNS_RESOLVER_ADDITIONAL_BUF_CTR) /* Compressed RR uses a pointer to another RR. So, min size is 12 bytes without * considering RR payload. * See https://tools.ietf.org/html/rfc1035#section-4.1.4 */ #define DNS_ANSWER_PTR_LEN 12 /* See dns_unpack_answer, and also see: * https://tools.ietf.org/html/rfc1035#section-4.1.2 */ #define DNS_QUERY_POS 0x0c #define DNS_IPV4_LEN sizeof(struct in_addr) #define DNS_IPV6_LEN sizeof(struct in6_addr) NET_BUF_POOL_DEFINE(dns_msg_pool, DNS_RESOLVER_BUF_CTR, DNS_RESOLVER_MAX_BUF_SIZE, 0, NULL); NET_BUF_POOL_DEFINE(dns_qname_pool, DNS_RESOLVER_BUF_CTR, DNS_MAX_NAME_LEN, 0, NULL); static struct dns_resolve_context dns_default_ctx; int dns_resolve_init(struct dns_resolve_context *ctx, const char *servers[]) { #if defined(CONFIG_NET_IPV6) struct sockaddr_in6 local_addr6 = { .sin6_family = AF_INET6, .sin6_port = 0, }; #endif #if defined(CONFIG_NET_IPV4) struct sockaddr_in local_addr4 = { .sin_family = AF_INET, .sin_port = 0, }; #endif struct sockaddr *local_addr = NULL; socklen_t addr_len = 0; int i = 0, idx = 0; u16_t port = 0; int ret, count; if (!ctx) { return -ENOENT; } if (!servers || !*servers) { return -ENOENT; } if (ctx->is_used) { return -ENOTEMPTY; } memset(ctx, 0, sizeof(*ctx)); for (i = 0; i < CONFIG_DNS_RESOLVER_MAX_SERVERS && servers[i]; i++) { int j; char *ptr; if (*servers[i] == '[') { #if defined(CONFIG_NET_IPV6) /* IPv6 address with port number */ struct in6_addr *addr; char server[INET6_ADDRSTRLEN + 1]; int end; ptr = strstr(servers[i], "]:"); if (!ptr) { continue; } end = min(INET6_ADDRSTRLEN, ptr - (servers[i] + 1)); memcpy(server, servers[i] + 1, end); server[end] = '\0'; addr = &net_sin6(&ctx->servers[idx].dns_server)-> sin6_addr; ret = net_addr_pton(AF_INET6, server, addr); if (ret < 0 && ret != -EINVAL) { return ret; } if (ret == -EINVAL) { continue; } port = strtol(ptr + 2, NULL, 10); net_sin6(&ctx->servers[idx].dns_server)->sin6_port = htons(port); NET_DBG("[%d] IPv6 server %s port %d", idx, server, port); ctx->servers[idx++].dns_server.family = AF_INET6; #endif /* CONFIG_NET_IPV6 */ continue; } count = j = 0; while (servers[i][j]) { if (servers[i][j] == ':') { count++; } j++; } if (count == 1) { #if defined(CONFIG_NET_IPV4) /* IPv4 address with port number */ char server[NET_IPV4_ADDR_LEN + 1]; struct in_addr *addr; int end; ptr = strstr(servers[i], ":"); if (!ptr) { continue; } end = min(NET_IPV4_ADDR_LEN, ptr - servers[i]); memcpy(server, servers[i], end); server[end] = '\0'; addr = &net_sin(&ctx->servers[idx].dns_server)-> sin_addr; ret = net_addr_pton(AF_INET, server, addr); if (ret < 0 && ret != -EINVAL) { NET_ERR("Cannot set DNS server %s", server); return ret; } if (ret == -EINVAL) { continue; } port = strtol(ptr + 1, NULL, 10); net_sin(&ctx->servers[idx].dns_server)->sin_port = htons(port); NET_DBG("[%d] IPv4 server %s port %d", idx, server, port); ctx->servers[idx++].dns_server.family = AF_INET; #endif /* CONFIG_NET_IPV4 */ continue; } #if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_IPV6) /* First try IPv4 address */ ret = net_addr_pton(AF_INET, servers[i], &net_sin(&ctx->servers[idx].dns_server)-> sin_addr); if (ret < 0 && ret != -EINVAL) { return ret; } if (!ret) { net_sin(&ctx->servers[idx].dns_server)->sin_port = htons(53); NET_DBG("[%d] IPv4 server %s port %d", idx, servers[i], 53); ctx->servers[idx++].dns_server.family = AF_INET; } else if (ret == -EINVAL) { /* Then the address must be IPv6 based */ ret = net_addr_pton(AF_INET6, servers[i], &net_sin6(&ctx->servers[idx].dns_server)-> sin6_addr); if (ret < 0 && ret != -EINVAL) { return ret; } if (ret == -EINVAL) { continue; } net_sin6(&ctx->servers[idx].dns_server)->sin6_port = htons(53); NET_DBG("[%d] IPv6 server %s port %d", idx, servers[i], 53); ctx->servers[idx++].dns_server.family = AF_INET6; } #endif #if defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6) ret = net_addr_pton(AF_INET, servers[i], &net_sin(&ctx->servers[idx].dns_server)-> sin_addr); if (ret < 0 && ret != -EINVAL) { return ret; } if (ret == -EINVAL) { continue; } NET_DBG("[%d] IPv4 server %s port %d", idx, servers[i], 53); net_sin(&ctx->servers[idx].dns_server)->sin_port = htons(53); ctx->servers[idx++].dns_server.family = AF_INET; #endif /* IPv4 && !IPv6 */ #if defined(CONFIG_NET_IPV6) && !defined(CONFIG_NET_IPV4) ret = net_addr_pton(AF_INET6, servers[i], &net_sin6(&ctx->servers[idx].dns_server)->sin6_addr); if (ret < 0 && ret != -EINVAL) { return ret; } if (ret == -EINVAL) { continue; } NET_DBG("[%d] IPv6 server %s port %d", idx, servers[i], 53); net_sin6(&ctx->servers[idx].dns_server)->sin6_port = htons(53); ctx->servers[idx++].dns_server.family = AF_INET6; #endif /* IPv6 && !IPv4 */ } for (i = 0, count = 0; i < CONFIG_DNS_RESOLVER_MAX_SERVERS && ctx->servers[i].dns_server.family; i++) { if (ctx->servers[i].dns_server.family == AF_INET6) { #if defined(CONFIG_NET_IPV6) local_addr = (struct sockaddr *)&local_addr6; addr_len = sizeof(struct sockaddr_in6); #else continue; #endif } if (ctx->servers[i].dns_server.family == AF_INET) { #if defined(CONFIG_NET_IPV4) local_addr = (struct sockaddr *)&local_addr4; addr_len = sizeof(struct sockaddr_in); #else continue; #endif } ret = net_context_get(ctx->servers[i].dns_server.family, SOCK_DGRAM, IPPROTO_UDP, &ctx->servers[i].net_ctx); if (ret < 0) { NET_DBG("Cannot get net_context (%d)", ret); return ret; } ret = net_context_bind(ctx->servers[i].net_ctx, local_addr, addr_len); if (ret < 0) { NET_DBG("Cannot bind DNS context (%d)", ret); return ret; } count++; } if (count == 0) { /* No servers defined */ NET_DBG("No DNS servers defined."); return -EINVAL; } ctx->is_used = true; ctx->buf_timeout = DNS_BUF_TIMEOUT; return 0; } static inline int get_cb_slot(struct dns_resolve_context *ctx) { int i; for (i = 0; i < CONFIG_DNS_NUM_CONCUR_QUERIES; i++) { if (!ctx->queries[i].cb) { return i; } } return -ENOENT; } static inline int get_slot_by_id(struct dns_resolve_context *ctx, u16_t dns_id) { int i; for (i = 0; i < CONFIG_DNS_NUM_CONCUR_QUERIES; i++) { if (ctx->queries[i].cb && ctx->queries[i].id == dns_id) { return i; } } return -ENOENT; } static int dns_read(struct dns_resolve_context *ctx, struct net_pkt *pkt, struct net_buf *dns_data, u16_t *dns_id, struct net_buf *dns_cname, struct dns_addrinfo *info) { /* Helper struct to track the dns msg received from the server */ struct dns_msg_t dns_msg; u32_t ttl; /* RR ttl, so far it is not passed to caller */ u8_t *src, *addr; int address_size; /* index that points to the current answer being analyzed */ int answer_ptr; int data_len; int offset; int items; int ret; int server_idx, query_idx; data_len = min(net_pkt_appdatalen(pkt), DNS_RESOLVER_MAX_BUF_SIZE); offset = net_pkt_get_len(pkt) - data_len; /* TODO: Instead of this temporary copy, just use the net_pkt directly. */ ret = net_frag_linear_copy(dns_data, pkt->frags, offset, data_len); if (ret < 0) { ret = DNS_EAI_MEMORY; goto quit; } dns_msg.msg = dns_data->data; dns_msg.msg_size = data_len; /* The dns_unpack_response_header() has design flaw as it expects * dns id to be given instead of returning the id to the caller. * In our case we would like to get it returned instead so that we * can match the DNS query that we sent. When dns_read() is called, * we do not know what the DNS id is yet. */ *dns_id = dns_unpack_header_id(dns_msg.msg); query_idx = get_slot_by_id(ctx, *dns_id); if (query_idx < 0) { ret = DNS_EAI_SYSTEM; goto quit; } if (dns_header_rcode(dns_msg.msg) == DNS_HEADER_REFUSED) { ret = DNS_EAI_FAIL; goto quit; } ret = dns_unpack_response_header(&dns_msg, *dns_id); if (ret < 0) { ret = DNS_EAI_FAIL; goto quit; } if (dns_header_qdcount(dns_msg.msg) != 1) { ret = DNS_EAI_FAIL; goto quit; } ret = dns_unpack_response_query(&dns_msg); if (ret < 0) { ret = DNS_EAI_FAIL; goto quit; } if (ctx->queries[query_idx].query_type == DNS_QUERY_TYPE_A) { address_size = DNS_IPV4_LEN; addr = (u8_t *)&net_sin(&info->ai_addr)->sin_addr; info->ai_family = AF_INET; info->ai_addr.family = AF_INET; info->ai_addrlen = sizeof(struct sockaddr_in); } else if (ctx->queries[query_idx].query_type == DNS_QUERY_TYPE_AAAA) { address_size = DNS_IPV6_LEN; addr = (u8_t *)&net_sin6(&info->ai_addr)->sin6_addr; info->ai_family = AF_INET6; info->ai_addr.family = AF_INET6; info->ai_addrlen = sizeof(struct sockaddr_in6); } else { ret = DNS_EAI_FAMILY; goto quit; } /* while loop to traverse the response */ answer_ptr = DNS_QUERY_POS; items = 0; server_idx = 0; while (server_idx < dns_header_ancount(dns_msg.msg)) { ret = dns_unpack_answer(&dns_msg, answer_ptr, &ttl); if (ret < 0) { ret = DNS_EAI_FAIL; goto quit; } switch (dns_msg.response_type) { case DNS_RESPONSE_IP: if (dns_msg.response_length < address_size) { /* it seems this is a malformed message */ ret = DNS_EAI_FAIL; goto quit; } src = dns_msg.msg + dns_msg.response_position; memcpy(addr, src, address_size); ctx->queries[query_idx].cb(DNS_EAI_INPROGRESS, info, ctx->queries[query_idx].user_data); items++; break; case DNS_RESPONSE_CNAME_NO_IP: /* Instead of using the QNAME at DNS_QUERY_POS, * we will use this CNAME */ answer_ptr = dns_msg.response_position; break; default: ret = DNS_EAI_FAIL; goto quit; } /* Update the answer offset to point to the next RR (answer) */ dns_msg.answer_offset += DNS_ANSWER_PTR_LEN; dns_msg.answer_offset += dns_msg.response_length; server_idx++; } /* No IP addresses were found, so we take the last CNAME to generate * another query. Number of additional queries is controlled via Kconfig */ if (items == 0) { if (dns_msg.response_type == DNS_RESPONSE_CNAME_NO_IP) { u16_t pos = dns_msg.response_position; ret = dns_copy_qname(dns_cname->data, &dns_cname->len, dns_cname->size, &dns_msg, pos); if (ret < 0) { ret = DNS_EAI_SYSTEM; goto quit; } ret = DNS_EAI_AGAIN; goto finished; } } if (items == 0) { ret = DNS_EAI_NODATA; } else { ret = DNS_EAI_ALLDONE; } /* Marks the end of the results */ ctx->queries[query_idx].cb(ret, NULL, ctx->queries[query_idx].user_data); if (k_delayed_work_remaining_get(&ctx->queries[query_idx].timer) > 0) { k_delayed_work_cancel(&ctx->queries[query_idx].timer); } ctx->queries[query_idx].cb = NULL; net_pkt_unref(pkt); return 0; finished: dns_resolve_cancel(ctx, *dns_id); quit: net_pkt_unref(pkt); return ret; } static void cb_recv(struct net_context *net_ctx, struct net_pkt *pkt, int status, void *user_data) { struct dns_resolve_context *ctx = user_data; struct dns_addrinfo info = { 0 }; struct net_buf *dns_cname = NULL; struct net_buf *dns_data = NULL; u16_t dns_id = 0; int ret, i; ARG_UNUSED(net_ctx); if (status) { ret = DNS_EAI_SYSTEM; goto quit; } dns_data = net_buf_alloc(&dns_msg_pool, ctx->buf_timeout); if (!dns_data) { ret = DNS_EAI_MEMORY; goto quit; } dns_cname = net_buf_alloc(&dns_qname_pool, ctx->buf_timeout); if (!dns_cname) { ret = DNS_EAI_MEMORY; goto quit; } ret = dns_read(ctx, pkt, dns_data, &dns_id, dns_cname, &info); if (!ret) { /* We called the callback already in dns_read() if there * was no errors. */ goto free_buf; } /* Query again if we got CNAME */ if (ret == DNS_EAI_AGAIN) { int failure = 0; int j; i = get_slot_by_id(ctx, dns_id); if (i < 0) { goto free_buf; } for (j = 0; j < CONFIG_DNS_RESOLVER_MAX_SERVERS; j++) { if (!ctx->servers[j].net_ctx) { continue; } ret = dns_write(ctx, j, i, dns_data, dns_cname); if (ret < 0) { failure++; } } if (failure) { NET_DBG("DNS cname query failed %d times", failure); if (failure == j) { ret = DNS_EAI_SYSTEM; goto quit; } } goto free_buf; } quit: i = get_slot_by_id(ctx, dns_id); if (i < 0) { goto free_buf; } if (k_delayed_work_remaining_get(&ctx->queries[i].timer) > 0) { k_delayed_work_cancel(&ctx->queries[i].timer); } ctx->queries[i].cb(ret, &info, ctx->queries[i].user_data); ctx->queries[i].cb = NULL; free_buf: if (dns_data) { net_buf_unref(dns_data); } if (dns_cname) { net_buf_unref(dns_cname); } } static int dns_write(struct dns_resolve_context *ctx, int server_idx, int query_idx, struct net_buf *dns_data, struct net_buf *dns_qname) { enum dns_query_type query_type; struct net_context *net_ctx; struct sockaddr *server; struct net_pkt *pkt; int server_addr_len; u16_t dns_id; int ret; net_ctx = ctx->servers[server_idx].net_ctx; server = &ctx->servers[server_idx].dns_server; dns_id = ctx->queries[query_idx].id; query_type = ctx->queries[query_idx].query_type; ret = dns_msg_pack_query(dns_data->data, &dns_data->len, dns_data->size, dns_qname->data, dns_qname->len, dns_id, (enum dns_rr_type)query_type); if (ret < 0) { ret = -EINVAL; goto quit; } pkt = net_pkt_get_tx(net_ctx, ctx->buf_timeout); if (!pkt) { ret = -ENOMEM; goto quit; } ret = net_pkt_append_all(pkt, dns_data->len, dns_data->data, ctx->buf_timeout); if (ret < 0) { ret = -ENOMEM; goto quit; } if (server->family == AF_INET) { server_addr_len = sizeof(struct sockaddr_in); } else { server_addr_len = sizeof(struct sockaddr_in6); } net_context_recv(net_ctx, cb_recv, K_NO_WAIT, ctx); ret = net_context_sendto(pkt, server, server_addr_len, NULL, K_NO_WAIT, NULL, NULL); if (ret < 0) { NET_DBG("Cannot send query (%d)", ret); net_pkt_unref(pkt); goto quit; } ret = k_delayed_work_submit(&ctx->queries[query_idx].timer, ctx->queries[query_idx].timeout); if (ret < 0) { NET_DBG("[%u] cannot submit work to server idx %d for id %u " "timeout %u ret %d", query_idx, server_idx, dns_id, ctx->queries[query_idx].timeout, ret); goto quit; } else { NET_DBG("[%u] submitting work to server idx %d for id %u " "timeout %u", query_idx, server_idx, dns_id, ctx->queries[query_idx].timeout); } ret = 0; quit: return ret; } int dns_resolve_cancel(struct dns_resolve_context *ctx, u16_t dns_id) { int i; i = get_slot_by_id(ctx, dns_id); if (i < 0) { return -ENOENT; } NET_DBG("Cancelling DNS req %u", dns_id); if (k_delayed_work_remaining_get(&ctx->queries[i].timer) > 0) { k_delayed_work_cancel(&ctx->queries[i].timer); } ctx->queries[i].cb(DNS_EAI_CANCELED, NULL, ctx->queries[i].user_data); ctx->queries[i].cb = NULL; return 0; } static void query_timeout(struct k_work *work) { struct dns_pending_query *pending_query = CONTAINER_OF(work, struct dns_pending_query, timer); NET_DBG("Query timeout DNS req %u", pending_query->id); dns_resolve_cancel(pending_query->ctx, pending_query->id); } int dns_resolve_name(struct dns_resolve_context *ctx, const char *query, enum dns_query_type type, u16_t *dns_id, dns_resolve_cb_t cb, void *user_data, s32_t timeout) { struct net_buf *dns_data; struct net_buf *dns_qname = NULL; int ret, i, j = 0; int failure = 0; if (!ctx || !ctx->is_used || !query || !cb) { return -EINVAL; } /* Timeout cannot be 0 as we cannot resolve name that fast. */ if (timeout == K_NO_WAIT) { return -EINVAL; } i = get_cb_slot(ctx); if (i < 0) { return -EAGAIN; } ctx->queries[i].cb = cb; ctx->queries[i].timeout = timeout; ctx->queries[i].query = query; ctx->queries[i].query_type = type; ctx->queries[i].user_data = user_data; ctx->queries[i].ctx = ctx; k_delayed_work_init(&ctx->queries[i].timer, query_timeout); dns_data = net_buf_alloc(&dns_msg_pool, ctx->buf_timeout); if (!dns_data) { ret = -ENOMEM; goto quit; } dns_qname = net_buf_alloc(&dns_qname_pool, ctx->buf_timeout); if (!dns_qname) { ret = -ENOMEM; goto quit; } ret = dns_msg_pack_qname(&dns_qname->len, dns_qname->data, DNS_MAX_NAME_LEN, ctx->queries[i].query); if (ret < 0) { goto quit; } ctx->queries[i].id = sys_rand32_get(); /* Do this immediately after calculating the Id so that the unit * test will work properly. */ if (dns_id) { *dns_id = ctx->queries[i].id; NET_DBG("DNS id will be %u", *dns_id); } for (j = 0; j < CONFIG_DNS_RESOLVER_MAX_SERVERS; j++) { if (!ctx->servers[j].net_ctx) { continue; } ret = dns_write(ctx, j, i, dns_data, dns_qname); if (ret < 0) { failure++; continue; } /* Do one concurrent query only for each name resolve. * TODO: Change the i (query index) to do multiple concurrent * to each server. */ break; } if (failure) { NET_DBG("DNS query failed %d times", failure); if (failure == j) { ret = -ENOENT; goto quit; } } ret = 0; quit: if (ret < 0) { if (k_delayed_work_remaining_get(&ctx->queries[i].timer) > 0) { k_delayed_work_cancel(&ctx->queries[i].timer); } ctx->queries[i].cb = NULL; if (dns_id) { *dns_id = 0; } } if (dns_data) { net_buf_unref(dns_data); } if (dns_qname) { net_buf_unref(dns_qname); } return ret; } int dns_resolve_close(struct dns_resolve_context *ctx) { int i; if (!ctx->is_used) { return -ENOENT; } for (i = 0; i < CONFIG_DNS_RESOLVER_MAX_SERVERS; i++) { if (ctx->servers[i].net_ctx) { net_context_put(ctx->servers[i].net_ctx); } } return 0; } struct dns_resolve_context *dns_resolve_get_default(void) { return &dns_default_ctx; } void dns_init_resolver(void) { #if defined(CONFIG_DNS_SERVER_IP_ADDRESSES) static const char *dns_servers[CONFIG_DNS_RESOLVER_MAX_SERVERS + 1]; int count = CONFIG_DNS_RESOLVER_MAX_SERVERS; int ret; if (count > 5) { count = 5; } switch (count) { case 5: dns_servers[4] = CONFIG_DNS_SERVER5; /* fallthrough */ case 4: dns_servers[3] = CONFIG_DNS_SERVER4; /* fallthrough */ case 3: dns_servers[2] = CONFIG_DNS_SERVER3; /* fallthrough */ case 2: dns_servers[1] = CONFIG_DNS_SERVER2; /* fallthrough */ case 1: dns_servers[0] = CONFIG_DNS_SERVER1; /* fallthrough */ case 0: break; } dns_servers[CONFIG_DNS_RESOLVER_MAX_SERVERS] = NULL; ret = dns_resolve_init(dns_resolve_get_default(), dns_servers); if (ret < 0) { NET_WARN("Cannot initialize DNS resolver (%d)", ret); } #endif }