mirror of
https://github.com/zeromq/libzmq.git
synced 2024-12-12 18:40:27 +01:00
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:
parent
6160da5dbe
commit
4cd2c2ebf8
@ -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
|
||||
|
13
Makefile.am
13
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}
|
||||
|
658
src/ip_resolver.cpp
Normal file
658
src/ip_resolver.cpp
Normal 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
102
src/ip_resolver.hpp
Normal 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
|
@ -48,547 +48,6 @@
|
||||
#include <stdlib.h>
|
||||
#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)
|
||||
{
|
||||
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);
|
||||
|
||||
ip_resolver_options_t src_resolver_opts;
|
||||
|
||||
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);
|
||||
|
||||
ip_resolver_t src_resolver (src_resolver_opts);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 resolver_opts;
|
||||
|
||||
// Separate the address/port.
|
||||
std::string addr_str (name_, delimiter - name_);
|
||||
std::string port_str (delimiter + 1);
|
||||
resolver_opts.bindable (local_)
|
||||
.allow_dns (!local_)
|
||||
.allow_nic_name (local_)
|
||||
.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 resolver (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;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow 0 specifically, to detect invalid port error in atoi if not
|
||||
uint16_t port;
|
||||
if (port_str == "*" || port_str == "0")
|
||||
// Resolve wildcard to 0 to allow autoselection of port
|
||||
port = 0;
|
||||
else {
|
||||
// Parse the port number (0 is not a valid port).
|
||||
port = (uint16_t) atoi (port_str.c_str ());
|
||||
if (port == 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
@ -35,6 +35,8 @@
|
||||
#include <netinet/in.h>
|
||||
#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;
|
||||
};
|
||||
|
||||
|
@ -80,6 +80,8 @@
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#if defined(ZMQ_HAVE_AIX)
|
||||
#include <sys/types.h>
|
||||
#include <sys/socketvar.h>
|
||||
@ -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)
|
||||
|
@ -5,6 +5,7 @@ set(unittests
|
||||
unittest_ypipe
|
||||
unittest_poller
|
||||
unittest_mtrie
|
||||
unittest_ip_resolver
|
||||
)
|
||||
|
||||
#IF (ENABLE_DRAFTS)
|
||||
|
882
unittests/unittest_ip_resolver.cpp
Normal file
882
unittests/unittest_ip_resolver.cpp
Normal 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 ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user