blob: 0e7e1ac39eb07369a6008a6d4a4c26a969397f31 [file] [log] [blame]
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_dns_dispatcher, CONFIG_DNS_SOCKET_DISPATCHER_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/slist.h>
#include <zephyr/net/buf.h>
#include <zephyr/net/dns_resolve.h>
#include <zephyr/net/socket_service.h>
#include "dns_pack.h"
static K_MUTEX_DEFINE(lock);
static sys_slist_t sockets;
#define DNS_RESOLVER_MIN_BUF 1
#define DNS_RESOLVER_BUF_CTR (DNS_RESOLVER_MIN_BUF + \
CONFIG_DNS_RESOLVER_ADDITIONAL_BUF_CTR)
NET_BUF_POOL_DEFINE(dns_msg_pool, DNS_RESOLVER_BUF_CTR,
DNS_RESOLVER_MAX_BUF_SIZE, 0, NULL);
static int dns_dispatch(struct dns_socket_dispatcher *dispatcher,
int sock, struct sockaddr *addr, size_t addrlen,
struct net_buf *dns_data, size_t buf_len)
{
/* Helper struct to track the dns msg received from the server */
struct dns_msg_t dns_msg;
bool is_query;
int data_len;
int ret;
data_len = MIN(buf_len, DNS_RESOLVER_MAX_BUF_SIZE);
dns_msg.msg = dns_data->data;
dns_msg.msg_size = data_len;
/* Make sure that we can read DNS id, flags and rcode */
if (dns_msg.msg_size < (sizeof(uint16_t) + sizeof(uint16_t))) {
ret = -EINVAL;
goto done;
}
if (dns_header_rcode(dns_msg.msg) == DNS_HEADER_REFUSED) {
ret = -EINVAL;
goto done;
}
is_query = (dns_header_qr(dns_msg.msg) == DNS_QUERY);
if (is_query) {
if (dispatcher->type == DNS_SOCKET_RESPONDER) {
/* Call the responder callback */
ret = dispatcher->cb(dispatcher->ctx, sock,
addr, addrlen,
dns_data, data_len);
} else if (dispatcher->pair) {
ret = dispatcher->pair->cb(dispatcher->pair->ctx, sock,
addr, addrlen,
dns_data, data_len);
} else {
/* Discard the message as it was a query and there are none
* expecting a query.
*/
ret = -ENOENT;
}
} else {
/* So this was an answer to a query that was made by resolver.
*/
if (dispatcher->type == DNS_SOCKET_RESOLVER) {
/* Call the resolver callback */
ret = dispatcher->cb(dispatcher->ctx, sock,
addr, addrlen,
dns_data, data_len);
} else if (dispatcher->pair) {
ret = dispatcher->pair->cb(dispatcher->pair->ctx, sock,
addr, addrlen,
dns_data, data_len);
} else {
/* Discard the message as it was not a query reply and
* we were a reply.
*/
ret = -ENOENT;
}
}
done:
return ret;
}
static int recv_data(struct net_socket_service_event *pev)
{
struct dns_socket_dispatcher *dispatcher = pev->user_data;
socklen_t optlen = sizeof(int);
struct net_buf *dns_data = NULL;
struct sockaddr addr;
size_t addrlen;
int family, sock_error;
int ret = 0, len;
k_mutex_lock(&dispatcher->lock, K_FOREVER);
(void)zsock_getsockopt(pev->event.fd, SOL_SOCKET,
SO_DOMAIN, &family, &optlen);
if ((pev->event.revents & ZSOCK_POLLERR) ||
(pev->event.revents & ZSOCK_POLLNVAL)) {
(void)zsock_getsockopt(pev->event.fd, SOL_SOCKET,
SO_ERROR, &sock_error, &optlen);
NET_ERR("Receiver IPv%d socket error (%d)",
family == AF_INET ? 4 : 6, sock_error);
ret = DNS_EAI_SYSTEM;
goto unlock;
}
addrlen = (family == AF_INET) ? sizeof(struct sockaddr_in) :
sizeof(struct sockaddr_in6);
dns_data = net_buf_alloc(&dns_msg_pool, dispatcher->buf_timeout);
if (!dns_data) {
ret = DNS_EAI_MEMORY;
goto unlock;
}
ret = zsock_recvfrom(pev->event.fd, dns_data->data,
net_buf_max_len(dns_data), 0,
(struct sockaddr *)&addr, &addrlen);
if (ret < 0) {
ret = -errno;
NET_ERR("recv failed on IPv%d socket (%d)",
family == AF_INET ? 4 : 6, -ret);
goto free_buf;
}
len = ret;
ret = dns_dispatch(dispatcher, pev->event.fd,
(struct sockaddr *)&addr, addrlen,
dns_data, len);
free_buf:
if (dns_data) {
net_buf_unref(dns_data);
}
unlock:
k_mutex_unlock(&dispatcher->lock);
return ret;
}
void dns_dispatcher_svc_handler(struct k_work *work)
{
struct net_socket_service_event *pev =
CONTAINER_OF(work, struct net_socket_service_event, work);
int ret;
ret = recv_data(pev);
if (ret < 0 && ret != DNS_EAI_ALLDONE && ret != -ENOENT) {
NET_ERR("DNS recv error (%d)", ret);
}
}
int dns_dispatcher_register(struct dns_socket_dispatcher *ctx)
{
struct dns_socket_dispatcher *entry, *next, *found = NULL;
sys_snode_t *prev_node = NULL;
bool dup = false;
size_t addrlen;
int ret = 0;
k_mutex_lock(&lock, K_FOREVER);
if (sys_slist_find(&sockets, &ctx->node, &prev_node)) {
ret = -EALREADY;
goto out;
}
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&sockets, entry, next, node) {
/* Refuse to register context if we have identical context
* already registered.
*/
if (ctx->type == entry->type &&
ctx->local_addr.sa_family == entry->local_addr.sa_family) {
if (net_sin(&entry->local_addr)->sin_port ==
net_sin(&ctx->local_addr)->sin_port) {
dup = true;
continue;
}
}
/* Then check if there is an entry with same family and
* port already in the list. If there is then we can act
* as a dispatcher for the given socket. Do not break
* from the loop even if we found an entry so that we
* can catch possible duplicates.
*/
if (found == NULL && ctx->type != entry->type &&
ctx->local_addr.sa_family == entry->local_addr.sa_family) {
if (net_sin(&entry->local_addr)->sin_port ==
net_sin(&ctx->local_addr)->sin_port) {
found = entry;
continue;
}
}
}
if (dup) {
/* Found a duplicate */
ret = -EALREADY;
goto out;
}
if (found != NULL) {
entry = found;
if (entry->pair != NULL) {
NET_DBG("Already paired connection found.");
ret = -EALREADY;
goto out;
}
entry->pair = ctx;
/* Basically we are now done. If there is incoming data to
* the socket, the dispatcher will then pass it to the correct
* recipient.
*/
ret = 0;
goto out;
}
ctx->buf_timeout = DNS_BUF_TIMEOUT;
if (ctx->local_addr.sa_family == AF_INET) {
addrlen = sizeof(struct sockaddr_in);
} else {
addrlen = sizeof(struct sockaddr_in6);
}
/* Bind and then register a socket service with this combo */
ret = zsock_bind(ctx->sock, &ctx->local_addr, addrlen);
if (ret < 0) {
ret = -errno;
NET_DBG("Cannot bind DNS socket %d (%d)", ctx->sock, ret);
goto out;
}
ctx->pair = NULL;
ret = net_socket_service_register(ctx->svc, ctx->fds, ctx->fds_len, ctx);
if (ret < 0) {
NET_DBG("Cannot register socket service (%d)", ret);
goto out;
}
sys_slist_prepend(&sockets, &ctx->node);
out:
k_mutex_unlock(&lock);
return ret;
}
int dns_dispatcher_unregister(struct dns_socket_dispatcher *ctx)
{
k_mutex_lock(&lock, K_FOREVER);
(void)sys_slist_find_and_remove(&sockets, &ctx->node);
k_mutex_unlock(&lock);
return 0;
}
void dns_dispatcher_init(void)
{
sys_slist_init(&sockets);
}