diff --git a/sys/Makefile b/sys/Makefile index b7724719a709..592efe128cf4 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -158,6 +158,9 @@ endif ifneq (,$(filter posix_inet,$(USEMODULE))) DIRS += posix/inet endif +ifneq (,$(filter posix_netdb,$(USEMODULE))) + DIRS += posix/netdb +endif ifneq (,$(filter posix_select,$(USEMODULE))) DIRS += posix/select endif diff --git a/sys/Makefile.dep b/sys/Makefile.dep index b27127765ef7..30d38e2cc584 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -382,6 +382,11 @@ ifneq (,$(filter posix_sleep,$(USEMODULE))) USEMODULE += posix_headers endif +ifneq (,$(filter posix_netdb,$(USEMODULE))) + USEMODULE += posix_inet + USEMODULE += sock_dns +endif + ifneq (,$(filter posix_inet,$(USEMODULE))) USEMODULE += posix_headers endif diff --git a/sys/posix/include/netdb.h b/sys/posix/include/netdb.h new file mode 100644 index 000000000000..7a4022ca5716 --- /dev/null +++ b/sys/posix/include/netdb.h @@ -0,0 +1,179 @@ +/* + * 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. + */ + +/** + * @ingroup posix + * + * @{ + * @file + * @brief posix netdb implementation + * @author Hendrik van Essen + */ + +#ifndef NETDB_H +#define NETDB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sys/bytes.h" + +/** Errors used by the DNS API functions */ +/** + * @brief The name could not be resolved at this time. Future attempts may succeed. + */ +#define EAI_AGAIN 1 +/** + * @brief The flags parameter had an invalid value. + */ +#define EAI_BADFLAGS 2 +/** + * @brief A non-recoverable error occurred when attempting to resolve the name. + */ +#define EAI_FAIL 3 +/** + * @brief The address family was not recognized. + */ +#define EAI_FAMILY 4 +/** + * @brief There was a memory allocation failure when trying to allocate storage + * for the return value. + */ +#define EAI_MEMORY 5 +/** + * @brief The name does not resolve for the supplied parameters. Neither + * nodename nor servname were supplied. At least one of these need to + * be supplied. + */ +#define EAI_NONAME 6 +/** + * @brief The service passed was not recognized for the specified socket type. + */ +#define EAI_SERVICE 7 +/** + * @brief The intended socket type was not recognized. + */ +#define EAI_SOCKTYPE 8 +/** + * @brief A system error occurred. + */ +#define EAI_SYSTEM 9 + +/* Input flags for struct addrinfo */ +/** + * @brief Returned address information shall be suitable for use in binding a + * socket for accepting incoming connections for the specified service. + */ +#define AI_PASSIVE 0x01 +/** + * @brief Determine the canonical name corresponding to nodename. + */ +#define AI_CANONNAME 0x02 +/** + * @brief Supplied nodename string is a numeric host address string. + */ +#define AI_NUMERICHOST 0x04 +/** + * @brief Supplied servname string is a numeric port string. + */ +#define AI_NUMERICSERV 0x08 +/** + * @brief When specified along with ai_family = AF_INET6, getaddrinfo() returns + * IPv4-mapped IPv6 addresses on finding no matching IPv6 addresses. + * + * @note: Not supported yet + * @todo: Implement + */ +#define AI_V4MAPPED 0x10 +/** + * @brief When AI_V4MAPPED is specified, then getaddrinfo() returns all matching + * IPv6 and IPv4 addresses. Without AI_V4MAPPED this flag will be ignored. + * + * @note: Not supported yet + * @todo: Implement + */ +#define AI_ALL 0x20 +/** + * @brief IPv4 addresses are returned only if an IPv4 address is configured on + * the local system, and IPv6 addresses are returned only if an IPv6 + * address is configured on the local system. + * + * @note: Not supported yet + * @todo: Implement + */ +#define AI_ADDRCONFIG 0x40 + +/** + * @brief Structure that contains information about the address of a service provider + */ +struct addrinfo { + int ai_flags; /**< Input flags. */ + int ai_family; /**< Address family of socket. */ + int ai_socktype; /**< Socket type. */ + int ai_protocol; /**< Protocol of socket. */ + socklen_t ai_addrlen; /**< Length of socket address. */ + struct sockaddr *ai_addr; /**< Socket address of socket. */ + char *ai_canonname; /**< Canonical name of service location. */ + struct addrinfo *ai_next; /**< Pointer to next in list. */ +}; + +/** + * + * @brief Free the addrinfo reference returned by getaddrinfo() + * + * Frees one or more addrinfo structures returned by getaddrinfo(), along with + * any additional storage associated with those structures. If the ai_next field + * of the structure is not null, the entire list of structures is freed. + * + * @param ai struct addrinfo to free + */ +void freeaddrinfo(struct addrinfo *ai); + +/** + * + * @brief Translates the name of a service location and/or a service name and + * returns a set of socket addresses. + * + * @note Due to implementation limitations only a single entry is returned. + * + * Translates the name of a service location (for example, a host name) and/or + * a service name and returns a set of socket addresses and associated + * information to be used in creating a socket with which to address the + * specified service. + * Memory for the result is allocated internally and must be freed by calling + * freeaddrinfo()! + * + * @param nodename descriptive name or address string of the host (may be + * NULL -> local address) + * @param servname port number as string of NULL + * @param hints structure containing input values that set socktype and + * protocol + * @param res pointer to a pointer where to store the result (is set + * to NULL in case of failure) + * + * @return 0 On success, non-zero on failure + * @retval EAI_AGAIN Recoverable error, try again later + * @retval EAI_BADFLAGS Invalid value in flag parameters (hints->ai_flags) + * @retval EAI_FAIL Non-recoverable error + * @retval EAI_FAMILY Unsupported address family + * @retval EAI_MEMORY No memory available to receive data or to allocate result object + * @retval EAI_NONAME Name does not resolve + * @retval EAI_SERVICE Service does not resolve + */ +int getaddrinfo(const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ +#endif /* NETDB_H */ diff --git a/sys/posix/netdb/Makefile b/sys/posix/netdb/Makefile new file mode 100644 index 000000000000..793c8b6919ae --- /dev/null +++ b/sys/posix/netdb/Makefile @@ -0,0 +1,3 @@ +MODULE = posix_netdb + +include $(RIOTBASE)/Makefile.base diff --git a/sys/posix/netdb/netdb.c b/sys/posix/netdb/netdb.c new file mode 100644 index 000000000000..187cc8ac4469 --- /dev/null +++ b/sys/posix/netdb/netdb.c @@ -0,0 +1,276 @@ +/* + * 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. + */ + +/** + * @ingroup posix + * + * @{ + * @file + * @brief posix netdb implementation + * @author Hendrik van Essen + * @} + */ + +#include +#include + +#include + +#include "sys/socket.h" +#include "netinet/in.h" +#include "net/dns.h" + +#include "netdb.h" + +static const struct addrinfo default_hints = +{ + .ai_flags = 0, + .ai_family = AF_UNSPEC, + .ai_socktype = 0, + .ai_protocol = 0, + .ai_addrlen = 0, + .ai_addr = NULL, + .ai_canonname = NULL, + .ai_next = NULL +}; + +typedef struct ipvx_addr { + sa_family_t type; + union { + struct in_addr ipv4; + struct in6_addr ipv6; + }; +} ipvx_addr_t; + +void freeaddrinfo(struct addrinfo *ai) +{ + struct addrinfo *next; + + while (ai != NULL) { + next = ai->ai_next; + free(ai->ai_canonname); + free(ai->ai_addr); + free(ai); + ai = next; + } +} + +int getaddrinfo(const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) +{ + ipvx_addr_t addr; + int port = 0; + size_t name_len = 0; + + if (res == NULL) { + return EAI_FAIL; + } + else { + *res = NULL; + } + + if (nodename != NULL) { + if ((nodename[0] == '\0') || (nodename[0] == '*' && nodename[1] == '\0')) { + nodename = NULL; + } + } + + if (servname != NULL) { + if ((servname[0] == '\0') || (servname[0] == '*' && servname[1] == '\0')) { + servname = NULL; + } + } + + if ((nodename == NULL) && (servname == NULL)) { + return EAI_NONAME; + } + + if (nodename != NULL) { + name_len = strlen(nodename); + if (name_len > SOCK_DNS_MAX_NAME_LEN) { + /* invalid name length */ + return EAI_FAIL; + } + } + + if (hints == NULL) { + hints = &default_hints; + } + + if (hints->ai_flags + & ~(AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | AI_NUMERICSERV + | AI_V4MAPPED | AI_ALL | AI_ADDRCONFIG) + ) { + return EAI_BADFLAGS; + } + + if ((hints->ai_flags & AI_CANONNAME) && nodename == NULL) { + return EAI_BADFLAGS; + } + + if ((hints->ai_family != AF_UNSPEC) && + (hints->ai_family != AF_INET) && + (hints->ai_family != AF_INET6)) { + return EAI_FAMILY; + } + + if (servname != NULL) { /* service location specified, try to resolve */ + if (hints->ai_flags & AI_NUMERICSERV) { /* service name specified: convert to port number */ + port = atoi(servname); + + if (port == 0 && (servname[0] != '0')) { + /* atoi failed - service was not numeric */ + return EAI_SERVICE; + } + + if ((port < 0) || (port > (int)0xffff)) { + /* invalid port number */ + return EAI_SERVICE; + } + } + else { + /* non-numeric service names not supported */ + return EAI_SERVICE; + } + } + + if (nodename != NULL) { /* node location specified, try to resolve */ + if (hints->ai_flags & AI_NUMERICHOST) { /* no DNS lookup, just parse for an address string */ + + if (hints->ai_family == AF_INET) { + addr.type = AF_INET; + } + else if (hints->ai_family == AF_INET6) { + addr.type = AF_INET6; + } + else { /* auto-detect IP version */ + if (strstr(nodename, ":") != NULL) { /* IPv6 */ + addr.type = AF_INET6; + } + else if (strstr(nodename, ".") != NULL) { /* IPv4 */ + addr.type = AF_INET; + } + else { + /* try IPv6 */ + addr.type = AF_INET6; + } + } + + if (addr.type == AF_INET) { + if (inet_pton(AF_INET, nodename, &addr.ipv4) != 1) { + return EAI_NONAME; + } + } + else if (addr.type == AF_INET6) { + if (inet_pton(AF_INET6, nodename, &addr.ipv6) != 1) { + return EAI_NONAME; + } + } + } + else { /* do actual DNS lookup */ + uint8_t addr_buf[16] = { 0 }; + int res = dns_query(nodename, addr_buf, hints->ai_family); + switch (res) { + case -ETIMEDOUT: + /* fall-through */ + case -EAGAIN: + return EAI_AGAIN; + + case -ENOMEM: + return EAI_MEMORY; + + case INADDRSZ: + addr.type = AF_INET; + memcpy(&addr.ipv4, addr_buf, INADDRSZ); + break; + + case IN6ADDRSZ: + addr.type = AF_INET6; + memcpy(&addr.ipv6, addr_buf, IN6ADDRSZ); + break; + + default: + return EAI_FAIL; + } + } + } + else { /* node location not specified, use loopback address */ + if (hints->ai_flags & AI_PASSIVE) { + if (hints->ai_family == AF_INET6) { + memcpy(&(addr.ipv6), &ipv6_addr_unspecified, sizeof(ipv6_addr_t)); + } + else { + memcpy(&(addr.ipv4), &ipv4_addr_any, sizeof(ipv4_addr_t)); + } + } + else { + if (hints->ai_family == AF_INET6) { + memcpy(&(addr.ipv6), &ipv6_addr_loopback, sizeof(ipv6_addr_t)); + } + else { + memcpy(&(addr.ipv4), &ipv4_addr_loopback, sizeof(ipv4_addr_t)); + } + } + } + + /* prepare return */ + + struct addrinfo *ai; + ai = (struct addrinfo*) calloc(1, sizeof(struct addrinfo)); + + if (ai == NULL) { + return EAI_MEMORY; + } + + struct sockaddr_storage *sas; + sas = (struct sockaddr_storage*) calloc(1, sizeof(struct sockaddr_storage)); + + if (sas == NULL) { + free(ai); + return EAI_MEMORY; + } + + if (addr.type == AF_INET) { + ai->ai_family = AF_INET; + + struct sockaddr_in *sa4 = (struct sockaddr_in*) sas; + sa4->sin_addr = addr.ipv4; + sa4->sin_family = AF_INET; + sa4->sin_port = htons(port); + } + else if (addr.type == AF_INET6) { + ai->ai_family = AF_INET6; + + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*) sas; + sa6->sin6_addr = addr.ipv6; + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(port); + } + + ai->ai_addr = (struct sockaddr*) sas; + ai->ai_addrlen = sizeof(struct sockaddr_storage); + ai->ai_socktype = hints->ai_socktype; + ai->ai_protocol = hints->ai_protocol; + + if (nodename != NULL) { + ai->ai_canonname = (char*) malloc(name_len + 1); + + if (ai->ai_canonname == NULL) { + free(ai); + free(sas); + return EAI_MEMORY; + } + + memcpy(ai->ai_canonname, nodename, name_len); + ai->ai_canonname[name_len] = 0; + } + + *res = ai; + + return 0; +}