diff --git a/Makefile.dep b/Makefile.dep index 9037185244a42030926fee4ed64a7b99495b7313..90402796c29e245d34aa8ea0c8f7713312e47e08 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -611,6 +611,10 @@ ifneq (,$(filter vfs,$(USEMODULE))) endif endif +ifneq (,$(filter sock_dns,$(USEMODULE))) + USEMODULE += sock_util +endif + # include package dependencies -include $(USEPKG:%=$(RIOTPKG)/%/Makefile.dep) diff --git a/sys/Makefile b/sys/Makefile index 8a28bd5f959e92c2e3e1fd2d3933fbfcbc3d50d8..206985f5c170ff189afdf4ade8d961ed1d297ebe 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -114,6 +114,9 @@ endif ifneq (,$(filter sock_util,$(USEMODULE))) DIRS += net/sock endif +ifneq (,$(filter sock_dns,$(USEMODULE))) + DIRS += net/application_layer/dns +endif ifneq (,$(filter constfs,$(USEMODULE))) DIRS += fs/constfs diff --git a/sys/include/net/sock/dns.h b/sys/include/net/sock/dns.h new file mode 100644 index 0000000000000000000000000000000000000000..c2acf2fe62432a98d109dddf42a6e063a300d69f --- /dev/null +++ b/sys/include/net/sock/dns.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * + * 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_sock_dns DNS sock API + * @ingroup net_sock + * + * @brief Sock DNS client + * + * @{ + * + * @file + * @brief DNS sock definitions + * + * @author Kaspar Schleiser <kaspar@schleiser.de> + */ + +#ifndef SOCK_DNS_H +#define SOCK_DNS_H + +#include <errno.h> +#include <stdint.h> +#include <unistd.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_MAX_NAME_LEN (64U) /* we're in embedded context. */ +#define SOCK_DNS_QUERYBUF_LEN (sizeof(sock_dns_hdr_t) + 4 + SOCK_DNS_MAX_NAME_LEN) +/** @} */ + +/** + * @brief Get IP address for DNS name + * + * This function will synchronously try to resolve a DNS A or AAAA record by contacting + * the DNS server specified in the global variable @ref sock_dns_server. + * + * By supplying AF_INET, AF_INET6 or AF_UNSPEC in @p family requesting of A + * records (IPv4), AAAA records (IPv6) or both can be selected. + * + * This fuction will return the first DNS record it receives. IF both A and + * AAAA are requested, AAAA will be preferred. + * + * @note @p addr_out needs to provide space for any possible result! + * (4byte when family==AF_INET, 16byte otherwise) + * + * @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 0 on success + * @return !=0 otherwise + */ +int sock_dns_query(const char *domain_name, void *addr_out, int family); + +/** + * @brief global DNS server endpoint + */ +extern sock_udp_ep_t sock_dns_server; + +#ifdef __cplusplus +} +#endif + +#endif /* SOCK_DNS_H */ +/** @} */ diff --git a/sys/net/application_layer/dns/Makefile b/sys/net/application_layer/dns/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..5e55b6e793a1009a0f7bd5c56fb434288dc84607 --- /dev/null +++ b/sys/net/application_layer/dns/Makefile @@ -0,0 +1,2 @@ +MODULE=sock_dns +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/application_layer/dns/dns.c b/sys/net/application_layer/dns/dns.c new file mode 100644 index 0000000000000000000000000000000000000000..42c06d67e4cfa887951c25e4bf4e5dd81139c203 --- /dev/null +++ b/sys/net/application_layer/dns/dns.c @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * + * 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 sock DNS client implementation + * @author Kaspar Schleiser <kaspar@schleiser.de> + * @} + */ + +#include <string.h> +#include <stdio.h> + +#include "net/sock/udp.h" +#include "net/sock/dns.h" + +#ifdef RIOT_VERSION +#include "byteorder.h" +#define ntohs NTOHS +#define htons HTONS +#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) + +static ssize_t _enc_domain_name(uint8_t *out, const char *domain_name) +{ + /* + * DNS encodes domain names with "<len><part><len><part>", 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 size_t _skip_hostname(uint8_t *buf) +{ + uint8_t *bufpos = buf; + + /* handle DNS Message Compression */ + if (*bufpos >= 192) { + return 2; + } + + while(*bufpos) { + bufpos += *bufpos + 1; + } + return (bufpos - buf + 1); +} + +static int _parse_dns_reply(uint8_t *buf, size_t len, void* addr_out, int family) +{ + 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++) { + bufpos += _skip_hostname(bufpos); + bufpos += 4; /* skip type and class of query */ + } + + for (unsigned n = 0; n < ntohs(hdr->ancount); n++) { + bufpos += _skip_hostname(bufpos); + uint16_t _type = ntohs(_get_short(bufpos)); + bufpos += 2; + uint16_t class = ntohs(_get_short(bufpos)); + bufpos += 2; + bufpos += 4; /* skip ttl */ + + unsigned addrlen = ntohs(_get_short(bufpos)); + bufpos += 2; + if ((bufpos + addrlen) > (buf + len)) { + return -EBADMSG; + } + + /* 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)) + )) { + bufpos += addrlen; + continue; + } + + memcpy(addr_out, bufpos, addrlen); + return addrlen; + } + + return -1; +} + +int sock_dns_query(const char *domain_name, void *addr_out, int family) +{ + uint8_t buf[SOCK_DNS_QUERYBUF_LEN]; + uint8_t reply_buf[512]; + + if (strlen(domain_name) > SOCK_DNS_MAX_NAME_LEN) { + return -ENOSPC; + } + + sock_dns_hdr_t *hdr = (sock_dns_hdr_t*) buf; + memset(hdr, 0, sizeof(*hdr)); + hdr->id = 0; /* random? */ + 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)); + } + + sock_udp_t sock_dns; + + ssize_t res = sock_udp_create(&sock_dns, NULL, &sock_dns_server, 0); + if (res) { + puts("a"); + goto out; + } + + for (int i = 0; i < SOCK_DNS_RETRIES; i++) { + res = sock_udp_send(&sock_dns, buf, (bufpos-buf), NULL); + if (res <= 0) { + continue; + } + res = sock_udp_recv(&sock_dns, reply_buf, sizeof(reply_buf), 1000000LU, NULL); + if ((res > 0) && (res > (int)DNS_MIN_REPLY_LEN)) { + if ((res = _parse_dns_reply(reply_buf, res, addr_out, family)) > 0) { + goto out; + } + } + } + +out: + sock_udp_close(&sock_dns); + return res; +}