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 */
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];
/** 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,
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.
*

View file

@ -18,6 +18,7 @@ LOG_MODULE_REGISTER(net_dns_resolve, CONFIG_DNS_RESOLVER_LOG_LEVEL);
#include <errno.h>
#include <stdlib.h>
#include <sys/crc.h>
#include <net/net_ip.h>
#include <net/net_pkt.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,
u16_t dns_id)
u16_t dns_id,
u16_t query_hash)
{
int 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;
}
}
@ -359,20 +363,22 @@ 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 net_buf *dns_cname,
u16_t *query_hash)
{
struct dns_addrinfo info = { 0 };
/* 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;
const char *query_name;
int address_size;
/* index that points to the current answer being analyzed */
int answer_ptr;
int data_len;
int items;
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);
@ -395,12 +401,6 @@ static int dns_read(struct dns_resolve_context *ctx,
*/
*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;
@ -434,43 +434,12 @@ static int dns_read(struct dns_resolve_context *ctx,
dns_msg.answer_offset = dns_msg.query_offset;
}
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;
}
/* Because in mDNS the DNS id is set to 0 and must be ignored
* on reply, we need to figure out the answer in order to find
* the proper query. To simplify things, the normal DNS responses
* 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;
items = 0;
server_idx = 0;
@ -483,6 +452,66 @@ static int dns_read(struct dns_resolve_context *ctx,
switch (dns_msg.response_type) {
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) {
/* it seems this is a malformed message */
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;
memcpy(addr, src, address_size);
query_known:
ctx->queries[query_idx].cb(DNS_EAI_INPROGRESS, &info,
ctx->queries[query_idx].user_data);
items++;
@ -517,6 +546,21 @@ static int dns_read(struct dns_resolve_context *ctx,
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
* 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;
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:
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 net_buf *dns_cname = NULL;
struct net_buf *dns_data = NULL;
u16_t query_hash = 0U;
u16_t dns_id = 0U;
int ret, i;
@ -596,7 +642,7 @@ static void cb_recv(struct net_context *net_ctx,
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) {
/* We called the callback already in dns_read() if there
* was no errors.
@ -609,7 +655,7 @@ static void cb_recv(struct net_context *net_ctx,
int failure = 0;
int j;
i = get_slot_by_id(ctx, dns_id);
i = get_slot_by_id(ctx, dns_id, query_hash);
if (i < 0) {
goto free_buf;
}
@ -638,7 +684,7 @@ static void cb_recv(struct net_context *net_ctx,
}
quit:
i = get_slot_by_id(ctx, dns_id);
i = get_slot_by_id(ctx, dns_id, query_hash);
if (i < 0) {
goto free_buf;
}
@ -687,6 +733,13 @@ static int dns_write(struct dns_resolve_context *ctx,
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) &&
net_context_get_family(net_ctx) == AF_INET6) {
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);
}
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,
ctx->queries[query_idx].timeout);
if (ret < 0) {
@ -725,25 +770,38 @@ static int dns_write(struct dns_resolve_context *ctx,
return ret;
}
NET_DBG("[%u] submitting work to server idx %d for id %u "
"timeout %u",
"hash %u timeout %u",
query_idx, server_idx, dns_id,
ctx->queries[query_idx].query_hash,
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;
}
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;
i = get_slot_by_id(ctx, dns_id);
if (i < 0) {
i = get_slot_by_id(ctx, dns_id, query_hash);
if (i < 0 || !ctx->queries[i].cb) {
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) {
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;
}
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)
{
struct dns_pending_query *pending_query =
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,
@ -853,6 +968,7 @@ try_resolve:
ctx->queries[i].query_type = type;
ctx->queries[i].user_data = user_data;
ctx->queries[i].ctx = ctx;
ctx->queries[i].query_hash = 0;
k_delayed_work_init(&ctx->queries[i].timer, query_timeout);