net: dns: Support multiple simultaneous mDNS requests

As mDNS requests set DNS id to 0, we cannot use it to match
the DNS response packet. In order to allow this functionality,
create a hash from query name and type, and use that together
with DNS id to match request and response.

Fixes #21914

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
Jukka Rissanen 2020-01-15 16:30:07 +02:00
commit f9c18dd962
2 changed files with 209 additions and 68 deletions

View file

@ -208,6 +208,14 @@ struct dns_resolve_context {
/** DNS id of this query */ /** DNS id of this query */
u16_t id; u16_t id;
/** Hash of the DNS name + query type we are querying.
* This hash is calculated so we can match the response that
* we are receiving. This is needed mainly for mDNS which is
* setting the DNS id to 0, which means that the id alone
* cannot be used to find correct pending query.
*/
u16_t query_hash;
} queries[CONFIG_DNS_NUM_CONCUR_QUERIES]; } queries[CONFIG_DNS_NUM_CONCUR_QUERIES];
/** Is this context in use */ /** Is this context in use */
@ -270,6 +278,23 @@ int dns_resolve_close(struct dns_resolve_context *ctx);
int dns_resolve_cancel(struct dns_resolve_context *ctx, int dns_resolve_cancel(struct dns_resolve_context *ctx,
u16_t dns_id); u16_t dns_id);
/**
* @brief Cancel a pending DNS query using id, name and type.
*
* @details This releases DNS resources used by a pending query.
*
* @param ctx DNS context
* @param dns_id DNS id of the pending query
* @param query_name Name of the resource we are trying to query (hostname)
* @param query_type Type of the query (A or AAAA)
*
* @return 0 if ok, <0 if error.
*/
int dns_resolve_cancel_with_name(struct dns_resolve_context *ctx,
u16_t dns_id,
const char *query_name,
enum dns_query_type query_type);
/** /**
* @brief Resolve DNS name. * @brief Resolve DNS name.
* *

View file

@ -18,6 +18,7 @@ LOG_MODULE_REGISTER(net_dns_resolve, CONFIG_DNS_RESOLVER_LOG_LEVEL);
#include <errno.h> #include <errno.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/crc.h>
#include <net/net_ip.h> #include <net/net_ip.h>
#include <net/net_pkt.h> #include <net/net_pkt.h>
#include <net/net_mgmt.h> #include <net/net_mgmt.h>
@ -342,12 +343,15 @@ static inline int get_cb_slot(struct dns_resolve_context *ctx)
} }
static inline int get_slot_by_id(struct dns_resolve_context *ctx, static inline int get_slot_by_id(struct dns_resolve_context *ctx,
u16_t dns_id) u16_t dns_id,
u16_t query_hash)
{ {
int i; int i;
for (i = 0; i < CONFIG_DNS_NUM_CONCUR_QUERIES; i++) { for (i = 0; i < CONFIG_DNS_NUM_CONCUR_QUERIES; i++) {
if (ctx->queries[i].cb && ctx->queries[i].id == dns_id) { if (ctx->queries[i].cb && ctx->queries[i].id == dns_id &&
(query_hash == 0 ||
ctx->queries[i].query_hash == query_hash)) {
return i; return i;
} }
} }
@ -359,20 +363,22 @@ static int dns_read(struct dns_resolve_context *ctx,
struct net_pkt *pkt, struct net_pkt *pkt,
struct net_buf *dns_data, struct net_buf *dns_data,
u16_t *dns_id, u16_t *dns_id,
struct net_buf *dns_cname) struct net_buf *dns_cname,
u16_t *query_hash)
{ {
struct dns_addrinfo info = { 0 }; struct dns_addrinfo info = { 0 };
/* Helper struct to track the dns msg received from the server */ /* Helper struct to track the dns msg received from the server */
struct dns_msg_t dns_msg; struct dns_msg_t dns_msg;
u32_t ttl; /* RR ttl, so far it is not passed to caller */ u32_t ttl; /* RR ttl, so far it is not passed to caller */
u8_t *src, *addr; u8_t *src, *addr;
const char *query_name;
int address_size; int address_size;
/* index that points to the current answer being analyzed */ /* index that points to the current answer being analyzed */
int answer_ptr; int answer_ptr;
int data_len; int data_len;
int items; int items;
int ret; int ret;
int server_idx, query_idx; int server_idx, query_idx = -1;
data_len = MIN(net_pkt_remaining_data(pkt), DNS_RESOLVER_MAX_BUF_SIZE); data_len = MIN(net_pkt_remaining_data(pkt), DNS_RESOLVER_MAX_BUF_SIZE);
@ -395,12 +401,6 @@ static int dns_read(struct dns_resolve_context *ctx,
*/ */
*dns_id = dns_unpack_header_id(dns_msg.msg); *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) { if (dns_header_rcode(dns_msg.msg) == DNS_HEADER_REFUSED) {
ret = DNS_EAI_FAIL; ret = DNS_EAI_FAIL;
goto quit; goto quit;
@ -434,43 +434,12 @@ static int dns_read(struct dns_resolve_context *ctx,
dns_msg.answer_offset = dns_msg.query_offset; dns_msg.answer_offset = dns_msg.query_offset;
} }
if (ctx->queries[query_idx].query_type == DNS_QUERY_TYPE_A) { /* Because in mDNS the DNS id is set to 0 and must be ignored
if (net_sin(&info.ai_addr)->sin_family == AF_INET6) { * on reply, we need to figure out the answer in order to find
ret = DNS_EAI_ADDRFAMILY; * the proper query. To simplify things, the normal DNS responses
goto quit; * are handled the same way.
} */
address_size = DNS_IPV4_LEN;
addr = (u8_t *)&net_sin(&info.ai_addr)->sin_addr;
info.ai_family = AF_INET;
info.ai_addr.sa_family = AF_INET;
info.ai_addrlen = sizeof(struct sockaddr_in);
} else if (ctx->queries[query_idx].query_type == DNS_QUERY_TYPE_AAAA) {
if (net_sin6(&info.ai_addr)->sin6_family == AF_INET) {
ret = DNS_EAI_ADDRFAMILY;
goto quit;
}
/* We cannot resolve IPv6 address if IPv6 is disabled. The reason
* being that "struct sockaddr" does not have enough space for
* IPv6 address in that case.
*/
#if defined(CONFIG_NET_IPV6)
address_size = DNS_IPV6_LEN;
addr = (u8_t *)&net_sin6(&info.ai_addr)->sin6_addr;
info.ai_family = AF_INET6;
info.ai_addr.sa_family = AF_INET6;
info.ai_addrlen = sizeof(struct sockaddr_in6);
#else
ret = DNS_EAI_FAMILY;
goto quit;
#endif
} else {
ret = DNS_EAI_FAMILY;
goto quit;
}
/* while loop to traverse the response */
answer_ptr = DNS_QUERY_POS; answer_ptr = DNS_QUERY_POS;
items = 0; items = 0;
server_idx = 0; server_idx = 0;
@ -483,6 +452,66 @@ static int dns_read(struct dns_resolve_context *ctx,
switch (dns_msg.response_type) { switch (dns_msg.response_type) {
case DNS_RESPONSE_IP: case DNS_RESPONSE_IP:
if (query_idx >= 0) {
goto query_known;
}
query_name = dns_msg.msg + dns_msg.query_offset;
/* Add \0 and query type (A or AAAA) to the hash */
*query_hash = crc16_ansi(query_name,
strlen(query_name) + 1 + 2);
query_idx = get_slot_by_id(ctx, *dns_id, *query_hash);
if (query_idx < 0) {
ret = DNS_EAI_SYSTEM;
goto quit;
}
if (ctx->queries[query_idx].query_type ==
DNS_QUERY_TYPE_A) {
if (net_sin(&info.ai_addr)->sin_family ==
AF_INET6) {
ret = DNS_EAI_ADDRFAMILY;
goto quit;
}
address_size = DNS_IPV4_LEN;
addr = (u8_t *)&net_sin(&info.ai_addr)->
sin_addr;
info.ai_family = AF_INET;
info.ai_addr.sa_family = AF_INET;
info.ai_addrlen = sizeof(struct sockaddr_in);
} else if (ctx->queries[query_idx].query_type ==
DNS_QUERY_TYPE_AAAA) {
if (net_sin6(&info.ai_addr)->sin6_family ==
AF_INET) {
ret = DNS_EAI_ADDRFAMILY;
goto quit;
}
/* We cannot resolve IPv6 address if IPv6 is
* disabled. The reason being that
* "struct sockaddr" does not have enough space
* for IPv6 address in that case.
*/
#if defined(CONFIG_NET_IPV6)
address_size = DNS_IPV6_LEN;
addr = (u8_t *)&net_sin6(&info.ai_addr)->
sin6_addr;
info.ai_family = AF_INET6;
info.ai_addr.sa_family = AF_INET6;
info.ai_addrlen = sizeof(struct sockaddr_in6);
#else
ret = DNS_EAI_FAMILY;
goto quit;
#endif
} else {
ret = DNS_EAI_FAMILY;
goto quit;
}
if (dns_msg.response_length < address_size) { if (dns_msg.response_length < address_size) {
/* it seems this is a malformed message */ /* it seems this is a malformed message */
ret = DNS_EAI_FAIL; ret = DNS_EAI_FAIL;
@ -490,9 +519,9 @@ static int dns_read(struct dns_resolve_context *ctx,
} }
src = dns_msg.msg + dns_msg.response_position; src = dns_msg.msg + dns_msg.response_position;
memcpy(addr, src, address_size); memcpy(addr, src, address_size);
query_known:
ctx->queries[query_idx].cb(DNS_EAI_INPROGRESS, &info, ctx->queries[query_idx].cb(DNS_EAI_INPROGRESS, &info,
ctx->queries[query_idx].user_data); ctx->queries[query_idx].user_data);
items++; items++;
@ -517,6 +546,21 @@ static int dns_read(struct dns_resolve_context *ctx,
server_idx++; server_idx++;
} }
if (query_idx < 0) {
/* If the query_idx is still unknown, try to get it here
* and hope it is found.
*/
query_name = dns_msg.msg + dns_msg.query_offset;
*query_hash = crc16_ansi(query_name,
strlen(query_name) + 1 + 2);
query_idx = get_slot_by_id(ctx, *dns_id, *query_hash);
if (query_idx < 0) {
ret = DNS_EAI_SYSTEM;
goto quit;
}
}
/* No IP addresses were found, so we take the last CNAME to generate /* No IP addresses were found, so we take the last CNAME to generate
* another query. Number of additional queries is controlled via Kconfig * another query. Number of additional queries is controlled via Kconfig
*/ */
@ -556,8 +600,9 @@ static int dns_read(struct dns_resolve_context *ctx,
return 0; return 0;
finished: finished:
dns_resolve_cancel(ctx, *dns_id); dns_resolve_cancel_with_name(ctx, *dns_id,
ctx->queries[query_idx].query,
ctx->queries[query_idx].query_type);
quit: quit:
net_pkt_unref(pkt); net_pkt_unref(pkt);
@ -574,6 +619,7 @@ static void cb_recv(struct net_context *net_ctx,
struct dns_resolve_context *ctx = user_data; struct dns_resolve_context *ctx = user_data;
struct net_buf *dns_cname = NULL; struct net_buf *dns_cname = NULL;
struct net_buf *dns_data = NULL; struct net_buf *dns_data = NULL;
u16_t query_hash = 0U;
u16_t dns_id = 0U; u16_t dns_id = 0U;
int ret, i; int ret, i;
@ -596,7 +642,7 @@ static void cb_recv(struct net_context *net_ctx,
goto quit; goto quit;
} }
ret = dns_read(ctx, pkt, dns_data, &dns_id, dns_cname); ret = dns_read(ctx, pkt, dns_data, &dns_id, dns_cname, &query_hash);
if (!ret) { if (!ret) {
/* We called the callback already in dns_read() if there /* We called the callback already in dns_read() if there
* was no errors. * was no errors.
@ -609,7 +655,7 @@ static void cb_recv(struct net_context *net_ctx,
int failure = 0; int failure = 0;
int j; int j;
i = get_slot_by_id(ctx, dns_id); i = get_slot_by_id(ctx, dns_id, query_hash);
if (i < 0) { if (i < 0) {
goto free_buf; goto free_buf;
} }
@ -638,7 +684,7 @@ static void cb_recv(struct net_context *net_ctx,
} }
quit: quit:
i = get_slot_by_id(ctx, dns_id); i = get_slot_by_id(ctx, dns_id, query_hash);
if (i < 0) { if (i < 0) {
goto free_buf; goto free_buf;
} }
@ -687,6 +733,13 @@ static int dns_write(struct dns_resolve_context *ctx,
return -EINVAL; return -EINVAL;
} }
/* Add \0 and query type (A or AAAA) to the hash. Note that
* the dns_qname->len contains the length of \0
*/
ctx->queries[query_idx].query_hash =
crc16_ansi(dns_data->data + DNS_MSG_HEADER_SIZE,
dns_qname->len + 2);
if (IS_ENABLED(CONFIG_NET_IPV6) && if (IS_ENABLED(CONFIG_NET_IPV6) &&
net_context_get_family(net_ctx) == AF_INET6) { net_context_get_family(net_ctx) == AF_INET6) {
net_context_set_ipv6_hop_limit(net_ctx, hop_limit); net_context_set_ipv6_hop_limit(net_ctx, hop_limit);
@ -707,14 +760,6 @@ static int dns_write(struct dns_resolve_context *ctx,
server_addr_len = sizeof(struct sockaddr_in6); server_addr_len = sizeof(struct sockaddr_in6);
} }
ret = net_context_sendto(net_ctx, dns_data->data, dns_data->len,
server, server_addr_len, NULL,
K_NO_WAIT, NULL);
if (ret < 0) {
NET_DBG("Cannot send query (%d)", ret);
return ret;
}
ret = k_delayed_work_submit(&ctx->queries[query_idx].timer, ret = k_delayed_work_submit(&ctx->queries[query_idx].timer,
ctx->queries[query_idx].timeout); ctx->queries[query_idx].timeout);
if (ret < 0) { if (ret < 0) {
@ -725,25 +770,38 @@ static int dns_write(struct dns_resolve_context *ctx,
return ret; return ret;
} }
NET_DBG("[%u] submitting work to server idx %d for id %u " NET_DBG("[%u] submitting work to server idx %d for id %u "
"timeout %u", "hash %u timeout %u",
query_idx, server_idx, dns_id, query_idx, server_idx, dns_id,
ctx->queries[query_idx].query_hash,
ctx->queries[query_idx].timeout); ctx->queries[query_idx].timeout);
ret = net_context_sendto(net_ctx, dns_data->data, dns_data->len,
server, server_addr_len, NULL,
K_NO_WAIT, NULL);
if (ret < 0) {
NET_DBG("Cannot send query (%d)", ret);
return ret;
}
return 0; return 0;
} }
int dns_resolve_cancel(struct dns_resolve_context *ctx, u16_t dns_id) static int dns_resolve_cancel_with_hash(struct dns_resolve_context *ctx,
u16_t dns_id,
u16_t query_hash,
const char *query_name)
{ {
int i; int i;
i = get_slot_by_id(ctx, dns_id); i = get_slot_by_id(ctx, dns_id, query_hash);
if (i < 0) { if (i < 0 || !ctx->queries[i].cb) {
return -ENOENT; return -ENOENT;
} }
NET_DBG("Cancelling DNS req %u", dns_id); NET_DBG("Cancelling DNS req %u (name %s type %d hash %u)", dns_id,
log_strdup(query_name), ctx->queries[i].query_type,
query_hash);
if (k_delayed_work_remaining_get(&ctx->queries[i].timer) > 0) { if (k_delayed_work_remaining_get(&ctx->queries[i].timer) > 0) {
k_delayed_work_cancel(&ctx->queries[i].timer); k_delayed_work_cancel(&ctx->queries[i].timer);
@ -755,14 +813,71 @@ int dns_resolve_cancel(struct dns_resolve_context *ctx, u16_t dns_id)
return 0; return 0;
} }
int dns_resolve_cancel_with_name(struct dns_resolve_context *ctx,
u16_t dns_id,
const char *query_name,
enum dns_query_type query_type)
{
u16_t query_hash = 0;
if (query_name) {
struct net_buf *buf;
u16_t len;
int ret;
/* Use net_buf as a temporary buffer to store the packed
* DNS name.
*/
buf = net_buf_alloc(&dns_msg_pool, ctx->buf_timeout);
if (!buf) {
return -ENOMEM;
}
ret = dns_msg_pack_qname(&len, buf->data, buf->size,
query_name);
if (ret >= 0) {
/* If the query string + \0 + query type (A or AAAA)
* does not fit the tmp buf, then bail out
*/
if ((len + 2) > buf->size) {
net_buf_unref(buf);
return -ENOMEM;
}
net_buf_add(buf, len);
net_buf_add_be16(buf, query_type);
query_hash = crc16_ansi(buf->data, len + 2);
}
net_buf_unref(buf);
if (ret < 0) {
return ret;
}
}
return dns_resolve_cancel_with_hash(ctx, dns_id, query_hash,
query_name);
}
int dns_resolve_cancel(struct dns_resolve_context *ctx, u16_t dns_id)
{
return dns_resolve_cancel_with_name(ctx, dns_id, NULL, 0);
}
static void query_timeout(struct k_work *work) static void query_timeout(struct k_work *work)
{ {
struct dns_pending_query *pending_query = struct dns_pending_query *pending_query =
CONTAINER_OF(work, struct dns_pending_query, timer); CONTAINER_OF(work, struct dns_pending_query, timer);
NET_DBG("Query timeout DNS req %u", pending_query->id); NET_DBG("Query timeout DNS req %u type %d hash %u", pending_query->id,
pending_query->query_type, pending_query->query_hash);
dns_resolve_cancel(pending_query->ctx, pending_query->id); (void)dns_resolve_cancel_with_hash(pending_query->ctx,
pending_query->id,
pending_query->query_hash,
pending_query->query);
} }
int dns_resolve_name(struct dns_resolve_context *ctx, int dns_resolve_name(struct dns_resolve_context *ctx,
@ -853,6 +968,7 @@ try_resolve:
ctx->queries[i].query_type = type; ctx->queries[i].query_type = type;
ctx->queries[i].user_data = user_data; ctx->queries[i].user_data = user_data;
ctx->queries[i].ctx = ctx; ctx->queries[i].ctx = ctx;
ctx->queries[i].query_hash = 0;
k_delayed_work_init(&ctx->queries[i].timer, query_timeout); k_delayed_work_init(&ctx->queries[i].timer, query_timeout);