diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 22d23e6dcd..b965e949fa 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -27,6 +27,7 @@ PSEUDOMODULES += dhcpv6_% PSEUDOMODULES += dhcpv6_client_dns PSEUDOMODULES += dhcpv6_client_ia_pd PSEUDOMODULES += dhcpv6_client_mud_url +PSEUDOMODULES += dns_msg PSEUDOMODULES += ecc_% PSEUDOMODULES += event_% PSEUDOMODULES += event_timeout_ztimer diff --git a/sys/Makefile b/sys/Makefile index fd6300e6a6..5f36158ceb 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -38,6 +38,9 @@ endif ifneq (,$(filter dhcpv6,$(USEMODULE))) DIRS += net/application_layer/dhcpv6 endif +ifneq (,$(filter dns,$(USEMODULE))) + DIRS += net/application_layer/dns +endif ifneq (,$(filter dsm,$(USEMODULE))) DIRS += net/dsm endif diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 49bf49918f..97f5fb55ce 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -106,6 +106,10 @@ ifneq (,$(filter dhcpv6_client,$(USEMODULE))) endif endif +ifneq (,$(filter dns_%,$(USEMODULE))) + USEMODULE += dns +endif + ifneq (,$(filter fuzzing,$(USEMODULE))) USEMODULE += netdev_test USEMODULE += gnrc_netif @@ -470,6 +474,7 @@ ifneq (,$(filter sock_async,$(USEMODULE))) endif ifneq (,$(filter sock_dns,$(USEMODULE))) + USEMODULE += dns_msg USEMODULE += sock_udp USEMODULE += sock_util USEMODULE += posix_headers diff --git a/sys/include/net/dns.h b/sys/include/net/dns.h index 09ee148e16..6728815309 100644 --- a/sys/include/net/dns.h +++ b/sys/include/net/dns.h @@ -24,6 +24,15 @@ extern "C" { #endif +/** + * @name DNS defines + * @{ + */ +#define DNS_TYPE_A (1) +#define DNS_TYPE_AAAA (28) +#define DNS_CLASS_IN (1) +/** @} */ + /** * @name Field lengths * @{ diff --git a/sys/include/net/dns/msg.h b/sys/include/net/dns/msg.h new file mode 100644 index 0000000000..9a0975f291 --- /dev/null +++ b/sys/include/net/dns/msg.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2021 Freie Universität Berlin + * + * 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. + */ + +/** + * @defgroup net_dns_msg DNS message parser and composer + * @ingroup net_dns + * @brief Parsing and composition of DNS messages + * @{ + * + * @file + * @brief Definitions for parsing and composition of DNS messages + * + * @author Martine Lenders + */ +#ifndef NET_DNS_MSG_H +#define NET_DNS_MSG_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup net_dns_msg_conf DNS message parsing and composition configuration + * @ingroup config + * @{ + */ +/** + * @brief maximum DNS message length + */ +#ifndef CONFIG_DNS_MSG_LEN +#define CONFIG_DNS_MSG_LEN (128U) +#endif /* CONFIG_DNS_MSG_LEN */ +/** @} */ + +/** + * @brief DNS internal structure + * + * @see [RFC 1035, section 4.1.1](https://tools.ietf.org/html/rfc1035#section-4.1.1) + */ +typedef struct { + uint16_t id; /**< identifier */ + /** + * @brief flags + * + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * |QR| Opcode |AA|TC|RD|RA|Z |AD|CD| RCODE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * - QR: Query (0) or Response (1) + * - Opcode: Kind of query + * - AA: Authoritative Answer + * - TC: Truncated + * - RD: Recursion Desired + * - RA: Recursion Available + * - Z: Reserved + * - AD: Authentic Data (see [RFC 4035](https://tools.ietf.org/html/4035)) + * - CD: Checking Disabled (see [RFC 4035](https://tools.ietf.org/html/4035)) + * - RCODE: Response Code + */ + uint16_t flags; + uint16_t qdcount; /**< number of question entries */ + uint16_t ancount; /**< number of answer resource records */ + uint16_t nscount; /**< number of name server resource records */ + uint16_t arcount; /**< number of additional records */ + uint8_t payload[]; /**< payload */ +} dns_hdr_t; + +/** + * @brief Composes a DNS query message + * + * The query will request an A or AAAA IN record for @p domain_name depending on + * @p family: + * + * - When @p family is `AF_INET` or `AF_UNSPEC` a query for an A record will be + * added + * - When @p family is `AF_INET6` or `AF_UNSPEC` a query for an AAAA record will + * be added + * + * @param[out] dns_buf A buffer of length @ref CONFIG_DNS_MSG_LEN + * @param[in] domain_name The domain name to query. + * @param[in] id The ID for the query. + * @param[in] family Either `AF_UNSPEC`, `AF_INET`, or `AF_INET6`. + * Determines the address records for @p domain_name + * queried. + * + * @return Size of the composed query in bytes. + */ +size_t dns_msg_compose_query(void *dns_buf, const char *domain_name, + uint16_t id, int family); + +/** + * @brief Parses a DNS response message + * + * @param[in] buf The message to parse. + * @param[in] len Length of @p buf. + * @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. + * + * @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(uint8_t *buf, size_t len, int family, void *addr_out); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_DNS_MSG_H */ +/** @} */ diff --git a/sys/include/net/sock/dns.h b/sys/include/net/sock/dns.h index f7d4d82261..5a9db35ea6 100644 --- a/sys/include/net/sock/dns.h +++ b/sys/include/net/sock/dns.h @@ -27,38 +27,22 @@ #include #include +#include "net/dns/msg.h" + #include "net/sock/udp.h" #ifdef __cplusplus extern "C" { #endif -/** - * @brief DNS internal structure - */ -typedef struct { - uint16_t id; /**< read */ - uint16_t flags; /**< DNS */ - uint16_t qdcount; /**< RFC */ - uint16_t ancount; /**< for */ - uint16_t nscount; /**< detailed */ - uint16_t arcount; /**< explanations */ - uint8_t payload[]; /**< !! */ -} sock_dns_hdr_t; - /** * @name DNS defines * @{ */ -#define DNS_TYPE_A (1) -#define DNS_TYPE_AAAA (28) -#define DNS_CLASS_IN (1) - #define SOCK_DNS_PORT (53) #define SOCK_DNS_RETRIES (2) -#define SOCK_DNS_BUF_LEN (128) /* we're in embedded context. */ -#define SOCK_DNS_MAX_NAME_LEN (SOCK_DNS_BUF_LEN - sizeof(sock_dns_hdr_t) - 4) +#define SOCK_DNS_MAX_NAME_LEN (CONFIG_DNS_MSG_LEN - sizeof(dns_hdr_t) - 4) /** @} */ /** diff --git a/sys/net/application_layer/dns/Makefile b/sys/net/application_layer/dns/Makefile new file mode 100644 index 0000000000..8e2c538ab6 --- /dev/null +++ b/sys/net/application_layer/dns/Makefile @@ -0,0 +1,5 @@ +SRC := + +SUBMODULE := 1 + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/application_layer/dns/msg.c b/sys/net/application_layer/dns/msg.c new file mode 100644 index 0000000000..8e119d9f7c --- /dev/null +++ b/sys/net/application_layer/dns/msg.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * + * 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. + */ + +/** + * @{ + * + * @file + * @author Kaspar Schleiser + * @author Martine Lenders + */ + +#include +#include +#include + +#include "net/dns.h" + +#ifdef RIOT_VERSION +#include "byteorder.h" +#endif + +#include "net/dns/msg.h" + +static ssize_t _enc_domain_name(uint8_t *out, const char *domain_name) +{ + /* + * DNS encodes domain names with "", e.g., + * "example.org" ends up as "\7example\3org" in the packet. + */ + uint8_t *part_start = out; + uint8_t *out_pos = ++out; + + char c; + + while ((c = *domain_name)) { + if (c == '.') { + /* replace dot with length of name part as byte */ + *part_start = (out_pos - part_start - 1); + part_start = out_pos++; + } + else { + *out_pos++ = c; + } + domain_name++; + } + + *part_start = (out_pos - part_start - 1); + *out_pos++ = 0; + + return out_pos - out + 1; +} + +static unsigned _put_short(uint8_t *out, uint16_t val) +{ + memcpy(out, &val, 2); + return 2; +} + +static unsigned _get_short(uint8_t *buf) +{ + uint16_t _tmp; + memcpy(&_tmp, buf, 2); + return _tmp; +} + +static ssize_t _skip_hostname(const uint8_t *buf, size_t len, uint8_t *bufpos) +{ + const uint8_t *buflim = buf + len; + unsigned res = 0; + + if (bufpos >= buflim) { + /* out-of-bound */ + return -EBADMSG; + } + /* handle DNS Message Compression */ + if (*bufpos >= 192) { + if ((bufpos + 2) >= buflim) { + return -EBADMSG; + } + return 2; + } + + while (bufpos[res]) { + res += bufpos[res] + 1; + if ((&bufpos[res]) >= buflim) { + /* out-of-bound */ + return -EBADMSG; + } + } + return res + 1; +} + +size_t dns_msg_compose_query(void *dns_buf, const char *domain_name, + uint16_t id, int family) +{ + uint8_t *buf = dns_buf; + + dns_hdr_t *hdr = (dns_hdr_t*) buf; + memset(hdr, 0, sizeof(*hdr)); + hdr->id = id; + hdr->flags = htons(0x0120); + hdr->qdcount = htons(1 + (family == AF_UNSPEC)); + + uint8_t *bufpos = buf + sizeof(*hdr); + + unsigned _name_ptr; + if ((family == AF_INET6) || (family == AF_UNSPEC)) { + _name_ptr = (bufpos - buf); + bufpos += _enc_domain_name(bufpos, domain_name); + bufpos += _put_short(bufpos, htons(DNS_TYPE_AAAA)); + bufpos += _put_short(bufpos, htons(DNS_CLASS_IN)); + } + + if ((family == AF_INET) || (family == AF_UNSPEC)) { + if (family == AF_UNSPEC) { + bufpos += _put_short(bufpos, htons((0xc000) | (_name_ptr))); + } + else { + bufpos += _enc_domain_name(bufpos, domain_name); + } + bufpos += _put_short(bufpos, htons(DNS_TYPE_A)); + bufpos += _put_short(bufpos, htons(DNS_CLASS_IN)); + } + return bufpos - buf; +} + +int dns_msg_parse_reply(uint8_t *buf, size_t len, int family, void *addr_out) +{ + const uint8_t *buflim = buf + len; + dns_hdr_t *hdr = (dns_hdr_t *)buf; + uint8_t *bufpos = buf + sizeof(*hdr); + + /* skip all queries that are part of the reply */ + for (unsigned n = 0; n < ntohs(hdr->qdcount); n++) { + ssize_t tmp = _skip_hostname(buf, len, bufpos); + if (tmp < 0) { + return tmp; + } + bufpos += tmp; + /* skip type and class of query */ + bufpos += (RR_TYPE_LENGTH + RR_CLASS_LENGTH); + } + + for (unsigned n = 0; n < ntohs(hdr->ancount); n++) { + ssize_t tmp = _skip_hostname(buf, len, bufpos); + if (tmp < 0) { + return tmp; + } + bufpos += tmp; + if ((bufpos + RR_TYPE_LENGTH + RR_CLASS_LENGTH + + RR_TTL_LENGTH + sizeof(uint16_t)) >= buflim) { + return -EBADMSG; + } + uint16_t _type = ntohs(_get_short(bufpos)); + bufpos += RR_TYPE_LENGTH; + uint16_t class = ntohs(_get_short(bufpos)); + bufpos += RR_CLASS_LENGTH; + bufpos += RR_TTL_LENGTH; /* skip ttl */ + + unsigned addrlen = ntohs(_get_short(bufpos)); + /* skip unwanted answers */ + if ((class != DNS_CLASS_IN) || + ((_type == DNS_TYPE_A) && (family == AF_INET6)) || + ((_type == DNS_TYPE_AAAA) && (family == AF_INET)) || + ! ((_type == DNS_TYPE_A) || ((_type == DNS_TYPE_AAAA)) + )) { + if (addrlen > len) { + /* buffer wraps around memory space */ + return -EBADMSG; + } + bufpos += addrlen; + /* other out-of-bound is checked in `_skip_hostname()` at start of + * loop */ + continue; + } + if (((addrlen != INADDRSZ) && (family == AF_INET)) || + ((addrlen != IN6ADDRSZ) && (family == AF_INET6)) || + ((addrlen != IN6ADDRSZ) && (addrlen != INADDRSZ) && + (family == AF_UNSPEC))) { + return -EBADMSG; + } + bufpos += RR_RDLENGTH_LENGTH; + if ((bufpos + addrlen) > buflim) { + return -EBADMSG; + } + + memcpy(addr_out, bufpos, addrlen); + return addrlen; + } + + return -EBADMSG; +} + +/** @} */ diff --git a/sys/net/application_layer/sock_dns/dns.c b/sys/net/application_layer/sock_dns/dns.c index b9e80029c4..3a8fa6ea3d 100644 --- a/sys/net/application_layer/sock_dns/dns.c +++ b/sys/net/application_layer/sock_dns/dns.c @@ -15,163 +15,22 @@ * @} */ -#include -#include -#include +#include #include "net/dns.h" +#include "net/dns/msg.h" #include "net/sock/udp.h" #include "net/sock/dns.h" -#ifdef RIOT_VERSION -#include "byteorder.h" -#endif - /* min domain name length is 1, so minimum record length is 7 */ -#define DNS_MIN_REPLY_LEN (unsigned)(sizeof(sock_dns_hdr_t ) + 7) +#define DNS_MIN_REPLY_LEN (unsigned)(sizeof(dns_hdr_t ) + 7) /* global DNS server UDP endpoint */ sock_udp_ep_t sock_dns_server; -static ssize_t _enc_domain_name(uint8_t *out, const char *domain_name) -{ - /* - * DNS encodes domain names with "", e.g., - * "example.org" ends up as "\7example\3org" in the packet. - */ - uint8_t *part_start = out; - uint8_t *out_pos = ++out; - - char c; - - while ((c = *domain_name)) { - if (c == '.') { - /* replace dot with length of name part as byte */ - *part_start = (out_pos - part_start - 1); - part_start = out_pos++; - } - else { - *out_pos++ = c; - } - domain_name++; - } - - *part_start = (out_pos - part_start - 1); - *out_pos++ = 0; - - return out_pos - out + 1; -} - -static unsigned _put_short(uint8_t *out, uint16_t val) -{ - memcpy(out, &val, 2); - return 2; -} - -static unsigned _get_short(uint8_t *buf) -{ - uint16_t _tmp; - memcpy(&_tmp, buf, 2); - return _tmp; -} - -static ssize_t _skip_hostname(const uint8_t *buf, size_t len, uint8_t *bufpos) -{ - const uint8_t *buflim = buf + len; - unsigned res = 0; - - if (bufpos >= buflim) { - /* out-of-bound */ - return -EBADMSG; - } - /* handle DNS Message Compression */ - if (*bufpos >= 192) { - if ((bufpos + 2) >= buflim) { - return -EBADMSG; - } - return 2; - } - - while (bufpos[res]) { - res += bufpos[res] + 1; - if ((&bufpos[res]) >= buflim) { - /* out-of-bound */ - return -EBADMSG; - } - } - return res + 1; -} - -static int _parse_dns_reply(uint8_t *buf, size_t len, void* addr_out, int family) -{ - const uint8_t *buflim = buf + len; - sock_dns_hdr_t *hdr = (sock_dns_hdr_t*) buf; - uint8_t *bufpos = buf + sizeof(*hdr); - - /* skip all queries that are part of the reply */ - for (unsigned n = 0; n < ntohs(hdr->qdcount); n++) { - ssize_t tmp = _skip_hostname(buf, len, bufpos); - if (tmp < 0) { - return tmp; - } - bufpos += tmp; - /* skip type and class of query */ - bufpos += (RR_TYPE_LENGTH + RR_CLASS_LENGTH); - } - - for (unsigned n = 0; n < ntohs(hdr->ancount); n++) { - ssize_t tmp = _skip_hostname(buf, len, bufpos); - if (tmp < 0) { - return tmp; - } - bufpos += tmp; - if ((bufpos + RR_TYPE_LENGTH + RR_CLASS_LENGTH + - RR_TTL_LENGTH + sizeof(uint16_t)) >= buflim) { - return -EBADMSG; - } - uint16_t _type = ntohs(_get_short(bufpos)); - bufpos += RR_TYPE_LENGTH; - uint16_t class = ntohs(_get_short(bufpos)); - bufpos += RR_CLASS_LENGTH; - bufpos += RR_TTL_LENGTH; /* skip ttl */ - - unsigned addrlen = ntohs(_get_short(bufpos)); - /* skip unwanted answers */ - if ((class != DNS_CLASS_IN) || - ((_type == DNS_TYPE_A) && (family == AF_INET6)) || - ((_type == DNS_TYPE_AAAA) && (family == AF_INET)) || - ! ((_type == DNS_TYPE_A) || ((_type == DNS_TYPE_AAAA)) - )) { - if (addrlen > len) { - /* buffer wraps around memory space */ - return -EBADMSG; - } - bufpos += addrlen; - /* other out-of-bound is checked in `_skip_hostname()` at start of - * loop */ - continue; - } - if (((addrlen != INADDRSZ) && (family == AF_INET)) || - ((addrlen != IN6ADDRSZ) && (family == AF_INET6)) || - ((addrlen != IN6ADDRSZ) && (addrlen != INADDRSZ) && - (family == AF_UNSPEC))) { - return -EBADMSG; - } - bufpos += RR_RDLENGTH_LENGTH; - if ((bufpos + addrlen) > buflim) { - return -EBADMSG; - } - - memcpy(addr_out, bufpos, addrlen); - return addrlen; - } - - return -1; -} - int sock_dns_query(const char *domain_name, void *addr_out, int family) { - static uint8_t dns_buf[SOCK_DNS_BUF_LEN]; + static uint8_t dns_buf[CONFIG_DNS_MSG_LEN]; if (sock_dns_server.port == 0) { return -ECONNREFUSED; @@ -188,46 +47,19 @@ int sock_dns_query(const char *domain_name, void *addr_out, int family) goto out; } - uint16_t id = 0; /* random? */ + uint16_t id = 0; for (int i = 0; i < SOCK_DNS_RETRIES; i++) { - uint8_t *buf = dns_buf; + size_t buflen = dns_msg_compose_query(dns_buf, domain_name, id, family); - sock_dns_hdr_t *hdr = (sock_dns_hdr_t*) buf; - memset(hdr, 0, sizeof(*hdr)); - hdr->id = id; - hdr->flags = htons(0x0120); - hdr->qdcount = htons(1 + (family == AF_UNSPEC)); - - uint8_t *bufpos = buf + sizeof(*hdr); - - unsigned _name_ptr; - if ((family == AF_INET6) || (family == AF_UNSPEC)) { - _name_ptr = (bufpos - buf); - bufpos += _enc_domain_name(bufpos, domain_name); - bufpos += _put_short(bufpos, htons(DNS_TYPE_AAAA)); - bufpos += _put_short(bufpos, htons(DNS_CLASS_IN)); - } - - if ((family == AF_INET) || (family == AF_UNSPEC)) { - if (family == AF_UNSPEC) { - bufpos += _put_short(bufpos, htons((0xc000) | (_name_ptr))); - } - else { - bufpos += _enc_domain_name(bufpos, domain_name); - } - bufpos += _put_short(bufpos, htons(DNS_TYPE_A)); - bufpos += _put_short(bufpos, htons(DNS_CLASS_IN)); - } - - res = sock_udp_send(&sock_dns, buf, (bufpos-buf), NULL); + res = sock_udp_send(&sock_dns, dns_buf, buflen, NULL); if (res <= 0) { continue; } res = sock_udp_recv(&sock_dns, dns_buf, sizeof(dns_buf), 1000000LU, NULL); if (res > 0) { if (res > (int)DNS_MIN_REPLY_LEN) { - if ((res = _parse_dns_reply(dns_buf, res, addr_out, - family)) > 0) { + if ((res = dns_msg_parse_reply(dns_buf, res, family, + addr_out)) > 0) { goto out; } }