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.
This commit is contained in:
Lionel Flandrin 2018-04-30 16:56:17 +02:00
parent 6160da5dbe
commit 4cd2c2ebf8
9 changed files with 1731 additions and 659 deletions

View File

@ -603,6 +603,7 @@ set (cxx-sources
udp_address.cpp udp_address.cpp
scatter.cpp scatter.cpp
gather.cpp gather.cpp
ip_resolver.cpp
zap_client.cpp zap_client.cpp
# at least for VS, the header files must also be listed # at least for VS, the header files must also be listed
address.hpp address.hpp

View File

@ -83,6 +83,8 @@ src_libzmq_la_SOURCES = \
src/io_thread.hpp \ src/io_thread.hpp \
src/ip.cpp \ src/ip.cpp \
src/ip.hpp \ src/ip.hpp \
src/ip_resolver.cpp \
src/ip_resolver.hpp \
src/ipc_address.cpp \ src/ipc_address.cpp \
src/ipc_address.hpp \ src/ipc_address.hpp \
src/ipc_connecter.cpp \ src/ipc_connecter.cpp \
@ -895,7 +897,8 @@ if ENABLE_STATIC
test_apps += \ test_apps += \
unittests/unittest_poller \ unittests/unittest_poller \
unittests/unittest_ypipe \ unittests/unittest_ypipe \
unittests/unittest_mtrie unittests/unittest_mtrie \
unittests/unittest_ip_resolver
unittests_unittest_poller_SOURCES = unittests/unittest_poller.cpp unittests_unittest_poller_SOURCES = unittests/unittest_poller.cpp
unittests_unittest_poller_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS} $(CODE_COVERAGE_CPPFLAGS) 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} \ ${src_libzmq_la_LIBADD} \
${UNITY_LIBS} \ ${UNITY_LIBS} \
$(CODE_COVERAGE_LDFLAGS) $(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 endif
check_PROGRAMS = ${test_apps} check_PROGRAMS = ${test_apps}

658
src/ip_resolver.cpp Normal file
View File

@ -0,0 +1,658 @@
#include "precompiled.hpp"
#include <string>
#include <cstring>
#include "macros.hpp"
#include "stdint.hpp"
#include "err.hpp"
#include "ip.hpp"
#ifndef ZMQ_HAVE_WINDOWS
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <net/if.h>
#include <netdb.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#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 <sys/sockio.h>
// 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 <sys/ioctl.h>
#ifdef ZMQ_HAVE_VXWORKS
#include <ioLib.h>
#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 <ifaddrs.h>
// 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 <netioapi.h>
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
}

102
src/ip_resolver.hpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __ZMQ_IP_RESOLVER_HPP_INCLUDED__
#define __ZMQ_IP_RESOLVER_HPP_INCLUDED__
#if !defined ZMQ_HAVE_WINDOWS
#include <sys/socket.h>
#include <netinet/in.h>
#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

View File

