From 4cd2c2ebf877091cdb22134cfbb4dae201a58ef1 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 | 13 +- src/ip_resolver.cpp | 658 +++++++++++++++++++++ src/ip_resolver.hpp | 102 ++++ src/tcp_address.cpp | 671 ++-------------------- src/tcp_address.hpp | 32 +- tests/testutil.hpp | 30 + unittests/CMakeLists.txt | 1 + unittests/unittest_ip_resolver.cpp | 882 +++++++++++++++++++++++++++++ 9 files changed, 1731 insertions(+), 659 deletions(-) create mode 100644 src/ip_resolver.cpp create mode 100644 src/ip_resolver.hpp create mode 100644 unittests/unittest_ip_resolver.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a634b9ee..604cc76a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -603,6 +603,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 bcb60c24..a1679794 100644 --- a/Makefile.am +++ b/Makefile.am @@ -83,6 +83,8 @@ src_libzmq_la_SOURCES = \ src/io_thread.hpp \ src/ip.cpp \ src/ip.hpp \ + src/ip_resolver.cpp \ + src/ip_resolver.hpp \ src/ipc_address.cpp \ src/ipc_address.hpp \ src/ipc_connecter.cpp \ @@ -895,7 +897,8 @@ if ENABLE_STATIC test_apps += \ unittests/unittest_poller \ unittests/unittest_ypipe \ - unittests/unittest_mtrie + unittests/unittest_mtrie \ + unittests/unittest_ip_resolver unittests_unittest_poller_SOURCES = unittests/unittest_poller.cpp unittests_unittest_poller_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS} $(CODE_COVERAGE_CPPFLAGS) @@ -920,6 +923,14 @@ unittests_unittest_mtrie_LDADD = $(top_builddir)/src/.libs/libzmq.a \ ${src_libzmq_la_LIBADD} \ ${UNITY_LIBS} \ $(CODE_COVERAGE_LDFLAGS) + +unittests_unittest_ip_resolver_SOURCES = unittests/unittest_ip_resolver.cpp +unittests_unittest_ip_resolver_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS} $(CODE_COVERAGE_CPPFLAGS) +unittests_unittest_ip_resolver_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS) +unittests_unittest_ip_resolver_LDADD = $(top_builddir)/src/.libs/libzmq.a \ + ${src_libzmq_la_LIBADD} \ + ${UNITY_LIBS} \ + $(CODE_COVERAGE_LDFLAGS) endif check_PROGRAMS = ${test_apps} diff --git a/src/ip_resolver.cpp b/src/ip_resolver.cpp new file mode 100644 index 00000000..cd377305 --- /dev/null +++ b/src/ip_resolver.cpp @@ -0,0 +1,658 @@ +#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_options_t::ip_resolver_options_t () : + bindable_wanted (false), + nic_name_allowed (false), + ipv6_wanted (false), + port_expected (false), + dns_allowed (false) +{ +} + +zmq::ip_resolver_options_t & +zmq::ip_resolver_options_t::bindable (bool bindable_) +{ + bindable_wanted = bindable_; + + return *this; +} + +zmq::ip_resolver_options_t & +zmq::ip_resolver_options_t::allow_nic_name (bool allow_) +{ + nic_name_allowed = allow_; + + return *this; +} + +zmq::ip_resolver_options_t &zmq::ip_resolver_options_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_options_t & +zmq::ip_resolver_options_t::expect_port (bool expect_) +{ + port_expected = expect_; + + return *this; +} + +zmq::ip_resolver_options_t &zmq::ip_resolver_options_t::allow_dns (bool allow_) +{ + dns_allowed = allow_; + + return *this; +} + +bool zmq::ip_resolver_options_t::bindable () +{ + return bindable_wanted; +} + +bool zmq::ip_resolver_options_t::allow_nic_name () +{ + return nic_name_allowed; +} + +bool zmq::ip_resolver_options_t::ipv6 () +{ + return ipv6_wanted; +} + +bool zmq::ip_resolver_options_t::expect_port () +{ + return port_expected; +} + +bool zmq::ip_resolver_options_t::allow_dns () +{ + return dns_allowed; +} + +zmq::ip_resolver_t::ip_resolver_t (ip_resolver_options_t opts_) : + options (opts_) +{ +} + +int zmq::ip_resolver_t::resolve (ip_addr_t *ip_addr_, const char *name_) +{ + std::string addr; + uint16_t port; + + if (options.expect_port ()) { + // 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 surrounding 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))) { + zone_id = do_if_nametoindex (if_str.c_str ()); + } 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 (options.bindable () && addr == "*") { + // Return an ANY address + resolved = true; + + if (options.ipv6 ()) { + 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 && options.allow_nic_name ()) { + // 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); + } + + assert (resolved == true); + 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 = options.ipv6 () ? AF_INET6 : AF_INET; + + // Arbitrary, not used in the output, but avoids duplicate results. + req.ai_socktype = SOCK_STREAM; + + req.ai_flags = 0; + + if (options.bindable ()) { + req.ai_flags |= AI_PASSIVE; + } + + if (!options.allow_dns ()) { + 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 = do_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 = do_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 = do_getaddrinfo (addr_, NULL, &req, &res); + } +#endif + + if (rc) { + switch (rc) { + case EAI_MEMORY: + errno = ENOMEM; + break; + default: + if (options.bindable ()) { + 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. + do_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 (options.ipv6 ()) { + errno = ENODEV; + return -1; + } +#endif + + // Create a socket. + const int sd = + open_socket (options.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 == (options.ipv6 () ? 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 == (options.ipv6 () ? 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 == (options.ipv6 () ? 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 + +int zmq::ip_resolver_t::do_getaddrinfo (const char *node_, + const char *service_, + const struct addrinfo *hints_, + struct addrinfo **res_) +{ + return getaddrinfo (node_, service_, hints_, res_); +} + +void zmq::ip_resolver_t::do_freeaddrinfo (struct addrinfo *res_) +{ + freeaddrinfo (res_); +} + +unsigned int zmq::ip_resolver_t::do_if_nametoindex (const char *ifname_) +{ +#if !defined ZMQ_HAVE_WINDOWS_TARGET_XP && !defined ZMQ_HAVE_WINDOWS_UWP \ + && !defined ZMQ_HAVE_VXWORKS + return if_nametoindex (ifname_); +#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. + return 0; +#endif +} diff --git a/src/ip_resolver.hpp b/src/ip_resolver.hpp new file mode 100644 index 00000000..24658ec2 --- /dev/null +++ b/src/ip_resolver.hpp @@ -0,0 +1,102 @@ +/* + 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_options_t +{ + public: + ip_resolver_options_t (); + + ip_resolver_options_t &bindable (bool bindable_); + ip_resolver_options_t &allow_nic_name (bool allow_); + ip_resolver_options_t &ipv6 (bool ipv6_); + ip_resolver_options_t &expect_port (bool expect_); + ip_resolver_options_t &allow_dns (bool allow_); + + bool bindable (); + bool allow_nic_name (); + bool ipv6 (); + bool expect_port (); + bool allow_dns (); + + private: + bool bindable_wanted; + bool nic_name_allowed; + bool ipv6_wanted; + bool port_expected; + bool dns_allowed; +}; + +class ip_resolver_t +{ + public: + ip_resolver_t (ip_resolver_options_t opts_); + + int resolve (ip_addr_t *ip_addr_, const char *name_); + + protected: + ip_resolver_options_t options; + + 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 + + // Virtual functions that are overriden in tests + virtual int do_getaddrinfo (const char *node_, + const char *service_, + const struct addrinfo *hints_, + struct addrinfo **res_); + + virtual void do_freeaddrinfo (struct addrinfo *res_); + + virtual unsigned int do_if_nametoindex (const char *ifname_); +}; +} + +#endif diff --git a/src/tcp_address.cpp b/src/tcp_address.cpp index 95eb889c..e8c54ced 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)); @@ -613,106 +72,46 @@ zmq::tcp_address_t::~tcp_address_t () { } -int zmq::tcp_address_t::resolve (const char *name_, - bool local_, - bool ipv6_, - bool is_src_) +int zmq::tcp_address_t::resolve (const char *name_, bool local_, 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; - } - } + // 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_); - // Find the ':' at end that separates address from the port number. - const char *delimiter = strrchr (name_, ':'); - if (!delimiter) { - errno = EINVAL; - return -1; - } + ip_resolver_options_t src_resolver_opts; - // Separate the address/port. - std::string addr_str (name_, delimiter - name_); - std::string port_str (delimiter + 1); + src_resolver_opts + .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); - // 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); + ip_resolver_t src_resolver (src_resolver_opts); - // 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; + 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; } - // 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; - return -1; - } - } + ip_resolver_options_t resolver_opts; - // 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; + resolver_opts.bindable (local_) + .allow_dns (!local_) + .allow_nic_name (local_) + .ipv6 (ipv6_) + .expect_port (true); - // 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); - } + ip_resolver_t resolver (resolver_opts); - return 0; + return resolver.resolve (&address, name_); } int zmq::tcp_address_t::to_string (std::string &addr_) @@ -813,7 +212,17 @@ 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_options_t resolver_opts; + + resolver_opts.bindable (false) + .allow_dns (false) + .allow_nic_name (false) + .ipv6 (ipv6_) + .expect_port (false); + + ip_resolver_t resolver (resolver_opts); + + 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 8c20a4ce..d3916d1f 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 @@ -48,8 +50,7 @@ class tcp_address_t // structure. If 'local' is true, names are resolved as local interface // 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); + int resolve (const char *name_, bool local_, bool ipv6_); // The opposite to resolve() virtual int to_string (std::string &addr_); @@ -67,31 +68,8 @@ 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/tests/testutil.hpp b/tests/testutil.hpp index dad38433..3a3f9d10 100644 --- a/tests/testutil.hpp +++ b/tests/testutil.hpp @@ -80,6 +80,8 @@ #include #include #include +#include +#include #if defined(ZMQ_HAVE_AIX) #include #include @@ -412,6 +414,34 @@ int is_tipc_available (void) #endif // ZMQ_HAVE_TIPC } +// Wrapper around 'inet_pton' for systems that don't support it (e.g. Windows +// XP) +int test_inet_pton (int af_, const char *src_, void *dst_) +{ +#if defined(ZMQ_HAVE_WINDOWS) && (_WIN32_WINNT < 0x0600) + if (af_ == AF_INET) { + struct in_addr *ip4addr = (struct in_addr *) dst_; + + ip4addr->s_addr = inet_addr (src_); + + // INADDR_NONE is -1 which is also a valid representation for IP + // 255.255.255.255 + if (ip4addr->s_addr == INADDR_NONE + && strcmp (src_, "255.255.255.255") != 0) { + return 0; + } + + // Success + return 1; + } else { + // Not supported. + return 0; + } +#else + return inet_pton (af_, src_, dst_); +#endif +} + #if defined(ZMQ_HAVE_WINDOWS) int close (int fd) diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 04b4cdf3..1b832810 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -5,6 +5,7 @@ set(unittests unittest_ypipe unittest_poller unittest_mtrie + unittest_ip_resolver ) #IF (ENABLE_DRAFTS) diff --git a/unittests/unittest_ip_resolver.cpp b/unittests/unittest_ip_resolver.cpp new file mode 100644 index 00000000..a9b70d12 --- /dev/null +++ b/unittests/unittest_ip_resolver.cpp @@ -0,0 +1,882 @@ +/* +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 + +void setUp () +{ +} + +void tearDown () +{ +} + +class test_ip_resolver_t : public zmq::ip_resolver_t +{ + public: + test_ip_resolver_t (zmq::ip_resolver_options_t opts_) : + ip_resolver_t (opts_) + { + } + + protected: + struct dns_lut_t + { + const char *hostname; + const char *ipv4; + const char *ipv6; + }; + + virtual int do_getaddrinfo (const char *node_, + const char *service_, + const struct addrinfo *hints_, + struct addrinfo **res_) + { + 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"}, + }; + unsigned lut_len = sizeof (dns_lut) / sizeof (dns_lut[0]); + struct addrinfo ai; + + assert (service_ == NULL); + + bool ipv6 = (hints_->ai_family == AF_INET6); + bool no_dns = hints_->ai_flags & AI_NUMERICHOST; + 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 (or DNS is + // forbidden), assume that it's a numeric IP address + ip = node_; + } + + // Call the real getaddrinfo implementation, making sure that it won't + // attempt to resolve using DNS + ai = *hints_; + ai.ai_flags |= AI_NUMERICHOST; + + return zmq::ip_resolver_t::do_getaddrinfo (ip, NULL, &ai, res_); + } + + virtual unsigned int do_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; + } +}; + +// Attempt a resolution and test the results. If 'expected_addr_' is NULL +// assume that the resolution is meant to fail. +// +// On windows we can receive an IPv4 address even when an IPv6 is requested, if +// we're in this situation then we compare to 'expected_addr_v4_failover_' +// instead. +static void test_resolve (zmq::ip_resolver_options_t opts_, + const char *name_, + const char *expected_addr_, + uint16_t expected_port_ = 0, + uint16_t expected_zone_ = 0, + const char *expected_addr_v4_failover_ = NULL) +{ + zmq::ip_addr_t addr; + int family = opts_.ipv6 () ? AF_INET6 : AF_INET; + + if (family == AF_INET6 && !is_ipv6_available ()) { + TEST_IGNORE_MESSAGE ("ipv6 is not available"); + } + + // Generate an invalid but well-defined 'ip_addr_t'. Avoids testing + // uninitialized values if the code is buggy. + memset (&addr, 0xba, sizeof (addr)); + + test_ip_resolver_t resolver (opts_); + + int rc = resolver.resolve (&addr, name_); + + if (expected_addr_ == NULL) { + TEST_ASSERT_EQUAL (-1, rc); + return; + } else { + TEST_ASSERT_EQUAL (0, rc); + } + +#if defined ZMQ_HAVE_WINDOWS + if (family == AF_INET6 && expected_addr_v4_failover_ != NULL && + addr.generic.sa_family == AF_INET) { + // We've requested an IPv6 but the system gave us an IPv4, use the + // failover address + family = AF_INET; + expected_addr_ = expected_addr_v4_failover_; + } +#else + (void)expected_addr_v4_failover_; +#endif + + TEST_ASSERT_EQUAL (family, addr.generic.sa_family); + + if (family == AF_INET6) { + struct in6_addr expected_addr; + const sockaddr_in6 *ip6_addr = &addr.ipv6; + + assert (test_inet_pton (AF_INET6, expected_addr_, &expected_addr) == 1); + + 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); + } else { + struct in_addr expected_addr; + const sockaddr_in *ip4_addr = &addr.ipv4; + + assert (test_inet_pton (AF_INET, expected_addr_, &expected_addr) == 1); + + 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); + } +} + +// Helper macro to define the v4/v6 function pairs +#define MAKE_TEST_V4V6(_test) \ + static void _test##_ipv4 () { _test (false); } \ + \ + static void _test##_ipv6 () { _test (true); } + +static void test_bind_any (int ipv6_) +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.bindable (true).expect_port (true).ipv6 (ipv6_); + + const char *expected = ipv6_ ? "::" : "0.0.0.0"; + test_resolve (resolver_opts, "*:*", expected, 0); +} +MAKE_TEST_V4V6 (test_bind_any) + +static void test_nobind_any (int ipv6_) +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true).ipv6 (ipv6_); + + // Wildcard should be rejected if we're not looking for a + // bindable address + test_resolve (resolver_opts, "*:*", NULL); +} +MAKE_TEST_V4V6 (test_nobind_any) + +static void test_nobind_any_port (int ipv6_) +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true).ipv6 (ipv6_); + + // Wildcard should be rejected if we're not looking for a + // bindable address + test_resolve (resolver_opts, "*:1234", NULL); +} +MAKE_TEST_V4V6 (test_nobind_any_port) + +static void test_nobind_addr_anyport (int ipv6_) +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true).ipv6 (ipv6_); + + // 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. + const char *expected = ipv6_ ? "::ffff:127.0.0.1" : "127.0.0.1"; + const char *fallback = ipv6_ ? "127.0.0.1" : NULL; + test_resolve (resolver_opts, "127.0.0.1:*", expected, 0, 0, fallback); +} +MAKE_TEST_V4V6 (test_nobind_addr_anyport) + +static void test_parse_ipv4_simple () +{ + zmq::ip_resolver_options_t resolver_opts; + + test_resolve (resolver_opts, "1.2.128.129", "1.2.128.129"); +} + +static void test_parse_ipv4_zero () +{ + zmq::ip_resolver_options_t resolver_opts; + + test_resolve (resolver_opts, "0.0.0.0", "0.0.0.0"); +} + +static void test_parse_ipv4_max () +{ + zmq::ip_resolver_options_t resolver_opts; + + test_resolve (resolver_opts, "255.255.255.255", "255.255.255.255"); +} + +static void test_parse_ipv4_brackets () +{ + zmq::ip_resolver_options_t resolver_opts; + + // Not particularly useful, but valid + test_resolve (resolver_opts, "[1.2.128.129]", "1.2.128.129"); +} + +static void test_parse_ipv4_brackets_missingl () +{ + zmq::ip_resolver_options_t resolver_opts; + + test_resolve (resolver_opts, "1.2.128.129]", NULL); +} + +static void test_parse_ipv4_brackets_missingr () +{ + zmq::ip_resolver_options_t resolver_opts; + + test_resolve (resolver_opts, "[1.2.128.129", NULL); +} + +static void test_parse_ipv4_brackets_bad () +{ + zmq::ip_resolver_options_t resolver_opts; + + test_resolve (resolver_opts, "[1.2.128].129", NULL); +} + +static void test_parse_ipv4_reject_port () +{ + zmq::ip_resolver_options_t resolver_opts; + + // No port expected, should be rejected + test_resolve (resolver_opts, "1.2.128.129:123", NULL); +} + +static void test_parse_ipv4_reject_any () +{ + zmq::ip_resolver_options_t resolver_opts; + + // No port expected, should be rejected + test_resolve (resolver_opts, "1.2.128.129:*", NULL); +} + +static void test_parse_ipv4_reject_ipv6 () +{ + zmq::ip_resolver_options_t resolver_opts; + + // No port expected, should be rejected + test_resolve (resolver_opts, "::1", NULL); +} + +static void test_parse_ipv4_port () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true); + + test_resolve (resolver_opts, "1.2.128.129:123", "1.2.128.129", 123); +} + +static void test_parse_ipv4_port0 () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true); + + // Port 0 is accepted and is equivalent to * + test_resolve (resolver_opts, "1.2.128.129:0", "1.2.128.129", 0); +} + +static void test_parse_ipv4_port_garbage () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true); + + // The code doesn't validate that the port doesn't contain garbage + test_resolve (resolver_opts, "1.2.3.4:567bad", "1.2.3.4", 567); +} + +static void test_parse_ipv4_port_missing () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true); + + test_resolve (resolver_opts, "1.2.3.4", NULL); +} + +static void test_parse_ipv4_port_bad () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true); + + test_resolve (resolver_opts, "1.2.3.4:bad", NULL); +} + +static void test_parse_ipv4_port_brackets () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true); + + test_resolve (resolver_opts, "[192.168.1.1]:5555", "192.168.1.1", 5555); +} + +static void test_parse_ipv4_port_brackets_bad () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true); + + test_resolve (resolver_opts, "[192.168.1.1:]5555", NULL); +} + +static void test_parse_ipv4_port_brackets_bad2 () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true); + + test_resolve (resolver_opts, "[192.168.1.1:5555]", NULL); +} + +static void test_parse_ipv4_wild_brackets_bad () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true); + + test_resolve (resolver_opts, "[192.168.1.1:*]", NULL); +} + +static void test_parse_ipv4_port_ipv6_reject () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true); + + test_resolve (resolver_opts, "[::1]:1234", NULL); +} + +static void test_parse_ipv6_simple () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "::1", "::1"); +} + +static void test_parse_ipv6_simple2 () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "abcd:1234::1:0:234", "abcd:1234::1:0:234"); +} + +static void test_parse_ipv6_zero () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "::", "::"); +} + +static void test_parse_ipv6_max () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); +} + +static void test_parse_ipv6_brackets () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "[::1]", "::1"); +} + +static void test_parse_ipv6_brackets_missingl () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "::1]", NULL); +} + +static void test_parse_ipv6_brackets_missingr () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "[::1", NULL); +} + +static void test_parse_ipv6_brackets_bad () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "[abcd:1234::1:]0:234", NULL); +} + +static void test_parse_ipv6_port () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true).expect_port (true); + + test_resolve (resolver_opts, "[1234::1]:80", "1234::1", 80); +} + +static void test_parse_ipv6_port_any () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true).expect_port (true); + + test_resolve (resolver_opts, "[1234::1]:*", "1234::1", 0); +} + +static void test_parse_ipv6_port_nobrackets () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true).expect_port (true); + + // Should this be allowed? Seems error-prone but so far ZMQ accepts it. + test_resolve (resolver_opts, "abcd:1234::1:0:234:123", "abcd:1234::1:0:234", + 123); +} + +static void test_parse_ipv4_in_ipv6 () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + // Parsing IPv4 should also work if an IPv6 is requested, it returns an + // IPv6 with the IPv4 address embedded (except sometimes on Windows where + // we end up with an IPv4 anyway) + test_resolve (resolver_opts, "11.22.33.44", "::ffff:11.22.33.44", 0, 0, + "11.22.33.44"); +} + +static void test_parse_ipv4_in_ipv6_port () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true).expect_port (true); + + test_resolve (resolver_opts, "11.22.33.44:55", "::ffff:11.22.33.44", 55, 0, + "11.22.33.44"); +} + +static void test_parse_ipv6_scope_int () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "3000:4:5::1:234%2", "3000:4:5::1:234", 0, 2); +} + +static void test_parse_ipv6_scope_zero () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "3000:4:5::1:234%0", NULL); +} + +static void test_parse_ipv6_scope_int_port () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true).ipv6 (true); + + test_resolve (resolver_opts, "3000:4:5::1:234%2:1111", "3000:4:5::1:234", + 1111, 2); +} + +static void test_parse_ipv6_scope_if () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "3000:4:5::1:234%eth1", "3000:4:5::1:234", 0, + 3); +} + +static void test_parse_ipv6_scope_if_port () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true).ipv6 (true); + + test_resolve (resolver_opts, "3000:4:5::1:234%eth0:8080", "3000:4:5::1:234", + 8080, 2); +} + +static void test_parse_ipv6_scope_if_port_brackets () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true).ipv6 (true); + + test_resolve (resolver_opts, "[3000:4:5::1:234%eth0]:8080", + "3000:4:5::1:234", 8080, 2); +} + +static void test_parse_ipv6_scope_badif () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true); + + test_resolve (resolver_opts, "3000:4:5::1:234%bad0", NULL); +} + +static void test_dns_ipv4_simple () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true); + + test_resolve (resolver_opts, "ip.zeromq.org", "10.100.0.1"); +} + +static void test_dns_ipv4_only () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true); + + test_resolve (resolver_opts, "ipv4only.zeromq.org", "10.100.0.2"); +} + +static void test_dns_ipv4_invalid () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true); + + test_resolve (resolver_opts, "invalid.zeromq.org", NULL); +} + +static void test_dns_ipv4_ipv6 () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true); + + test_resolve (resolver_opts, "ipv6only.zeromq.org", NULL); +} + +static void test_dns_ipv4_numeric () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true); + + // Numeric IPs should still work + test_resolve (resolver_opts, "5.4.3.2", "5.4.3.2"); +} + +static void test_dns_ipv4_port () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.expect_port (true).allow_dns (true); + + test_resolve (resolver_opts, "ip.zeromq.org:1234", "10.100.0.1", 1234); +} + +static void test_dns_ipv6_simple () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true).allow_dns (true); + + test_resolve (resolver_opts, "ip.zeromq.org", "fdf5:d058:d656::1"); +} + +static void test_dns_ipv6_only () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true).allow_dns (true); + + test_resolve (resolver_opts, "ipv6only.zeromq.org", "fdf5:d058:d656::2"); +} + +static void test_dns_ipv6_invalid () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true).allow_dns (true); + + test_resolve (resolver_opts, "invalid.zeromq.org", NULL); +} + +static void test_dns_ipv6_ipv4 () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true).allow_dns (true); + + // If a host doesn't have an IPv6 then it should resolve as an embedded v4 + // address in an IPv6 + test_resolve (resolver_opts, "ipv4only.zeromq.org", "::ffff:10.100.0.2"); +} + +static void test_dns_ipv6_numeric () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true).allow_dns (true); + + // Numeric IPs should still work + test_resolve (resolver_opts, "fdf5:d058:d656::1", "fdf5:d058:d656::1"); +} + +static void test_dns_ipv6_port () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.ipv6 (true).expect_port (true).allow_dns (true); + + test_resolve (resolver_opts, "ip.zeromq.org:1234", "fdf5:d058:d656::1", + 1234); +} + +void test_dns_brackets () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true); + + test_resolve (resolver_opts, "[ip.zeromq.org]", "10.100.0.1"); +} + +void test_dns_brackets_bad () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true); + + test_resolve (resolver_opts, "[ip.zeromq].org", NULL); +} + +void test_dns_brackets_port () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true); + + test_resolve (resolver_opts, "[ip.zeromq.org]:22", "10.100.0.1", 22); +} + +void test_dns_brackets_port_bad () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true); + + test_resolve (resolver_opts, "[ip.zeromq.org:22]", NULL); +} + +void test_dns_deny (int ipv6_) +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (false).ipv6 (ipv6_); + + // DNS resolution shouldn't work when disallowed + test_resolve (resolver_opts, "ip.zeromq.org", NULL); +} +MAKE_TEST_V4V6 (test_dns_deny) + +void test_dns_ipv6_scope () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true).ipv6 (true); + + // Not sure if that's very useful but you could technically add a scope + // identifier to a hostname + test_resolve (resolver_opts, "ip.zeromq.org%lo0", "fdf5:d058:d656::1", 0, + 1); +} + +void test_dns_ipv6_scope_port () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true).expect_port (true).ipv6 (true); + + // Not sure if that's very useful but you could technically add a scope + // identifier to a hostname + test_resolve (resolver_opts, "ip.zeromq.org%lo0:4444", "fdf5:d058:d656::1", + 4444, 1); +} + +void test_dns_ipv6_scope_port_brackets () +{ + zmq::ip_resolver_options_t resolver_opts; + + resolver_opts.allow_dns (true).expect_port (true).ipv6 (true); + + test_resolve (resolver_opts, "[ip.zeromq.org%lo0]:4444", + "fdf5:d058:d656::1", 4444, 1); +} + +int main (void) +{ + zmq::initialize_network (); + setup_test_environment (); + + UNITY_BEGIN (); + + RUN_TEST (test_bind_any_ipv4); + RUN_TEST (test_bind_any_ipv6); + RUN_TEST (test_nobind_any_ipv4); + RUN_TEST (test_nobind_any_ipv6); + RUN_TEST (test_nobind_any_port_ipv4); + RUN_TEST (test_nobind_any_port_ipv6); + RUN_TEST (test_nobind_addr_anyport_ipv4); + RUN_TEST (test_nobind_addr_anyport_ipv6); + RUN_TEST (test_parse_ipv4_simple); + RUN_TEST (test_parse_ipv4_zero); + RUN_TEST (test_parse_ipv4_max); + RUN_TEST (test_parse_ipv4_brackets); + RUN_TEST (test_parse_ipv4_brackets_missingl); + RUN_TEST (test_parse_ipv4_brackets_missingr); + RUN_TEST (test_parse_ipv4_brackets_bad); + RUN_TEST (test_parse_ipv4_reject_port); + RUN_TEST (test_parse_ipv4_reject_any); + RUN_TEST (test_parse_ipv4_reject_ipv6); + RUN_TEST (test_parse_ipv4_port); + RUN_TEST (test_parse_ipv4_port0); + RUN_TEST (test_parse_ipv4_port_garbage); + RUN_TEST (test_parse_ipv4_port_missing); + RUN_TEST (test_parse_ipv4_port_bad); + RUN_TEST (test_parse_ipv4_port_brackets); + RUN_TEST (test_parse_ipv4_port_brackets_bad); + RUN_TEST (test_parse_ipv4_port_brackets_bad2); + RUN_TEST (test_parse_ipv4_wild_brackets_bad); + RUN_TEST (test_parse_ipv4_port_ipv6_reject); + RUN_TEST (test_parse_ipv6_simple); + RUN_TEST (test_parse_ipv6_simple2); + RUN_TEST (test_parse_ipv6_zero); + RUN_TEST (test_parse_ipv6_max); + RUN_TEST (test_parse_ipv6_brackets); + RUN_TEST (test_parse_ipv6_brackets_missingl); + RUN_TEST (test_parse_ipv6_brackets_missingr); + RUN_TEST (test_parse_ipv6_brackets_bad); + RUN_TEST (test_parse_ipv6_port); + RUN_TEST (test_parse_ipv6_port_any); + RUN_TEST (test_parse_ipv6_port_nobrackets); + RUN_TEST (test_parse_ipv4_in_ipv6); + RUN_TEST (test_parse_ipv4_in_ipv6_port); + RUN_TEST (test_parse_ipv6_scope_int); + RUN_TEST (test_parse_ipv6_scope_zero); + RUN_TEST (test_parse_ipv6_scope_int_port); + RUN_TEST (test_parse_ipv6_scope_if); + RUN_TEST (test_parse_ipv6_scope_if_port); + RUN_TEST (test_parse_ipv6_scope_if_port_brackets); + RUN_TEST (test_parse_ipv6_scope_badif); + RUN_TEST (test_dns_ipv4_simple); + RUN_TEST (test_dns_ipv4_only); + RUN_TEST (test_dns_ipv4_invalid); + RUN_TEST (test_dns_ipv4_ipv6); + RUN_TEST (test_dns_ipv4_numeric); + RUN_TEST (test_dns_ipv4_port); + RUN_TEST (test_dns_ipv6_simple); + RUN_TEST (test_dns_ipv6_only); + RUN_TEST (test_dns_ipv6_invalid); + RUN_TEST (test_dns_ipv6_ipv4); + RUN_TEST (test_dns_ipv6_numeric); + RUN_TEST (test_dns_ipv6_port); + RUN_TEST (test_dns_brackets); + RUN_TEST (test_dns_brackets_bad); + RUN_TEST (test_dns_deny_ipv4); + RUN_TEST (test_dns_deny_ipv6); + RUN_TEST (test_dns_ipv6_scope); + RUN_TEST (test_dns_ipv6_scope_port); + RUN_TEST (test_dns_ipv6_scope_port_brackets); + + zmq::shutdown_network (); + + return UNITY_END (); +}