1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-17 05:12:57 +01:00

sock_dns: implement DNS cache

This commit is contained in:
Benjamin Valentin 2022-02-19 14:20:40 +01:00
parent 54544c0a36
commit 077a41a719
9 changed files with 316 additions and 7 deletions

View File

@ -215,6 +215,7 @@ PSEUDOMODULES += sock_async
PSEUDOMODULES += sock_aux_local
PSEUDOMODULES += sock_aux_rssi
PSEUDOMODULES += sock_aux_timestamp
PSEUDOMODULES += sock_dns_cache
PSEUDOMODULES += sock_dtls
PSEUDOMODULES += sock_ip
PSEUDOMODULES += sock_tcp

View File

@ -602,6 +602,12 @@ ifneq (,$(filter sock_dns,$(USEMODULE)))
USEMODULE += posix_headers
endif
ifneq (,$(filter sock_dns_cache,$(USEMODULE)))
USEMODULE += sock_dns
USEMODULE += ztimer_msec
USEMODULE += checksum
endif
ifneq (,$(filter sock_util,$(USEMODULE)))
USEMODULE += posix_inet
USEMODULE += fmt

View File

@ -104,13 +104,14 @@ size_t dns_msg_compose_query(void *dns_buf, const char *domain_name,
* @param[in] family The address family used to compose the query for
* this response (see @ref dns_msg_compose_query())
* @param[out] addr_out The IP address returned by the response.
* @param[out] ttl The live time of the entry in seconds
*
* @return Length of the @p addr_out on success.
* @return -EBADMSG, when an address corresponding to @p family can not be found
* in @p buf.
*/
int dns_msg_parse_reply(const uint8_t *buf, size_t len, int family,
void *addr_out);
void *addr_out, uint32_t *ttl);
#ifdef __cplusplus
}

View File