@ -48,547 +48,6 @@
#include <stdlib.h> #include <stdlib.h>
#endif #endif
#ifdef ZMQ_HAVE_SOLARIS
#include <sys/sockio.h>
// 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 <sys/ioctl.h>
#ifdef ZMQ_HAVE_VXWORKS
#include <ioLib.h>
#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 <ifaddrs.h>
// 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 <netioapi.h>
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) zmq::tcp_address_t::tcp_address_t () : _has_src_addr (false)
{ {
memset (&address, 0, sizeof (address)); 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_, int zmq::tcp_address_t::resolve (const char *name_, bool local_, bool ipv6_)
bool local_,
bool ipv6_,
bool is_src_)
{ {
if (!is_src_) { // Test the ';' to know if we have a source address in name_
// Test the ';' to know if we have a source address in name_ const char *src_delimiter = strrchr (name_, ';');
const char *src_delimiter = strrchr (name_, ';'); if (src_delimiter) {
if (src_delimiter) { std::string src_name (name_, src_delimiter - name_);
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. ip_resolver_options_t src_resolver_opts;
const char *delimiter = strrchr (name_, ':');
if (!delimiter) {
errno = EINVAL;
return -1;
}
// Separate the address/port. src_resolver_opts
std::string addr_str (name_, delimiter - name_); .bindable (true)
std::string port_str (delimiter + 1); // 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 ip_resolver_t src_resolver (src_resolver_opts);
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 const int rc =
// Reference: https://tools.ietf.org/html/rfc4007 src_resolver.resolve (&source_address, src_name.c_str ());
std::size_t pos = addr_str.rfind ('%'); if (rc != 0)
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; return -1;
} name_ = src_delimiter + 1;
_has_src_addr = true;
} }
// Allow 0 specifically, to detect invalid port error in atoi if not ip_resolver_options_t resolver_opts;
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;
}
}
// Resolve the IP address. resolver_opts.bindable (local_)
int rc; .allow_dns (!local_)
if (local_ || is_src_) .allow_nic_name (local_)
rc = resolve_interface (addr_str.c_str (), ipv6_, is_src_); .ipv6 (ipv6_)
else .expect_port (true);
rc = resolve_hostname (addr_str.c_str (), ipv6_, is_src_);
if (rc != 0)
return -1;
// Set the port into the address structure. ip_resolver_t resolver (resolver_opts);
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);
}
return 0; return resolver.resolve (&address, name_);
} }
int zmq::tcp_address_t::to_string (std::string &addr_) 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_); addr_str.assign (name_);
// Parse address part using standard routines. // 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) if (rc != 0)
return rc; return rc;

View File

@ -35,6 +35,8 @@
#include <netinet/in.h> #include <netinet/in.h>
#endif #endif
#include "ip_resolver.hpp"
namespace zmq namespace zmq
{ {
class tcp_address_t class tcp_address_t
@ -48,8 +50,7 @@ class tcp_address_t
// structure. If 'local' is true, names are resolved as local interface // structure. If 'local' is true, names are resolved as local interface
// names. If it is false, names are resolved as remote hostnames. // names. If it is false, names are resolved as remote hostnames.
// If 'ipv6' is true, the name may resolve to IPv6 address. // If 'ipv6' is true, the name may resolve to IPv6 address.
int int resolve (const char *name_, bool local_, bool ipv6_);
resolve (const char *name_, bool local_, bool ipv6_, bool is_src_ = false);
// The opposite to resolve() // The opposite to resolve()
virtual int to_string (std::string &addr_); virtual int to_string (std::string &addr_);
@ -67,31 +68,8 @@ class tcp_address_t
bool has_src_addr () const; bool has_src_addr () const;
protected: protected:
int resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_ = false); ip_addr_t address;
int resolve_interface (const char *interface_, ip_addr_t source_address;
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;
bool _has_src_addr; bool _has_src_addr;
}; };

View File

@ -80,6 +80,8 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <net/if.h>
#include <netdb.h>
#if defined(ZMQ_HAVE_AIX) #if defined(ZMQ_HAVE_AIX)
#include <sys/types.h> #include <sys/types.h>
#include <sys/socketvar.h> #include <sys/socketvar.h>
@ -412,6 +414,34 @@ int is_tipc_available (void)
#endif // ZMQ_HAVE_TIPC #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) #if defined(ZMQ_HAVE_WINDOWS)
int close (int fd) int close (int fd)

View File

@ -5,6 +5,7 @@ set(unittests
unittest_ypipe unittest_ypipe
unittest_poller unittest_poller
unittest_mtrie unittest_mtrie
unittest_ip_resolver
) )
#IF (ENABLE_DRAFTS) #IF (ENABLE_DRAFTS)

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <unity.h>
#include "../tests/testutil.hpp"
#include <ip_resolver.hpp>
#include <ip.hpp>
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 ();
}