From 003e2d1711d8d43f50c5033f00be1b513f18b533 Mon Sep 17 00:00:00 2001 From: Lionel Flandrin Date: Mon, 30 Apr 2018 16:56:17 +0200 Subject: [PATCH] Problem: address parsing code is tied to the TCP code Solution: Factor the code into a different file with a well defined API and add unit tests. --- CMakeLists.txt | 1 + Makefile.am | 4 +- src/ip_resolver.cpp | 610 ++++++++++++++++++++++ src/ip_resolver.hpp | 79 +++ src/tcp_address.cpp | 675 ++----------------------- src/tcp_address.hpp | 30 +- unittests/CMakeLists.txt | 2 + unittests/unittest_ip_resolver.cpp | 383 ++++++++++++++ unittests/unittest_ip_resolver_dns.cpp | 316 ++++++++++++ 9 files changed, 1440 insertions(+), 660 deletions(-) create mode 100644 src/ip_resolver.cpp create mode 100644 src/ip_resolver.hpp create mode 100644 unittests/unittest_ip_resolver.cpp create mode 100644 unittests/unittest_ip_resolver_dns.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a7e204786c..0c3eaa3459 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -602,6 +602,7 @@ set (cxx-sources udp_address.cpp scatter.cpp gather.cpp + ip_resolver.cpp zap_client.cpp # at least for VS, the header files must also be listed address.hpp diff --git a/Makefile.am b/Makefile.am index bcb60c2436..e4952d6766 100644 --- a/Makefile.am +++ b/Makefile.am @@ -895,7 +895,9 @@ if ENABLE_STATIC test_apps += \ unittests/unittest_poller \ unittests/unittest_ypipe \ - unittests/unittest_mtrie + unittests/unittest_mtrie \ + unittests/unittest_ip_resolver \ + unittests/unittest_ip_resolver_dns unittests_unittest_poller_SOURCES = unittests/unittest_poller.cpp unittests_unittest_poller_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS} $(CODE_COVERAGE_CPPFLAGS) diff --git a/src/ip_resolver.cpp b/src/ip_resolver.cpp new file mode 100644 index 0000000000..0b9832c2ad --- /dev/null +++ b/src/ip_resolver.cpp @@ -0,0 +1,610 @@ +#include "precompiled.hpp" +#include +#include + +#include "macros.hpp" +#include "stdint.hpp" +#include "err.hpp" +#include "ip.hpp" + +#ifndef ZMQ_HAVE_WINDOWS +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "ip_resolver.hpp" + +zmq::ip_resolver_t::ip_resolver_t () : + bindable_wanted (false), + nic_name_allowed (false), + ipv6_wanted (false), + port_expected (false), + dns_allowed (false) +{ +} + +zmq::ip_resolver_t &zmq::ip_resolver_t::bindable (bool bindable_) +{ + bindable_wanted = bindable_; + + return *this; +} + +zmq::ip_resolver_t &zmq::ip_resolver_t::allow_nic_name (bool allow_) +{ + nic_name_allowed = allow_; + + return *this; +} + +zmq::ip_resolver_t &zmq::ip_resolver_t::ipv6 (bool ipv6_) +{ + ipv6_wanted = ipv6_; + + return *this; +} + +// If true we expect that the host will be followed by a colon and a port +// number or service name +zmq::ip_resolver_t &zmq::ip_resolver_t::expect_port (bool expect_) +{ + port_expected = expect_; + + return *this; +} + +zmq::ip_resolver_t &zmq::ip_resolver_t::allow_dns (bool allow_) +{ + dns_allowed = allow_; + + return *this; +} + +int zmq::ip_resolver_t::resolve (ip_addr_t *ip_addr_, const char *name_) +{ + std::string addr; + uint16_t port; + + if (port_expected) { + // We expect 'addr:port'. It's important to use str*r*chr to only get + // the latest colon since IPv6 addresses use colons as delemiters. + const char *delim = strrchr (name_, ':'); + + if (delim == NULL) { + errno = EINVAL; + return -1; + } + + addr = std::string (name_, delim - name_); + std::string port_str = std::string (delim + 1); + + if (port_str == "*" || port_str == "0") { + // Resolve wildcard to 0 to allow autoselection of port + port = 0; + } else { + // Parse the port number (0 is not a valid port). + port = (uint16_t) atoi (port_str.c_str ()); + if (port == 0) { + errno = EINVAL; + return -1; + } + } + } else { + addr = std::string (name_); + port = 0; + } + + // Trim any square brackets surronding the address. Used for IPv6 addresses + // to remove the confusion with the port delimiter. Should we validate that + // the brackets are present if 'addr' contains ':' ? + if (addr.size () >= 2 && addr[0] == '[' && addr[addr.size () - 1] == ']') { + addr = addr.substr (1, addr.size () - 2); + } + + // Look for an interface name / zone_id in the address + // Reference: https://tools.ietf.org/html/rfc4007 + std::size_t pos = addr.rfind ('%'); + uint32_t zone_id = 0; + + if (pos != std::string::npos) { + std::string if_str = addr.substr (pos + 1); + addr = addr.substr (0, pos); + + if (isalpha (if_str.at (0))) { +#if !defined ZMQ_HAVE_WINDOWS_TARGET_XP && !defined ZMQ_HAVE_WINDOWS_UWP \ + && !defined ZMQ_HAVE_VXWORKS + zone_id = if_nametoindex (if_str.c_str ()); +#else + // The function 'if_nametoindex' is not supported on Windows XP. + // If we are targeting XP using a vxxx_xp toolset then fail. + // This is brutal as this code could be run on later windows clients + // meaning the IPv6 zone_id cannot have an interface name. + // This could be fixed with a runtime check. + zone_id = 0; +#endif + } else { + zone_id = (uint32_t) atoi (if_str.c_str ()); + } + + if (zone_id == 0) { + errno = EINVAL; + return -1; + } + } + + bool resolved = false; + const char *addr_str = addr.c_str(); + + if (bindable_wanted && addr == "*") { + // Return an ANY address + resolved = true; + + if (ipv6_wanted) { + sockaddr_in6 *ip6_addr = &ip_addr_->ipv6; + + memset (ip6_addr, 0, sizeof (*ip6_addr)); + ip6_addr->sin6_family = AF_INET6; +#ifdef ZMQ_HAVE_VXWORKS + struct in6_addr newaddr = IN6ADDR_ANY_INIT; + memcpy (&ip6_addr->sin6_addr, &newaddr, sizeof (in6_addr)); +#else + memcpy (&ip6_addr->sin6_addr, &in6addr_any, sizeof (in6addr_any)); +#endif + } else { + sockaddr_in *ip4_addr = &ip_addr_->ipv4; + memset (ip4_addr, 0, sizeof (*ip4_addr)); + ip4_addr->sin_family = AF_INET; + ip4_addr->sin_addr.s_addr = htonl (INADDR_ANY); + } + } + + if (!resolved && nic_name_allowed) { + // Try to resolve the string as a NIC name. + int rc = resolve_nic_name (ip_addr_, addr_str); + + if (rc == 0) { + resolved = true; + } else if (errno != ENODEV) { + return rc; + } + } + + if (!resolved) { + int rc = resolve_getaddrinfo(ip_addr_, addr_str); + + if (rc != 0) { + return rc; + } + resolved = true; + } + + // Store the port into the structure. We could get 'getaddrinfo' to do it + // for us but since we don't resolve service names it's a bit overkill and + // we'd still have to do it manually when the address is resolved by + // 'resolve_nic_name' + if (ip_addr_->generic.sa_family == AF_INET6) { + ip_addr_->ipv6.sin6_port = htons (port); + ip_addr_->ipv6.sin6_scope_id = zone_id; + } else { + ip_addr_->ipv4.sin_port = htons (port); + } + + return 0; +} + +int zmq::ip_resolver_t::resolve_getaddrinfo (ip_addr_t *ip_addr_, + const char *addr_) +{ +#if defined ZMQ_HAVE_OPENVMS && defined __ia64 + __addrinfo64 *res = NULL; + __addrinfo64 req; +#else + addrinfo *res = NULL; + addrinfo req; +#endif + + memset (&req, 0, sizeof (req)); + + // Choose IPv4 or IPv6 protocol family. Note that IPv6 allows for + // IPv4-in-IPv6 addresses. + req.ai_family = ipv6_wanted ? AF_INET6 : AF_INET; + + // Arbitrary, not used in the output, but avoids duplicate results. + req.ai_socktype = SOCK_STREAM; + + req.ai_flags = 0; + + if (bindable_wanted) { + req.ai_flags |= AI_PASSIVE; + } + + if (!dns_allowed) { + req.ai_flags |= AI_NUMERICHOST; + } + +#if defined AI_V4MAPPED + // In this API we only require IPv4-mapped addresses when + // no native IPv6 interfaces are available (~AI_ALL). + // This saves an additional DNS roundtrip for IPv4 addresses. + if (req.ai_family == AF_INET6) { + req.ai_flags |= AI_V4MAPPED; + } +#endif + + // Resolve the literal address. Some of the error info is lost in case + // of error, however, there's no way to report EAI errors via errno. + int rc = getaddrinfo (addr_, NULL, &req, &res); + +#if defined AI_V4MAPPED + // Some OS do have AI_V4MAPPED defined but it is not supported in getaddrinfo() + // returning EAI_BADFLAGS. Detect this and retry + if (rc == EAI_BADFLAGS && (req.ai_flags & AI_V4MAPPED)) { + req.ai_flags &= ~AI_V4MAPPED; + rc = getaddrinfo (addr_, NULL, &req, &res); + } +#endif + +#if defined ZMQ_HAVE_WINDOWS + // Resolve specific case on Windows platform when using IPv4 address + // with ZMQ_IPv6 socket option. + if ((req.ai_family == AF_INET6) && (rc == WSAHOST_NOT_FOUND)) { + req.ai_family = AF_INET; + rc = getaddrinfo (addr_, NULL, &req, &res); + } +#endif + + if (rc) { + switch (rc) { + case EAI_MEMORY: + errno = ENOMEM; + break; + default: + if (bindable_wanted) { + errno = ENODEV; + } else { + errno = EINVAL; + } + break; + } + return -1; + } + + // Use the first result. + zmq_assert (res != NULL); + zmq_assert ((size_t) res->ai_addrlen <= sizeof (*ip_addr_)); + memcpy (ip_addr_, res->ai_addr, res->ai_addrlen); + + // Cleanup getaddrinfo after copying the possibly referenced result. + freeaddrinfo (res); + + return 0; +} + +#ifdef ZMQ_HAVE_SOLARIS +#include + +// On Solaris platform, network interface name can be queried by ioctl. +int zmq::ip_resolver_t::resolve_nic_name (ip_addr_t *ip_addr_, + const char *nic_) +{ + // Create a socket. + const int fd = open_socket (AF_INET, SOCK_DGRAM, 0); + errno_assert (fd != -1); + + // Retrieve number of interfaces. + lifnum ifn; + ifn.lifn_family = AF_INET; + ifn.lifn_flags = 0; + int rc = ioctl (fd, SIOCGLIFNUM, (char *) &ifn); + errno_assert (rc != -1); + + // Allocate memory to get interface names. + const size_t ifr_size = sizeof (struct lifreq) * ifn.lifn_count; + char *ifr = (char *) malloc (ifr_size); + alloc_assert (ifr); + + // Retrieve interface names. + lifconf ifc; + ifc.lifc_family = AF_INET; + ifc.lifc_flags = 0; + ifc.lifc_len = ifr_size; + ifc.lifc_buf = ifr; + rc = ioctl (fd, SIOCGLIFCONF, (char *) &ifc); + errno_assert (rc != -1); + + // Find the interface with the specified name and AF_INET family. + bool found = false; + lifreq *ifrp = ifc.lifc_req; + for (int n = 0; n < (int) (ifc.lifc_len / sizeof (lifreq)); n++, ifrp++) { + if (!strcmp (nic_, ifrp->lifr_name)) { + rc = ioctl (fd, SIOCGLIFADDR, (char *) ifrp); + errno_assert (rc != -1); + if (ifrp->lifr_addr.ss_family == AF_INET) { + ip_addr_->ipv4 = *(sockaddr_in *) &ifrp->lifr_addr; + found = true; + break; + } + } + } + + // Clean-up. + free (ifr); + close (fd); + + if (!found) { + errno = ENODEV; + return -1; + } + return 0; +} + +#elif defined ZMQ_HAVE_AIX || defined ZMQ_HAVE_HPUX \ + || defined ZMQ_HAVE_ANDROID || defined ZMQ_HAVE_VXWORKS +#include +#ifdef ZMQ_HAVE_VXWORKS +#include +#endif + +int zmq::ip_resolver_t::resolve_nic_name (ip_addr_t *ip_addr_, + const char *nic_) +{ +#if defined ZMQ_HAVE_AIX || defined ZMQ_HAVE_HPUX + // IPv6 support not implemented for AIX or HP/UX. + if (ipv6_wanted) { + errno = ENODEV; + return -1; + } +#endif + + // Create a socket. + const int sd = open_socket (ipv6_wanted ? AF_INET6 : AF_INET, SOCK_DGRAM, 0); + errno_assert (sd != -1); + + struct ifreq ifr; + + // Copy interface name for ioctl get. + strncpy (ifr.ifr_name, nic_, sizeof (ifr.ifr_name)); + + // Fetch interface address. + const int rc = ioctl (sd, SIOCGIFADDR, (caddr_t) &ifr, sizeof (ifr)); + + // Clean up. + close (sd); + + if (rc == -1) { + errno = ENODEV; + return -1; + } + + const int family = ifr.ifr_addr.sa_family; + if (family == (ipv6_wanted ? AF_INET6 : AF_INET) + && !strcmp (nic_, ifr.ifr_name)) { + memcpy (ip_addr_, &ifr.ifr_addr, + (family == AF_INET) ? sizeof (struct sockaddr_in) + : sizeof (struct sockaddr_in6)); + } else { + errno = ENODEV; + return -1; + } + + return 0; +} + +#elif ((defined ZMQ_HAVE_LINUX || defined ZMQ_HAVE_FREEBSD \ + || defined ZMQ_HAVE_OSX || defined ZMQ_HAVE_OPENBSD \ + || defined ZMQ_HAVE_QNXNTO || defined ZMQ_HAVE_NETBSD \ + || defined ZMQ_HAVE_DRAGONFLY || defined ZMQ_HAVE_GNU) \ + && defined ZMQ_HAVE_IFADDRS) + +#include + +// On these platforms, network interface name can be queried +// using getifaddrs function. +int zmq::ip_resolver_t::resolve_nic_name (ip_addr_t *ip_addr_, + const char *nic_) +{ + // Get the addresses. + ifaddrs *ifa = NULL; + int rc = 0; + const int max_attempts = 10; + const int backoff_msec = 1; + for (int i = 0; i < max_attempts; i++) { + rc = getifaddrs (&ifa); + if (rc == 0 || (rc < 0 && errno != ECONNREFUSED)) + break; + usleep ((backoff_msec << i) * 1000); + } + + if (rc != 0 && ((errno == EINVAL) || (errno == EOPNOTSUPP))) { + // Windows Subsystem for Linux compatibility + errno = ENODEV; + return -1; + } + errno_assert (rc == 0); + zmq_assert (ifa != NULL); + + // Find the corresponding network interface. + bool found = false; + for (ifaddrs *ifp = ifa; ifp != NULL; ifp = ifp->ifa_next) { + if (ifp->ifa_addr == NULL) + continue; + + const int family = ifp->ifa_addr->sa_family; + if (family == (ipv6_wanted ? AF_INET6 : AF_INET) + && !strcmp (nic_, ifp->ifa_name)) { + memcpy (ip_addr_, ifp->ifa_addr, + (family == AF_INET) ? sizeof (struct sockaddr_in) + : sizeof (struct sockaddr_in6)); + found = true; + break; + } + } + + // Clean-up; + freeifaddrs (ifa); + + if (!found) { + errno = ENODEV; + return -1; + } + return 0; +} + +#elif (defined ZMQ_HAVE_WINDOWS) + +#include + +int zmq::ip_resolver_t::get_interface_name (unsigned long index, + char **dest) const +{ +#ifdef ZMQ_HAVE_WINDOWS_UWP + char *buffer = (char *) malloc (1024); +#else + char *buffer = (char *) malloc (IF_MAX_STRING_SIZE); +#endif + alloc_assert (buffer); + + char *if_name_result = NULL; + +#if !defined ZMQ_HAVE_WINDOWS_TARGET_XP && !defined ZMQ_HAVE_WINDOWS_UWP + if_name_result = if_indextoname (index, buffer); +#endif + + if (if_name_result == NULL) { + free (buffer); + return -1; + } + + *dest = buffer; + return 0; +} + +int zmq::ip_resolver_t::wchar_to_utf8 (const WCHAR *src, char **dest) const +{ + int rc; + int buffer_len = + WideCharToMultiByte (CP_UTF8, 0, src, -1, NULL, 0, NULL, 0); + + char *buffer = (char *) malloc (buffer_len); + alloc_assert (buffer); + + rc = WideCharToMultiByte (CP_UTF8, 0, src, -1, buffer, buffer_len, NULL, 0); + + if (rc == 0) { + free (buffer); + return -1; + } + + *dest = buffer; + return 0; +} + +int zmq::ip_resolver_t::resolve_nic_name (ip_addr_t *ip_addr_, + const char *nic_) +{ + int rc; + bool found = false; + const int max_attempts = 10; + + int iterations = 0; + IP_ADAPTER_ADDRESSES *addresses = NULL; + IP_ADAPTER_ADDRESSES *current_addresses = NULL; + unsigned long out_buf_len = sizeof (IP_ADAPTER_ADDRESSES); + + do { + addresses = (IP_ADAPTER_ADDRESSES *) malloc (out_buf_len); + alloc_assert (addresses); + + rc = + GetAdaptersAddresses (AF_UNSPEC, + GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST + | GAA_FLAG_SKIP_DNS_SERVER, + NULL, addresses, &out_buf_len); + if (rc == ERROR_BUFFER_OVERFLOW) { + free (addresses); + addresses = NULL; + } else { + break; + } + iterations++; + } while ((rc == ERROR_BUFFER_OVERFLOW) && (iterations < max_attempts)); + + if (rc == 0) { + current_addresses = addresses; + while (current_addresses) { + char *if_name = NULL; + char *if_friendly_name = NULL; + int str_rc1, str_rc2; + + str_rc1 = get_interface_name (current_addresses->IfIndex, &if_name); + str_rc2 = wchar_to_utf8 (current_addresses->FriendlyName, + &if_friendly_name); + + // Find a network adapter by its "name" or "friendly name" + if (((str_rc1 == 0) && (!strcmp (nic_, if_name))) + || ((str_rc2 == 0) && (!strcmp (nic_, if_friendly_name)))) { + // Iterate over all unicast addresses bound to the current network interface + IP_ADAPTER_UNICAST_ADDRESS *unicast_address = + current_addresses->FirstUnicastAddress; + IP_ADAPTER_UNICAST_ADDRESS *current_unicast_address = + unicast_address; + + while (current_unicast_address) { + ADDRESS_FAMILY family = + current_unicast_address->Address.lpSockaddr->sa_family; + + if (family == (ipv6_wanted ? AF_INET6 : AF_INET)) { + memcpy (ip_addr_, + current_unicast_address->Address.lpSockaddr, + (family == AF_INET) + ? sizeof (struct sockaddr_in) + : sizeof (struct sockaddr_in6)); + found = true; + break; + } + + current_unicast_address = current_unicast_address->Next; + } + + if (found) + break; + } + + if (str_rc1 == 0) + free (if_name); + if (str_rc2 == 0) + free (if_friendly_name); + + current_addresses = current_addresses->Next; + } + + free (addresses); + } + + if (!found) { + errno = ENODEV; + return -1; + } + return 0; +} + +#else + +// On other platforms we assume there are no sane interface names. +int zmq::ip_resolver_t::resolve_nic_name (ip_addr_t *ip_addr_, + const char *nic_) +{ + LIBZMQ_UNUSED (ip_addr_); + LIBZMQ_UNUSED (nic_); + + errno = ENODEV; + return -1; +} + +#endif diff --git a/src/ip_resolver.hpp b/src/ip_resolver.hpp new file mode 100644 index 0000000000..f8b094d0d2 --- /dev/null +++ b/src/ip_resolver.hpp @@ -0,0 +1,79 @@ +/* + Copyright (c) 2007-2018 Contributors as noted in the AUTHORS file + + This file is part of libzmq, the ZeroMQ core engine in C++. + + libzmq is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + As a special exception, the Contributors give you permission to link + this library with independent modules to produce an executable, + regardless of the license terms of these independent modules, and to + copy and distribute the resulting executable under terms of your choice, + provided that you also meet, for each linked independent module, the + terms and conditions of the license of that module. An independent + module is a module which is not derived from or based on this library. + If you modify this library, you must extend this exception to your + version of the library. + + libzmq is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef __ZMQ_IP_RESOLVER_HPP_INCLUDED__ +#define __ZMQ_IP_RESOLVER_HPP_INCLUDED__ + +#if !defined ZMQ_HAVE_WINDOWS +#include +#include +#endif + +namespace zmq +{ + +union ip_addr_t +{ + sockaddr generic; + sockaddr_in ipv4; + sockaddr_in6 ipv6; +}; + +class ip_resolver_t +{ + public: + ip_resolver_t (); + + ip_resolver_t &bindable (bool bindable_); + ip_resolver_t &allow_nic_name (bool allow_); + ip_resolver_t &ipv6 (bool ipv6_); + ip_resolver_t &expect_port (bool expect_); + ip_resolver_t &allow_dns (bool allow_); + + int resolve(ip_addr_t *ip_addr_, const char *name_); + + protected: + bool bindable_wanted; + bool nic_name_allowed; + bool ipv6_wanted; + bool port_expected; + bool dns_allowed; + + int resolve_nic_name (ip_addr_t *ip_addr_, const char *nic_); + int resolve_getaddrinfo (ip_addr_t *ip_addr_, const char *addr_); + +#if defined ZMQ_HAVE_WINDOWS + int get_interface_name (unsigned long index, char **dest) const; + int wchar_to_utf8 (const WCHAR *src, char **dest) const; +#endif +}; + +} + +#endif diff --git a/src/tcp_address.cpp b/src/tcp_address.cpp index 95eb889ce6..c235e78c22 100644 --- a/src/tcp_address.cpp +++ b/src/tcp_address.cpp @@ -48,547 +48,6 @@ #include #endif -#ifdef ZMQ_HAVE_SOLARIS -#include - -// On Solaris platform, network interface name can be queried by ioctl. -int zmq::tcp_address_t::resolve_nic_name (const char *nic_, - bool ipv6_, - bool is_src_) -{ - // TODO: Unused parameter, IPv6 support not implemented for Solaris. - LIBZMQ_UNUSED (ipv6_); - - // Create a socket. - const int fd = open_socket (AF_INET, SOCK_DGRAM, 0); - errno_assert (fd != -1); - - // Retrieve number of interfaces. - lifnum ifn; - ifn.lifn_family = AF_INET; - ifn.lifn_flags = 0; - int rc = ioctl (fd, SIOCGLIFNUM, (char *) &ifn); - errno_assert (rc != -1); - - // Allocate memory to get interface names. - const size_t ifr_size = sizeof (struct lifreq) * ifn.lifn_count; - char *ifr = (char *) malloc (ifr_size); - alloc_assert (ifr); - - // Retrieve interface names. - lifconf ifc; - ifc.lifc_family = AF_INET; - ifc.lifc_flags = 0; - ifc.lifc_len = ifr_size; - ifc.lifc_buf = ifr; - rc = ioctl (fd, SIOCGLIFCONF, (char *) &ifc); - errno_assert (rc != -1); - - // Find the interface with the specified name and AF_INET family. - bool found = false; - lifreq *ifrp = ifc.lifc_req; - for (int n = 0; n < (int) (ifc.lifc_len / sizeof (lifreq)); n++, ifrp++) { - if (!strcmp (nic_, ifrp->lifr_name)) { - rc = ioctl (fd, SIOCGLIFADDR, (char *) ifrp); - errno_assert (rc != -1); - if (ifrp->lifr_addr.ss_family == AF_INET) { - if (is_src_) - source_address.ipv4 = *(sockaddr_in *) &ifrp->lifr_addr; - else - address.ipv4 = *(sockaddr_in *) &ifrp->lifr_addr; - found = true; - break; - } - } - } - - // Clean-up. - free (ifr); - close (fd); - - if (!found) { - errno = ENODEV; - return -1; - } - return 0; -} - -#elif defined ZMQ_HAVE_AIX || defined ZMQ_HAVE_HPUX \ - || defined ZMQ_HAVE_ANDROID || defined ZMQ_HAVE_VXWORKS -#include -#ifdef ZMQ_HAVE_VXWORKS -#include -#endif - -int zmq::tcp_address_t::resolve_nic_name (const char *nic_, - bool ipv6_, - bool is_src_) -{ -#if defined ZMQ_HAVE_AIX || defined ZMQ_HAVE_HPUX - // IPv6 support not implemented for AIX or HP/UX. - if (ipv6_) { - errno = ENODEV; - return -1; - } -#endif - - // Create a socket. - const int sd = open_socket (ipv6_ ? AF_INET6 : AF_INET, SOCK_DGRAM, 0); - errno_assert (sd != -1); - - struct ifreq ifr; - - // Copy interface name for ioctl get. - strncpy (ifr.ifr_name, nic_, sizeof (ifr.ifr_name)); - - // Fetch interface address. - const int rc = ioctl (sd, SIOCGIFADDR, (caddr_t) &ifr, sizeof (ifr)); - - // Clean up. - close (sd); - - if (rc == -1) { - errno = ENODEV; - return -1; - } - - const int family = ifr.ifr_addr.sa_family; - if (family == (ipv6_ ? AF_INET6 : AF_INET) - && !strcmp (nic_, ifr.ifr_name)) { - if (is_src_) - memcpy (&source_address, &ifr.ifr_addr, - (family == AF_INET) ? sizeof (struct sockaddr_in) - : sizeof (struct sockaddr_in6)); - else - memcpy (&address, &ifr.ifr_addr, - (family == AF_INET) ? sizeof (struct sockaddr_in) - : sizeof (struct sockaddr_in6)); - } else { - errno = ENODEV; - return -1; - } - - return 0; -} - -#elif ((defined ZMQ_HAVE_LINUX || defined ZMQ_HAVE_FREEBSD \ - || defined ZMQ_HAVE_OSX || defined ZMQ_HAVE_OPENBSD \ - || defined ZMQ_HAVE_QNXNTO || defined ZMQ_HAVE_NETBSD \ - || defined ZMQ_HAVE_DRAGONFLY || defined ZMQ_HAVE_GNU) \ - && defined ZMQ_HAVE_IFADDRS) - -#include - -// On these platforms, network interface name can be queried -// using getifaddrs function. -int zmq::tcp_address_t::resolve_nic_name (const char *nic_, - bool ipv6_, - bool is_src_) -{ - // Get the addresses. - ifaddrs *ifa = NULL; - int rc = 0; - const int max_attempts = 10; - const int backoff_msec = 1; - for (int i = 0; i < max_attempts; i++) { - rc = getifaddrs (&ifa); - if (rc == 0 || (rc < 0 && errno != ECONNREFUSED)) - break; - usleep ((backoff_msec << i) * 1000); - } - - if (rc != 0 && ((errno == EINVAL) || (errno == EOPNOTSUPP))) { - // Windows Subsystem for Linux compatibility - LIBZMQ_UNUSED (nic_); - LIBZMQ_UNUSED (ipv6_); - - errno = ENODEV; - return -1; - } - errno_assert (rc == 0); - zmq_assert (ifa != NULL); - - // Find the corresponding network interface. - bool found = false; - for (ifaddrs *ifp = ifa; ifp != NULL; ifp = ifp->ifa_next) { - if (ifp->ifa_addr == NULL) - continue; - - const int family = ifp->ifa_addr->sa_family; - if (family == (ipv6_ ? AF_INET6 : AF_INET) - && !strcmp (nic_, ifp->ifa_name)) { - if (is_src_) - memcpy (&source_address, ifp->ifa_addr, - (family == AF_INET) ? sizeof (struct sockaddr_in) - : sizeof (struct sockaddr_in6)); - else - memcpy (&address, ifp->ifa_addr, - (family == AF_INET) ? sizeof (struct sockaddr_in) - : sizeof (struct sockaddr_in6)); - found = true; - break; - } - } - - // Clean-up; - freeifaddrs (ifa); - - if (!found) { - errno = ENODEV; - return -1; - } - return 0; -} - -#elif (defined ZMQ_HAVE_WINDOWS) - -#include - -int zmq::tcp_address_t::get_interface_name (unsigned long index, - char **dest) const -{ -#ifdef ZMQ_HAVE_WINDOWS_UWP - char *buffer = (char *) malloc (1024); -#else - char *buffer = (char *) malloc (IF_MAX_STRING_SIZE); -#endif - alloc_assert (buffer); - - char *if_name_result = NULL; - -#if !defined ZMQ_HAVE_WINDOWS_TARGET_XP && !defined ZMQ_HAVE_WINDOWS_UWP - if_name_result = if_indextoname (index, buffer); -#endif - - if (if_name_result == NULL) { - free (buffer); - return -1; - } - - *dest = buffer; - return 0; -} - -int zmq::tcp_address_t::wchar_to_utf8 (const WCHAR *src, char **dest) const -{ - int rc; - int buffer_len = - WideCharToMultiByte (CP_UTF8, 0, src, -1, NULL, 0, NULL, 0); - - char *buffer = (char *) malloc (buffer_len); - alloc_assert (buffer); - - rc = WideCharToMultiByte (CP_UTF8, 0, src, -1, buffer, buffer_len, NULL, 0); - - if (rc == 0) { - free (buffer); - return -1; - } - - *dest = buffer; - return 0; -} - -int zmq::tcp_address_t::resolve_nic_name (const char *nic_, - bool ipv6_, - bool is_src_) -{ - int rc; - bool found = false; - const int max_attempts = 10; - - int iterations = 0; - IP_ADAPTER_ADDRESSES *addresses = NULL; - IP_ADAPTER_ADDRESSES *current_addresses = NULL; - unsigned long out_buf_len = sizeof (IP_ADAPTER_ADDRESSES); - - do { - addresses = (IP_ADAPTER_ADDRESSES *) malloc (out_buf_len); - alloc_assert (addresses); - - rc = - GetAdaptersAddresses (AF_UNSPEC, - GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST - | GAA_FLAG_SKIP_DNS_SERVER, - NULL, addresses, &out_buf_len); - if (rc == ERROR_BUFFER_OVERFLOW) { - free (addresses); - addresses = NULL; - } else { - break; - } - iterations++; - } while ((rc == ERROR_BUFFER_OVERFLOW) && (iterations < max_attempts)); - - if (rc == 0) { - current_addresses = addresses; - while (current_addresses) { - char *if_name = NULL; - char *if_friendly_name = NULL; - int str_rc1, str_rc2; - - str_rc1 = get_interface_name (current_addresses->IfIndex, &if_name); - str_rc2 = wchar_to_utf8 (current_addresses->FriendlyName, - &if_friendly_name); - - // Find a network adapter by its "name" or "friendly name" - if (((str_rc1 == 0) && (!strcmp (nic_, if_name))) - || ((str_rc2 == 0) && (!strcmp (nic_, if_friendly_name)))) { - // Iterate over all unicast addresses bound to the current network interface - IP_ADAPTER_UNICAST_ADDRESS *unicast_address = - current_addresses->FirstUnicastAddress; - IP_ADAPTER_UNICAST_ADDRESS *current_unicast_address = - unicast_address; - - while (current_unicast_address) { - ADDRESS_FAMILY family = - current_unicast_address->Address.lpSockaddr->sa_family; - - if (family == (ipv6_ ? AF_INET6 : AF_INET)) { - if (is_src_) - memcpy (&source_address, - current_unicast_address->Address.lpSockaddr, - (family == AF_INET) - ? sizeof (struct sockaddr_in) - : sizeof (struct sockaddr_in6)); - else - memcpy (&address, - current_unicast_address->Address.lpSockaddr, - (family == AF_INET) - ? sizeof (struct sockaddr_in) - : sizeof (struct sockaddr_in6)); - found = true; - break; - } - - current_unicast_address = current_unicast_address->Next; - } - - if (found) - break; - } - - if (str_rc1 == 0) - free (if_name); - if (str_rc2 == 0) - free (if_friendly_name); - - current_addresses = current_addresses->Next; - } - - free (addresses); - } - - if (!found) { - errno = ENODEV; - return -1; - } - return 0; -} - -#else - -// On other platforms we assume there are no sane interface names. -int zmq::tcp_address_t::resolve_nic_name (const char *nic_, - bool ipv6_, - bool is_src_) -{ - LIBZMQ_UNUSED (nic_); - LIBZMQ_UNUSED (ipv6_); - - errno = ENODEV; - return -1; -} - -#endif - -int zmq::tcp_address_t::resolve_interface (const char *interface_, - bool ipv6_, - bool is_src_) -{ - // Initialize temporary output pointers with storage address. - sockaddr_storage ss; - sockaddr *out_addr = (sockaddr *) &ss; - size_t out_addrlen; - - // Initialise IP-format family/port and populate temporary output pointers - // with the address. - if (ipv6_) { - sockaddr_in6 ip6_addr; - memset (&ip6_addr, 0, sizeof (ip6_addr)); - ip6_addr.sin6_family = AF_INET6; -#ifdef ZMQ_HAVE_VXWORKS - struct in6_addr newaddr = IN6ADDR_ANY_INIT; - memcpy (&ip6_addr.sin6_addr, &newaddr, sizeof (in6_addr)); -#else - memcpy (&ip6_addr.sin6_addr, &in6addr_any, sizeof (in6addr_any)); -#endif - out_addrlen = sizeof (ip6_addr); - memcpy (out_addr, &ip6_addr, out_addrlen); - } else { - sockaddr_in ip4_addr; - memset (&ip4_addr, 0, sizeof (ip4_addr)); - ip4_addr.sin_family = AF_INET; - ip4_addr.sin_addr.s_addr = htonl (INADDR_ANY); - out_addrlen = sizeof (ip4_addr); - memcpy (out_addr, &ip4_addr, out_addrlen); - } - // "*" resolves to INADDR_ANY or in6addr_any. - if (strcmp (interface_, "*") == 0) { - zmq_assert (out_addrlen <= sizeof (address)); - if (is_src_) - memcpy (&source_address, out_addr, out_addrlen); - else - memcpy (&address, out_addr, out_addrlen); - return 0; - } - - // Try to resolve the string as a NIC name. - int rc = resolve_nic_name (interface_, ipv6_, is_src_); - if (rc == 0 || errno != ENODEV) - return rc; - - // There's no such interface name. Assume literal address. -#if defined ZMQ_HAVE_OPENVMS && defined __ia64 - __addrinfo64 *res = NULL; - __addrinfo64 req; -#else - addrinfo *res = NULL; - addrinfo req; -#endif - memset (&req, 0, sizeof (req)); - - // Choose IPv4 or IPv6 protocol family. Note that IPv6 allows for - // IPv4-in-IPv6 addresses. - req.ai_family = ipv6_ ? AF_INET6 : AF_INET; - - // Arbitrary, not used in the output, but avoids duplicate results. - req.ai_socktype = SOCK_STREAM; - - // Restrict hostname/service to literals to avoid any DNS lookups or - // service-name irregularity due to indeterminate socktype. - req.ai_flags = AI_PASSIVE | AI_NUMERICHOST; - -#if defined AI_V4MAPPED - // In this API we only require IPv4-mapped addresses when - // no native IPv6 interfaces are available (~AI_ALL). - // This saves an additional DNS roundtrip for IPv4 addresses. - if (req.ai_family == AF_INET6) - req.ai_flags |= AI_V4MAPPED; -#endif - - // Resolve the literal address. Some of the error info is lost in case - // of error, however, there's no way to report EAI errors via errno. - - rc = getaddrinfo (interface_, NULL, &req, &res); - -#if defined AI_V4MAPPED - // Some OS do have AI_V4MAPPED defined but it is not supported in getaddrinfo() - // returning EAI_BADFLAGS. Detect this and retry - if (rc == EAI_BADFLAGS && (req.ai_flags & AI_V4MAPPED)) { - req.ai_flags &= ~AI_V4MAPPED; - rc = getaddrinfo (interface_, NULL, &req, &res); - } -#endif - -#if defined ZMQ_HAVE_WINDOWS - // Resolve specific case on Windows platform when using IPv4 address - // with ZMQ_IPv6 socket option. - if ((req.ai_family == AF_INET6) && (rc == WSAHOST_NOT_FOUND)) { - req.ai_family = AF_INET; - rc = getaddrinfo (interface_, NULL, &req, &res); - } -#endif - - if (rc) { - errno = ENODEV; - return -1; - } - - // Use the first result. - zmq_assert (res != NULL); - zmq_assert ((size_t) res->ai_addrlen <= sizeof (address)); - if (is_src_) - memcpy (&source_address, res->ai_addr, res->ai_addrlen); - else - memcpy (&address, res->ai_addr, res->ai_addrlen); - - // Cleanup getaddrinfo after copying the possibly referenced result. - freeaddrinfo (res); - - return 0; -} - -int zmq::tcp_address_t::resolve_hostname (const char *hostname_, - bool ipv6_, - bool is_src_) -{ -// Set up the query. -#if defined ZMQ_HAVE_OPENVMS && defined __ia64 && __INITIAL_POINTER_SIZE == 64 - __addrinfo64 req; -#else - addrinfo req; -#endif - memset (&req, 0, sizeof (req)); - - // Choose IPv4 or IPv6 protocol family. Note that IPv6 allows for - // IPv4-in-IPv6 addresses. - req.ai_family = ipv6_ ? AF_INET6 : AF_INET; - - // Need to choose one to avoid duplicate results from getaddrinfo() - this - // doesn't really matter, since it's not included in the addr-output. - req.ai_socktype = SOCK_STREAM; - -#if defined AI_V4MAPPED - // In this API we only require IPv4-mapped addresses when - // no native IPv6 interfaces are available. - // This saves an additional DNS roundtrip for IPv4 addresses. - if (req.ai_family == AF_INET6) - req.ai_flags |= AI_V4MAPPED; -#endif - - // Resolve host name. Some of the error info is lost in case of error, - // however, there's no way to report EAI errors via errno. -#if defined ZMQ_HAVE_OPENVMS && defined __ia64 && __INITIAL_POINTER_SIZE == 64 - __addrinfo64 *res; -#else - addrinfo *res; -#endif - int rc = getaddrinfo (hostname_, NULL, &req, &res); - -#if defined AI_V4MAPPED - // Some OS do have AI_V4MAPPED defined but it is not supported in getaddrinfo() - // returning EAI_BADFLAGS. Detect this and retry - if (rc == EAI_BADFLAGS && (req.ai_flags & AI_V4MAPPED)) { - req.ai_flags &= ~AI_V4MAPPED; - rc = getaddrinfo (hostname_, NULL, &req, &res); - } -#endif - - if (rc) { - switch (rc) { - case EAI_MEMORY: - errno = ENOMEM; - break; - default: - errno = EINVAL; - break; - } - return -1; - } - - // Copy first result to output addr with hostname and service. - zmq_assert ((size_t) res->ai_addrlen <= sizeof (address)); - if (is_src_) - memcpy (&source_address, res->ai_addr, res->ai_addrlen); - else - memcpy (&address, res->ai_addr, res->ai_addrlen); - - freeaddrinfo (res); - - return 0; -} - zmq::tcp_address_t::tcp_address_t () : _has_src_addr (false) { memset (&address, 0, sizeof (address)); @@ -615,104 +74,43 @@ zmq::tcp_address_t::~tcp_address_t () int zmq::tcp_address_t::resolve (const char *name_, bool local_, - bool ipv6_, - bool is_src_) + bool ipv6_) { - if (!is_src_) { - // Test the ';' to know if we have a source address in name_ - const char *src_delimiter = strrchr (name_, ';'); - if (src_delimiter) { - std::string src_name (name_, src_delimiter - name_); - const int rc = resolve (src_name.c_str (), local_, ipv6_, true); - if (rc != 0) - return -1; - name_ = src_delimiter + 1; - _has_src_addr = true; - } - } - - // Find the ':' at end that separates address from the port number. - const char *delimiter = strrchr (name_, ':'); - if (!delimiter) { - errno = EINVAL; - return -1; - } - - // Separate the address/port. - std::string addr_str (name_, delimiter - name_); - std::string port_str (delimiter + 1); - - // Remove square brackets around the address, if any, as used in IPv6 - if (addr_str.size () >= 2 && addr_str[0] == '[' - && addr_str[addr_str.size () - 1] == ']') - addr_str = addr_str.substr (1, addr_str.size () - 2); - - // Test the '%' to know if we have an interface name / zone_id in the address - // Reference: https://tools.ietf.org/html/rfc4007 - std::size_t pos = addr_str.rfind ('%'); - uint32_t zone_id = 0; - if (pos != std::string::npos) { - std::string if_str = addr_str.substr (pos + 1); - addr_str = addr_str.substr (0, pos); - if (isalpha (if_str.at (0))) -#if !defined ZMQ_HAVE_WINDOWS_TARGET_XP && !defined ZMQ_HAVE_WINDOWS_UWP \ - && !defined ZMQ_HAVE_VXWORKS - zone_id = if_nametoindex (if_str.c_str ()); -#else - // The function 'if_nametoindex' is not supported on Windows XP. - // If we are targeting XP using a vxxx_xp toolset then fail. - // This is brutal as this code could be run on later windows clients - // meaning the IPv6 zone_id cannot have an interface name. - // This could be fixed with a runtime check. - zone_id = 0; -#endif - else - zone_id = (uint32_t) atoi (if_str.c_str ()); - if (zone_id == 0) { - errno = EINVAL; - return -1; - } - } - - // Allow 0 specifically, to detect invalid port error in atoi if not - uint16_t port; - if (port_str == "*" || port_str == "0") - // Resolve wildcard to 0 to allow autoselection of port - port = 0; - else { - // Parse the port number (0 is not a valid port). - port = (uint16_t) atoi (port_str.c_str ()); - if (port == 0) { - errno = EINVAL; + // Test the ';' to know if we have a source address in name_ + const char *src_delimiter = strrchr (name_, ';'); + if (src_delimiter) { + std::string src_name (name_, src_delimiter - name_); + + ip_resolver_t src_resolver; + + src_resolver + .bindable (true) + // Restrict hostname/service to literals to avoid any DNS + // lookups or service-name irregularity due to + // indeterminate socktype. + .allow_dns (false) + .allow_nic_name (true) + .ipv6 (ipv6_) + .expect_port (true); + + const int rc = src_resolver.resolve (&source_address, + src_name.c_str ()); + if (rc != 0) return -1; - } + name_ = src_delimiter + 1; + _has_src_addr = true; } - // Resolve the IP address. - int rc; - if (local_ || is_src_) - rc = resolve_interface (addr_str.c_str (), ipv6_, is_src_); - else - rc = resolve_hostname (addr_str.c_str (), ipv6_, is_src_); - if (rc != 0) - return -1; + ip_resolver_t resolver; - // Set the port into the address structure. - if (is_src_) { - if (source_address.generic.sa_family == AF_INET6) { - source_address.ipv6.sin6_port = htons (port); - source_address.ipv6.sin6_scope_id = zone_id; - } else - source_address.ipv4.sin_port = htons (port); - } else { - if (address.generic.sa_family == AF_INET6) { - address.ipv6.sin6_port = htons (port); - address.ipv6.sin6_scope_id = zone_id; - } else - address.ipv4.sin_port = htons (port); - } + resolver + .bindable (local_) + .allow_dns (!local_) + .allow_nic_name (local_) + .ipv6 (ipv6_) + .expect_port (true); - return 0; + return resolver.resolve (&address, name_); } int zmq::tcp_address_t::to_string (std::string &addr_) @@ -813,7 +211,16 @@ int zmq::tcp_address_mask_t::resolve (const char *name_, bool ipv6_) addr_str.assign (name_); // Parse address part using standard routines. - const int rc = tcp_address_t::resolve_hostname (addr_str.c_str (), ipv6_); + ip_resolver_t resolver; + + resolver + .bindable (false) + .allow_dns (false) + .allow_nic_name (false) + .ipv6 (ipv6_) + .expect_port (false); + + const int rc = resolver.resolve(&address, addr_str.c_str ()); if (rc != 0) return rc; diff --git a/src/tcp_address.hpp b/src/tcp_address.hpp index 8c20a4ceb0..1e11492cb6 100644 --- a/src/tcp_address.hpp +++ b/src/tcp_address.hpp @@ -35,6 +35,8 @@ #include #endif +#include "ip_resolver.hpp" + namespace zmq { class tcp_address_t @@ -49,7 +51,7 @@ class tcp_address_t // names. If it is false, names are resolved as remote hostnames. // If 'ipv6' is true, the name may resolve to IPv6 address. int - resolve (const char *name_, bool local_, bool ipv6_, bool is_src_ = false); + resolve (const char *name_, bool local_, bool ipv6_); // The opposite to resolve() virtual int to_string (std::string &addr_); @@ -67,31 +69,9 @@ class tcp_address_t bool has_src_addr () const; protected: - int resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_ = false); - int resolve_interface (const char *interface_, - bool ipv6_, - bool is_src_ = false); - int - resolve_hostname (const char *hostname_, bool ipv6_, bool is_src_ = false); - -#if defined ZMQ_HAVE_WINDOWS - int get_interface_name (unsigned long index, char **dest) const; - int wchar_to_utf8 (const WCHAR *src, char **dest) const; -#endif - union - { - sockaddr generic; - sockaddr_in ipv4; - sockaddr_in6 ipv6; - } address; - - union - { - sockaddr generic; - sockaddr_in ipv4; - sockaddr_in6 ipv6; - } source_address; + ip_addr_t address; + ip_addr_t source_address; bool _has_src_addr; }; diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 04b4cdf33f..ad9ed1f1e1 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -5,6 +5,8 @@ set(unittests unittest_ypipe unittest_poller unittest_mtrie + unittest_ip_resolver + unittest_ip_resolver_dns ) #IF (ENABLE_DRAFTS) diff --git a/unittests/unittest_ip_resolver.cpp b/unittests/unittest_ip_resolver.cpp new file mode 100644 index 0000000000..1543710222 --- /dev/null +++ b/unittests/unittest_ip_resolver.cpp @@ -0,0 +1,383 @@ +/* +Copyright (c) 2018 Contributors as noted in the AUTHORS file + +This file is part of 0MQ. + +0MQ is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +0MQ is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ + +#include +#include "../tests/testutil.hpp" + +#include + +#if !defined ZMQ_HAVE_WINDOWS_TARGET_XP && !defined ZMQ_HAVE_WINDOWS_UWP \ + && !defined ZMQ_HAVE_VXWORKS +#define HAVE_IFNAMETOINDEX +#else +#undef HAVE_IFNAMETOINDEX +#endif + +#ifdef HAVE_IFNAMETOINDEX +#include + +extern "C" { + // Dummy if_nametoindex implementation to make test hardware-independant + unsigned int if_nametoindex (const char *ifname_) + { + static const char * dummy_interfaces[] = { + "lo0", + "eth0", + "eth1", + }; + unsigned lut_len = + sizeof (dummy_interfaces) / sizeof (dummy_interfaces[0]); + + for (unsigned i = 0; i < lut_len; i++) { + if (strcmp (dummy_interfaces[i], ifname_) == 0) { + // The dummy index will be the position in the array + 1 (0 is + // invalid) + return i + 1; + } + } + + // Not found + return 0; + } +} + +#endif // HAVE_IFNAMETOINDEX + +// Generate an invalid but well-defined 'ip_addr_t'. Avoids testing +// uninitialized values if the code is buggy. +static zmq::ip_addr_t test_bad_addr (void) +{ + zmq::ip_addr_t addr; + + memset (&addr, 0xba, sizeof (addr)); + + return addr; +} + +static void validade_ipv6_addr (const zmq::ip_addr_t &addr_, + const struct in6_addr &expected_addr_, + uint16_t expected_port_ = 0, + uint16_t expected_zone_ = 0) +{ + const sockaddr_in6 *ip6_addr = &addr_.ipv6; + + TEST_ASSERT_EQUAL (AF_INET6, addr_.generic.sa_family); + + int neq = memcmp (&ip6_addr->sin6_addr, + &expected_addr_, + sizeof (expected_addr_)); + + TEST_ASSERT_EQUAL (0, neq); + TEST_ASSERT_EQUAL (htons (expected_port_), ip6_addr->sin6_port); + TEST_ASSERT_EQUAL (expected_zone_, ip6_addr->sin6_scope_id); +} + +static void validade_ipv4_addr (const zmq::ip_addr_t &addr_, + const struct in_addr &expected_addr_, + uint16_t expected_port_ = 0) +{ + const sockaddr_in *ip4_addr = &addr_.ipv4; + + TEST_ASSERT_EQUAL (AF_INET, addr_.generic.sa_family); + TEST_ASSERT_EQUAL (expected_addr_.s_addr, ip4_addr->sin_addr.s_addr); + TEST_ASSERT_EQUAL (htons (expected_port_), ip4_addr->sin_port); +} + +void test_bind_any (int ipv6_) +{ + zmq::ip_resolver_t resolver; + zmq::ip_addr_t addr = test_bad_addr (); + int rc; + + resolver + .bindable (true) + .allow_dns (false) + .allow_nic_name (false) + .expect_port (true) + .ipv6 (ipv6_); + + rc = resolver.resolve(&addr, "*:*"); + + TEST_ASSERT_EQUAL (0, rc); + + if (ipv6_) { + struct in6_addr anyaddr; + +#ifdef ZMQ_HAVE_VXWORKS + anyaddr = IN6ADDR_ANY_INIT; +#else + anyaddr = in6addr_any; +#endif + validade_ipv6_addr (addr, anyaddr, 0); + } else { + struct in_addr anyaddr; + + anyaddr.s_addr = htonl (INADDR_ANY); + + validade_ipv4_addr (addr, anyaddr, 0); + } + + // Wildcard should be rejected if we're not looking for a + // bindable address + resolver.bindable (false); + + TEST_ASSERT_EQUAL(-1, resolver.resolve(&addr, "*:*")); + TEST_ASSERT_EQUAL(-1, resolver.resolve(&addr, "*:1234")); + + // This however works. Should it ? For the time being I'm going to + // keep it that way for backcompat but I can't imagine why you'd + // want a wildcard port if you're not binding. + TEST_ASSERT_EQUAL(0, resolver.resolve(&addr, "127.0.0.1:*")); +} + +void test_bind_any_ipv4 () { + test_bind_any (false); +} + +void test_bind_any_ipv6 () { + test_bind_any (true); +} + +void test_parse_ipv4 () { + zmq::ip_resolver_t resolver; + zmq::ip_addr_t addr = test_bad_addr (); + struct in_addr expected_addr; + + resolver + .bindable (false) + .allow_dns (false) + .allow_nic_name (false) + .expect_port (false) + .ipv6 (false); + + assert (inet_pton (AF_INET, "1.2.128.129", &expected_addr) == 1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "1.2.128.129")); + validade_ipv4_addr (addr, expected_addr); + + // Not particularly useful, but valid + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[1.2.128.129]")); + validade_ipv4_addr (addr, expected_addr); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[1.2.128].129")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[1.2.128.]129")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[1.2.128.129")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "1.2.128.129]")); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "1.2.128.129:123")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "1.2.128.129:*")); + + resolver.expect_port (true); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "1.2.128.129:123")); + validade_ipv4_addr (addr, expected_addr, 123); + + // The code doesn't validate that the port doesn't contain garbage, should + // it? + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "1.2.128.129:123bad")); + validade_ipv4_addr (addr, expected_addr, 123); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "1.2.128.129:bad")); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[1.2.128.129]:123")); + validade_ipv4_addr (addr, expected_addr, 123); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "1.2.128.129:*")); + validade_ipv4_addr (addr, expected_addr, 0); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[1.2.128.129:]123")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[1.2.128.]129")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[1.2.128].129")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[1.2.128]129")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[1.2.128.129:123]")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[1.2.128.129:*]")); + + // IPv6 should be rejected + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "::1")); + + resolver.expect_port (true); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[::1]:1234")); +} + +void test_parse_ipv6 () { + zmq::ip_resolver_t resolver; + zmq::ip_addr_t addr = test_bad_addr (); + struct in6_addr expected_addr; + + resolver + .bindable (false) + .allow_dns (false) + .allow_nic_name (false) + .expect_port (false) + .ipv6 (true); + + assert (inet_pton (AF_INET6, "::1", &expected_addr) == 1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "::1")); + validade_ipv6_addr (addr, expected_addr); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[::1]")); + validade_ipv6_addr (addr, expected_addr); + + assert (inet_pton (AF_INET6, "abcd:1234::1:0:234", &expected_addr) == 1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "abcd:1234::1:0:234")); + validade_ipv6_addr (addr, expected_addr); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[abcd:1234::1:0:234]")); + validade_ipv6_addr (addr, expected_addr); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[abcd:1234::1]:0:234")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[abcd:1234::1:0]:234")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[abcd:1234::1:0:]234")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[abcd:1234::1:0:234")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "abcd:1234::1:0:234]")); + + resolver.expect_port (true); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[abcd:1234::1:0:234]:*")); + validade_ipv6_addr (addr, expected_addr, 0); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[abcd:1234::1:0:234]:5432")); + validade_ipv6_addr (addr, expected_addr, 5432); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[::1]:123:456")); + + // Should this be allowed? Seems error-prone but so far ZMQ accepts it. + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "abcd:1234::1:0:234:123")); + validade_ipv6_addr (addr, expected_addr, 123); + + resolver.expect_port (false); + +} + +// Parsing IPv4 should also work if an IPv6 is requested, it +// returns an IPv6 with the IPv4 address embedded +void test_parse_ipv4_in_ipv6 () +{ + zmq::ip_resolver_t resolver; + zmq::ip_addr_t addr = test_bad_addr (); + struct in6_addr expected_addr; + + resolver + .bindable (false) + .allow_dns (false) + .allow_nic_name (false) + .expect_port (false) + .ipv6 (true); + + assert (inet_pton (AF_INET6, "::ffff:1.2.128.129", &expected_addr) == 1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "1.2.128.129")); + validade_ipv6_addr (addr, expected_addr); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "::ffff:1.2.128.129")); + validade_ipv6_addr (addr, expected_addr); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[1.2.128.129]")); + validade_ipv6_addr (addr, expected_addr); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[::ffff:1.2.128.129]")); + validade_ipv6_addr (addr, expected_addr); + + resolver.expect_port (true); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "1.2.128.129:1234")); + validade_ipv6_addr (addr, expected_addr, 1234); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[1.2.128.129]:1234")); + validade_ipv6_addr (addr, expected_addr, 1234); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "1.2.128.129:*")); + validade_ipv6_addr (addr, expected_addr, 0); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[::ffff:1.2.128.129]:1234")); + validade_ipv6_addr (addr, expected_addr, 1234); +} + + +void test_parse_ipv6_scope () +{ + zmq::ip_resolver_t resolver; + zmq::ip_addr_t addr = test_bad_addr (); + struct in6_addr expected_addr; + + resolver + .bindable (false) + .allow_dns (false) + .allow_nic_name (false) + .expect_port (false) + .ipv6 (true); + + assert (inet_pton (AF_INET6, "3000:4:5::1:234", &expected_addr) == 1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "3000:4:5::1:234%1")); + validade_ipv6_addr (addr, expected_addr, 0, 1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "3000:4:5::1:234%2")); + validade_ipv6_addr (addr, expected_addr, 0, 2); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "3000:4:5::1:234%0")); + +#ifdef HAVE_IFNAMETOINDEX + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "3000:4:5::1:234%lo0")); + validade_ipv6_addr (addr, expected_addr, 0, 1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "3000:4:5::1:234%eth0")); + validade_ipv6_addr (addr, expected_addr, 0, 2); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "3000:4:5::1:234%bad0")); +#endif // HAVE_IFNAMETOINDEX + + resolver.expect_port (true); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "3000:4:5::1:234%1:123")); + validade_ipv6_addr (addr, expected_addr, 123, 1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[3000:4:5::1:234%2]:123")); + validade_ipv6_addr (addr, expected_addr, 123, 2); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[3000:4:5::1:234]%2:123")); + +#ifdef HAVE_IFNAMETOINDEX + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "3000:4:5::1:234%lo0:456")); + validade_ipv6_addr (addr, expected_addr, 456, 1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[3000:4:5::1:234%eth0]:22")); + validade_ipv6_addr (addr, expected_addr, 22, 2); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "[3000:4:5::1:234]%bad0:44")); +#endif // HAVE_IFNAMETOINDEX +} + +int main (void) +{ + setup_test_environment (); + + UNITY_BEGIN (); + + RUN_TEST (test_bind_any_ipv4); + RUN_TEST (test_bind_any_ipv6); + RUN_TEST (test_parse_ipv4); + RUN_TEST (test_parse_ipv6); + RUN_TEST (test_parse_ipv4_in_ipv6); + RUN_TEST (test_parse_ipv6_scope); + + return UNITY_END (); +} diff --git a/unittests/unittest_ip_resolver_dns.cpp b/unittests/unittest_ip_resolver_dns.cpp new file mode 100644 index 0000000000..a8ee5ea177 --- /dev/null +++ b/unittests/unittest_ip_resolver_dns.cpp @@ -0,0 +1,316 @@ +/* +Copyright (c) 2018 Contributors as noted in the AUTHORS file + +This file is part of 0MQ. + +0MQ is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +0MQ is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ + +#include +#include "../tests/testutil.hpp" + +#include +#include +#include +#include + +extern "C" { + struct dns_lut_t + { + const char *hostname; + const char *ipv4; + const char *ipv6; + }; + + static const struct dns_lut_t dns_lut[] = { + { "ip.zeromq.org", "10.100.0.1", "fdf5:d058:d656::1" }, + { "ipv4only.zeromq.org", "10.100.0.2", "::ffff:10.100.0.2" }, + { "ipv6only.zeromq.org", NULL, "fdf5:d058:d656::2" }, + }; + + // Dummy getaddrinfo implementation to avoid making DNS queries in tests + int getaddrinfo (const char *node_, const char *service_, + const struct addrinfo *hints_, + struct addrinfo **res_) + { + struct addrinfo ai; + + assert (service_ == NULL); + + if (hints_->ai_family != AF_INET && hints_->ai_family != AF_INET6) { + return EAI_ADDRFAMILY; + } + + bool ipv6 = (hints_->ai_family == AF_INET6); + + ai = *hints_; + + bool no_dns = hints_->ai_flags & AI_NUMERICHOST; + + unsigned lut_len = sizeof (dns_lut) / sizeof (dns_lut[0]); + + const char *ip = NULL; + + if (!no_dns) { + for (unsigned i = 0; i < lut_len; i++) { + if (strcmp (dns_lut[i].hostname, node_) == 0) { + if (ipv6) { + ip = dns_lut[i].ipv6; + } else { + ip = dns_lut[i].ipv4; + + if (ip == NULL) { + // No address associated with NAME + return EAI_NODATA; + } + } + } + } + } + + if (ip == NULL) { + // No entry for 'node_' found in the LUT, assume that it's a + // numeric IP address + ip = node_; + } + + zmq::ip_addr_t addr; + + addr.generic.sa_family = ai.ai_family; + + int rc = 0; + + if (ai.ai_family == AF_INET) { + ai.ai_addrlen = sizeof (struct sockaddr_in); + rc = inet_pton (AF_INET, ip, &addr.ipv4.sin_addr); + } else { + ai.ai_addrlen = sizeof (struct sockaddr_in6); + rc = inet_pton (AF_INET6, ip, &addr.ipv6.sin6_addr); + } + + if (rc == 0) { + // NAME or SERVICE is unknown + return EAI_NONAME; + } + + ai.ai_addr = (struct sockaddr *)calloc (1, ai.ai_addrlen); + if (ai.ai_addr == NULL) { + return EAI_MEMORY; + } + + memcpy (ai.ai_addr, &addr, ai.ai_addrlen); + + *res_ = (struct addrinfo *)calloc (1, sizeof (**res_)); + if (*res_ == NULL) { + free (ai.ai_addr); + return EAI_MEMORY; + } + + **res_ = ai; + + return 0; + } + + void freeaddrinfo (struct addrinfo *res_) + { + if (res_->ai_addr) { + free (res_->ai_addr); + res_->ai_addr = NULL; + } + + free (res_); + } +} + +// Generate an invalid but well-defined 'ip_addr_t'. Avoids testing +// uninitialized values if the code is buggy. +static zmq::ip_addr_t test_bad_addr (void) +{ + zmq::ip_addr_t addr; + + memset (&addr, 0xba, sizeof (addr)); + + return addr; +} + +static void validade_ipv4_addr (const zmq::ip_addr_t &addr_, + const struct in_addr &expected_addr_, + uint16_t expected_port_ = 0) +{ + const sockaddr_in *ip4_addr = &addr_.ipv4; + + TEST_ASSERT_EQUAL (AF_INET, addr_.generic.sa_family); + TEST_ASSERT_EQUAL (expected_addr_.s_addr, ip4_addr->sin_addr.s_addr); + TEST_ASSERT_EQUAL (htons (expected_port_), ip4_addr->sin_port); +} + +static void validade_ipv6_addr (const zmq::ip_addr_t &addr_, + const struct in6_addr &expected_addr_, + uint16_t expected_port_ = 0, + uint16_t expected_zone_ = 0) +{ + const sockaddr_in6 *ip6_addr = &addr_.ipv6; + + TEST_ASSERT_EQUAL (AF_INET6, addr_.generic.sa_family); + + int neq = memcmp (&ip6_addr->sin6_addr, + &expected_addr_, + sizeof (expected_addr_)); + + TEST_ASSERT_EQUAL (0, neq); + TEST_ASSERT_EQUAL (htons (expected_port_), ip6_addr->sin6_port); + TEST_ASSERT_EQUAL (expected_zone_, ip6_addr->sin6_scope_id); +} + +void test_dns_ipv4 () +{ + zmq::ip_resolver_t resolver; + zmq::ip_addr_t addr = test_bad_addr (); + struct in_addr expected_addr1, expected_addr2; + + resolver + .bindable (false) + .allow_dns (true) + .allow_nic_name (false) + .expect_port (false) + .ipv6 (false); + + assert (inet_pton (AF_INET, "10.100.0.1", &expected_addr1) == 1); + assert (inet_pton (AF_INET, "10.100.0.2", &expected_addr2) == 1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "ip.zeromq.org")); + validade_ipv4_addr (addr, expected_addr1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "ipv4only.zeromq.org")); + validade_ipv4_addr (addr, expected_addr2); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[ip.zeromq.org]")); + validade_ipv4_addr (addr, expected_addr1); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "ipv6only.zeromq.org")); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "invalid.zeromq.org")); + + resolver.expect_port (true); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "ip.zeromq.org:1234")); + validade_ipv4_addr (addr, expected_addr1, 1234); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "ipv4only.zeromq.org:1234")); + validade_ipv4_addr (addr, expected_addr2, 1234); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[ip.zeromq.org]:1234")); + validade_ipv4_addr (addr, expected_addr1, 1234); + + // Numeric IPs should still work + resolver.expect_port (false); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "10.100.0.1")); + validade_ipv4_addr (addr, expected_addr1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "10.100.0.2")); + validade_ipv4_addr (addr, expected_addr2); + + // DNS resolution shouldn't work when disallowed + resolver.allow_dns (false); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "ip.zeromq.org")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "ipv4only.zeromq.org")); +} + +void test_dns_ipv6 () +{ + zmq::ip_resolver_t resolver; + zmq::ip_addr_t addr = test_bad_addr (); + struct in6_addr expected_addr1, expected_addr2, expected_addr_v4; + + resolver + .bindable (false) + .allow_dns (true) + .allow_nic_name (false) + .expect_port (false) + .ipv6 (true); + + assert (inet_pton (AF_INET6, "fdf5:d058:d656::1", &expected_addr1) == 1); + assert (inet_pton (AF_INET6, "fdf5:d058:d656::2", &expected_addr2) == 1); + assert (inet_pton (AF_INET6, "::ffff:10.100.0.2", &expected_addr_v4) == 1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "ip.zeromq.org")); + validade_ipv6_addr (addr, expected_addr1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "ipv6only.zeromq.org")); + validade_ipv6_addr (addr, expected_addr2); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[ip.zeromq.org]")); + validade_ipv6_addr (addr, expected_addr1); + + // Not sure if that's very useful but you could technically add a scope + // identifier to a hostname + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "ip.zeromq.org%4")); + validade_ipv6_addr (addr, expected_addr1, 0, 4); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[ip.zeromq.org%5]")); + validade_ipv6_addr (addr, expected_addr1, 0, 5); + + // If a host doesn't have an IPv4 then it should resolve as an embedded v4 + // address in an IPv6 + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "ipv4only.zeromq.org")); + validade_ipv6_addr (addr, expected_addr_v4); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "invalid.zeromq.org")); + + resolver.expect_port (true); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "ip.zeromq.org:1234")); + validade_ipv6_addr (addr, expected_addr1, 1234); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "ipv6only.zeromq.org:1234")); + validade_ipv6_addr (addr, expected_addr2, 1234); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[ip.zeromq.org]:1234")); + validade_ipv6_addr (addr, expected_addr1, 1234); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "ip.zeromq.org%8:1234")); + validade_ipv6_addr (addr, expected_addr1, 1234, 8); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "[ip.zeromq.org%9]:5678")); + validade_ipv6_addr (addr, expected_addr1, 5678, 9); + + // Numeric IPs should still work + resolver.expect_port (false); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "fdf5:d058:d656::1")); + validade_ipv6_addr (addr, expected_addr1); + + TEST_ASSERT_EQUAL (0, resolver.resolve(&addr, "fdf5:d058:d656::2")); + validade_ipv6_addr (addr, expected_addr2); + + // DNS resolution shouldn't work when disallowed + resolver.allow_dns (false); + + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "ip.zeromq.org")); + TEST_ASSERT_EQUAL (-1, resolver.resolve(&addr, "ipv6only.zeromq.org")); +} + +int main (void) +{ + setup_test_environment (); + + UNITY_BEGIN (); + + RUN_TEST (test_dns_ipv4); + RUN_TEST (test_dns_ipv6); + + return UNITY_END (); +}