@ -131,7 +131,7 @@ size_t dns_msg_compose_query(void *dns_buf, const char *domain_name,
}
int dns_msg_parse_reply(const uint8_t *buf, size_t len, int family,
void *addr_out)
void *addr_out, uint32_t *ttl)
{
const uint8_t *buflim = buf + len;
const dns_hdr_t *hdr = (dns_hdr_t *)buf;
@ -162,7 +162,10 @@ int dns_msg_parse_reply(const uint8_t *buf, size_t len, int family,
bufpos += RR_TYPE_LENGTH;
uint16_t class = ntohs(_get_short(bufpos));
bufpos += RR_CLASS_LENGTH;
bufpos += RR_TTL_LENGTH; /* skip ttl */
if (ttl) {
*ttl = byteorder_bebuftohl(bufpos);
}
bufpos += RR_TTL_LENGTH;
unsigned addrlen = ntohs(_get_short(bufpos));
/* skip unwanted answers */

View File

@ -712,7 +712,7 @@ static void _resp_handler(const gcoap_request_memo_t *memo, coap_pkt_t *pdu,
case COAP_FORMAT_DNS_MESSAGE:
case COAP_FORMAT_NONE:
context->res = dns_msg_parse_reply(data, data_len, family,
context->addr_out);
context->addr_out, NULL);
if ((ENABLE_DEBUG) && (context->res < 0)) {
DEBUG("gcoap_dns: Unable to parse DNS reply: %d\n",
context->res);

View File

@ -1 +1,7 @@
SRC += dns.c
ifneq (,$(filter sock_dns_cache, $(USEMODULE)))
SRC += dns_cache.c
endif
include $(RIOTBASE)/Makefile.base

View File

@ -25,6 +25,7 @@
#include "net/dns/msg.h"
#include "net/sock/udp.h"
#include "net/sock/dns.h"
#include "dns_cache.h"
/* min domain name length is 1, so minimum record length is 7 */
#define DNS_MIN_REPLY_LEN (unsigned)(sizeof(dns_hdr_t) + 7)
@ -67,6 +68,8 @@ void auto_init_sock_dns(void)
int sock_dns_query(const char *domain_name, void *addr_out, int family)
{
ssize_t res;
sock_udp_t sock_dns;
static uint8_t dns_buf[CONFIG_DNS_MSG_LEN];
if (sock_dns_server.port == 0) {
@ -77,9 +80,12 @@ int sock_dns_query(const char *domain_name, void *addr_out, int family)
return -ENOSPC;
}
sock_udp_t sock_dns;
res = sock_dns_cache_query(domain_name, addr_out, family);
if (res) {
return res;
}
ssize_t res = sock_udp_create(&sock_dns, NULL, &sock_dns_server, 0);
res = sock_udp_create(&sock_dns, NULL, &sock_dns_server, 0);
if (res) {
goto out;
}
@ -95,8 +101,10 @@ int sock_dns_query(const char *domain_name, void *addr_out, int family)
res = sock_udp_recv(&sock_dns, dns_buf, sizeof(dns_buf), 1000000LU, NULL);
if (res > 0) {
if (res > (int)DNS_MIN_REPLY_LEN) {
uint32_t ttl;
if ((res = dns_msg_parse_reply(dns_buf, res, family,
addr_out)) > 0) {
addr_out, &ttl)) > 0) {
sock_dns_cache_add(domain_name, addr_out, res, ttl);
goto out;
}
}

View File

@ -0,0 +1,188 @@
/*
* Copyright (C) 2022 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup net_sock_dns
* @{
* @file
* @brief DNS cache implementation
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
* @}
*/
#include "bitfield.h"
#include "checksum/fletcher32.h"
#include "net/sock/dns.h"
#include "time_units.h"
#include "dns_cache.h"
#include "ztimer.h"
#define ENABLE_DEBUG 0
#include "debug.h"
static struct dns_cache_entry {
uint32_t hash;
uint32_t expires;
union {
#if IS_ACTIVE(SOCK_HAS_IPV4)
ipv4_addr_t v4;
#endif
#if IS_ACTIVE(SOCK_HAS_IPV6)
ipv6_addr_t v6;
#endif
} addr;
} cache[CONFIG_DNS_CACHE_SIZE];
#if IS_ACTIVE(SOCK_HAS_IPV4) && IS_ACTIVE(SOCK_HAS_IPV6)
BITFIELD(cache_is_v6, CONFIG_DNS_CACHE_SIZE);
static inline uint8_t _get_len(unsigned idx)
{
return bf_isset(cache_is_v6, idx) ? 16 : 4;
}
#elif IS_ACTIVE(SOCK_HAS_IPV4)
static inline uint8_t _get_len(unsigned idx)
{
(void)idx;
return 4;
}
#elif IS_ACTIVE(SOCK_HAS_IPV6)
static inline uint8_t _get_len(unsigned idx)
{
(void)idx;
return 16;
}
#endif
static void _set_len(unsigned idx, uint8_t len)
{
#if IS_ACTIVE(SOCK_HAS_IPV4) && IS_ACTIVE(SOCK_HAS_IPV6)
if (len == 16) {
bf_set(cache_is_v6, idx);
} else {
bf_unset(cache_is_v6, idx);
}
#else
(void)idx;
(void)len;
#endif
}
static bool _is_empty(unsigned idx)
{
const uint8_t len = _get_len(idx);
const uint8_t *addr = (void *)&cache[idx].addr;
for (unsigned i = 0; i < len; ++i) {
if (addr[i]) {
return false;
}
}
return true;
}
static void _set_empty(unsigned idx)
{
memset(&cache[idx].addr, 0, _get_len(idx));
}
static uint8_t _addr_len(int family)
{
switch (family) {
#if IS_ACTIVE(SOCK_HAS_IPV4)
case AF_INET:
return sizeof(ipv4_addr_t);
#endif
#if IS_ACTIVE(SOCK_HAS_IPV6)
case AF_INET6:
return sizeof(ipv6_addr_t);
#endif
case AF_UNSPEC:
return 0;
default:
return 255;
}
}
static uint32_t _hash(const void *data, size_t len)
{
return fletcher32(data, (len + 1) / 2);
}
int sock_dns_cache_query(const char *domain_name, void *addr_out, int family)
{
uint32_t now = ztimer_now(ZTIMER_MSEC) / MS_PER_SEC;
uint32_t hash = _hash(domain_name, strlen(domain_name));
uint8_t addr_len = _addr_len(family);
for (unsigned i = 0; i < CONFIG_DNS_CACHE_SIZE; ++i) {
/* empty slot */
if (_is_empty(i)) {
continue;
}
/* TTL expired - invalidate slot */
if (now > cache[i].expires) {
DEBUG("dns_cache[%u] expired\n", i);
_set_empty(i);
continue;
}
/* check if hash and length match */
if (cache[i].hash == hash && (!addr_len || addr_len == _get_len(i))) {
DEBUG("dns_cache[%u] hit\n", i);
memcpy(addr_out, &cache[i].addr, _get_len(i));
return _get_len(i);
}
}
DEBUG("dns_cache miss\n");
return 0;
}
static void _add_entry(uint8_t i, uint32_t hash, const void *addr_out,
int addr_len, uint32_t expires)
{
DEBUG("dns_cache[%u] add cache entry\n", i);
cache[i].hash = hash;
cache[i].expires = expires;
memcpy(&cache[i].addr, addr_out, addr_len);
_set_len(i, addr_len);
}
void sock_dns_cache_add(const char *domain_name, const void *addr_out,
int addr_len, uint32_t ttl)
{
uint32_t now = ztimer_now(ZTIMER_MSEC) / MS_PER_SEC;
uint32_t hash = _hash(domain_name, strlen(domain_name));
uint32_t oldest = ttl;
int idx = -1;
assert(addr_len == 4 || addr_len == 16);
DEBUG("dns_cache: lifetime of %s is %"PRIu32" s\n", domain_name, ttl);
for (unsigned i = 0; i < CONFIG_DNS_CACHE_SIZE; ++i) {
if (now > cache[i].expires || _is_empty(i)) {
_add_entry(i, hash, addr_out, addr_len, now + ttl);
return;
}
if (cache[i].hash == hash && _get_len(i) == addr_len) {
DEBUG("dns_cache[%u] update ttl\n", i);
cache[i].expires = now + ttl;
return;
}
uint32_t _ttl = cache[i].expires - now;
if (_ttl < oldest) {
oldest = _ttl;
idx = i;
}
}
if (idx >= 0) {
DEBUG("dns_cache: evict first entry to expire\n");
_add_entry(idx, hash, addr_out, addr_len, now + ttl);
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2022 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup net_sock_dns
*
* @brief DNS cache
*
* @{
*
* @file
* @brief DNS cache definitions
*
* This implements a simple DNS cache for A and AAAA entries.
*
* The cache eviction strategy is based on the remaining time to live
* of the cache entries, so the first entry to expire will be evicted.
*
* This is fine if there are only few cache entries and cache eviction
* is unlikely.
* If there is communication to many different hosts, the addition of a
* least-recently used counter could likely improve the behavior.
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*/
#ifndef DNS_CACHE_H
#define DNS_CACHE_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Number of DNS cache entries
*/
#ifndef CONFIG_DNS_CACHE_SIZE
#define CONFIG_DNS_CACHE_SIZE 4
#endif
#if IS_USED(MODULE_SOCK_DNS_CACHE) || DOXYGEN
/**
* @brief Get IP address for a DNS name from the DNS cache
*
* @param[in] domain_name DNS name to resolve into address
* @param[out] addr_out buffer to write result into
* @param[in] family Either AF_INET, AF_INET6 or AF_UNSPEC
*
* @return the size of the resolved address on success
* @return < 0 otherwise
*/
int sock_dns_cache_query(const char *domain_name, void *addr_out, int family);
/**
* @brief Add an IP address for a DNS name to the DNS cache
*
* @param[in] domain_name DNS name to resolve into address
* @param[in] addr_out buffer to write result into
* @param[in] addr_len length of the address in bytes
* @param[in] ttl lifetime of the entry in seconds
*/
void sock_dns_cache_add(const char *domain_name, const void *addr_out,
int addr_len, uint32_t ttl);
#else
static inline int sock_dns_cache_query(const char *domain_name,
void *addr_out, int family)
{
(void)domain_name;
(void)addr_out;
(void)family;
return 0;
}
static inline void sock_dns_cache_add(const char *domain_name, const void *addr_out,
int addr_len, uint32_t ttl)
{
(void)domain_name;
(void)addr_out;
(void)addr_len;
(void)ttl;
}
#endif
#ifdef __cplusplus
}
#endif
#endif /* DNS_CACHE_H */
/** @} */