Commit b0df4be5 authored by Lionel Flandrin's avatar Lionel Flandrin

Problem: UDP engine does not support IPv6

Solution: Add IPv6 support
parent 7aba6821
......@@ -925,7 +925,7 @@ unittests_unittest_mtrie_LDADD = $(top_builddir)/src/.libs/libzmq.a \
${UNITY_LIBS} \
$(CODE_COVERAGE_LDFLAGS)
unittests_unittest_ip_resolver_SOURCES = unittests/unittest_ip_resolver.cpp
unittests_unittest_ip_resolver_SOURCES = unittests/unittest_ip_resolver.cpp unittests/unittest_resolver_common.hpp
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 \
......@@ -933,7 +933,7 @@ unittests_unittest_ip_resolver_LDADD = $(top_builddir)/src/.libs/libzmq.a \
${UNITY_LIBS} \
$(CODE_COVERAGE_LDFLAGS)
unittests_unittest_udp_address_SOURCES = unittests/unittest_udp_address.cpp
unittests_unittest_udp_address_SOURCES = unittests/unittest_udp_address.cpp unittests/unittest_resolver_common.hpp
unittests_unittest_udp_address_CPPFLAGS = -I$(top_srcdir)/src ${UNITY_CPPFLAGS} $(CODE_COVERAGE_CPPFLAGS)
unittests_unittest_udp_address_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS)
unittests_unittest_udp_address_LDADD = $(top_builddir)/src/.libs/libzmq.a \
......
......@@ -46,6 +46,20 @@ uint16_t zmq::ip_addr_t::port () const
}
}
const struct sockaddr *zmq::ip_addr_t::as_sockaddr () const
{
return &generic;
}
size_t zmq::ip_addr_t::sockaddr_len () const
{
if (family () == AF_INET6) {
return sizeof (ipv6);
} else {
return sizeof (ipv4);
}
}
void zmq::ip_addr_t::set_port (uint16_t port)
{
if (family () == AF_INET6) {
......
......@@ -46,6 +46,10 @@ union ip_addr_t
int family () const;
bool is_multicast () const;
uint16_t port () const;
const struct sockaddr *as_sockaddr () const;
size_t sockaddr_len () const;
void set_port (uint16_t);
static ip_addr_t any (int family);
......
......@@ -534,7 +534,8 @@ int zmq::socket_base_t::bind (const char *addr_)
paddr->resolved.udp_addr = new (std::nothrow) udp_address_t ();
alloc_assert (paddr->resolved.udp_addr);
rc = paddr->resolved.udp_addr->resolve (address.c_str (), true);
rc = paddr->resolved.udp_addr->resolve (address.c_str (), true,
options.ipv6);
if (rc != 0) {
LIBZMQ_DELETE (paddr);
return -1;
......@@ -876,7 +877,8 @@ int zmq::socket_base_t::connect (const char *addr_)
paddr->resolved.udp_addr = new (std::nothrow) udp_address_t ();
alloc_assert (paddr->resolved.udp_addr);
rc = paddr->resolved.udp_addr->resolve (address.c_str (), false);
rc = paddr->resolved.udp_addr->resolve (address.c_str (), false,
options.ipv6);
if (rc != 0) {
LIBZMQ_DELETE (paddr);
return -1;
......
......@@ -41,28 +41,26 @@
#include <sys/types.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <net/if.h>
#include <ctype.h>
#endif
#include "ip_resolver.hpp"
zmq::udp_address_t::udp_address_t () : is_multicast (false)
zmq::udp_address_t::udp_address_t () : bind_interface (-1), is_multicast (false)
{
memset (&bind_address, 0, sizeof bind_address);
memset (&dest_address, 0, sizeof dest_address);
bind_address = ip_addr_t::any (AF_INET);
target_address = ip_addr_t::any (AF_INET);
}
zmq::udp_address_t::~udp_address_t ()
{
}
int zmq::udp_address_t::resolve (const char *name_, bool bind_)
int zmq::udp_address_t::resolve (const char *name_, bool bind_, bool ipv6_)
{
// No IPv6 support yet
int family = AF_INET;
bool ipv6 = family == AF_INET6;
bool has_interface = false;
ip_addr_t interface_addr;
address = name_;
// If we have a semicolon then we should have an interface specifier in the
// URL
......@@ -79,24 +77,38 @@ int zmq::udp_address_t::resolve (const char *name_, bool bind_)
// indeterminate socktype.
.allow_dns (false)
.allow_nic_name (true)
.ipv6 (ipv6)
.ipv6 (ipv6_)
.expect_port (false);
ip_resolver_t src_resolver (src_resolver_opts);
const int rc =
src_resolver.resolve (&interface_addr, src_name.c_str ());
const int rc = src_resolver.resolve (&bind_address, src_name.c_str ());
if (rc != 0) {
return -1;
}
if (interface_addr.is_multicast ()) {
if (bind_address.is_multicast ()) {
// It doesn't make sense to have a multicast address as a source
errno = EINVAL;
return -1;
}
// This is a hack because we need the interface index when binding
// multicast IPv6, we can't do it by address. Unfortunately for the
// time being we don't have a generic platform-independent function to
// resolve an interface index from an address, so we only support it
// when an actual interface name is provided.
if (src_name == "*") {
bind_interface = 0;
} else {
bind_interface = if_nametoindex (src_name.c_str ());
if (bind_interface == 0) {
// Error, probably not an interface name.
bind_interface = -1;
}
}
has_interface = true;
name_ = src_delimiter + 1;
}
......@@ -107,19 +119,17 @@ int zmq::udp_address_t::resolve (const char *name_, bool bind_)
.allow_dns (!bind_)
.allow_nic_name (bind_)
.expect_port (true)
.ipv6 (ipv6);
.ipv6 (ipv6_);
ip_resolver_t resolver (resolver_opts);
ip_addr_t target_addr;
int rc = resolver.resolve (&target_addr, name_);
int rc = resolver.resolve (&target_address, name_);
if (rc != 0) {
return -1;
}
is_multicast = target_addr.is_multicast ();
uint16_t port = target_addr.port ();
is_multicast = target_address.is_multicast ();
uint16_t port = target_address.port ();
if (has_interface) {
// If we have an interface specifier then the target address must be a
......@@ -129,46 +139,43 @@ int zmq::udp_address_t::resolve (const char *name_, bool bind_)
return -1;
}
interface_addr.set_port (port);
dest_address = target_addr.ipv4;
bind_address = interface_addr.ipv4;
bind_address.set_port (port);
} else {
// If we don't have an explicit interface specifier then the URL is
// ambiguous: if the target address is multicast then it's the
// destination address and the bind address is ANY, if it's unicast
// then it's the bind address when 'bind_' is true and the destination
// otherwise
ip_addr_t any = ip_addr_t::any (family);
any.set_port (port);
if (is_multicast) {
dest_address = target_addr.ipv4;
bind_address = any.ipv4;
if (is_multicast || !bind_) {
bind_address = ip_addr_t::any (target_address.family ());
bind_address.set_port (port);
bind_interface = 0;
} else {
if (bind_) {
dest_address = target_addr.ipv4;
bind_address = target_addr.ipv4;
} else {
dest_address = target_addr.ipv4;
bind_address = any.ipv4;
}
// If we were asked for a bind socket and the address
// provided was not multicast then it was really meant as
// a bind address and the target_address is useless.
bind_address = target_address;
}
}
if (is_multicast) {
multicast = dest_address.sin_addr;
if (bind_address.family () != target_address.family ()) {
errno = EINVAL;
return -1;
}
address = name_;
// For IPv6 multicast we *must* have an interface index since we can't
// bind by address.
if (ipv6_ && is_multicast && bind_interface < 0) {
errno = ENODEV;
return -1;
}
return 0;
}
int zmq::udp_address_t::to_string (std::string &addr_)
int zmq::udp_address_t::family () const
{
addr_ = address;
return 0;
return bind_address.family ();
}
bool zmq::udp_address_t::is_mcast () const
......@@ -176,41 +183,24 @@ bool zmq::udp_address_t::is_mcast () const
return is_multicast;
}
const sockaddr *zmq::udp_address_t::bind_addr () const
{
return (sockaddr *) &bind_address;
}
socklen_t zmq::udp_address_t::bind_addrlen () const
{
return sizeof (sockaddr_in);
}
const sockaddr *zmq::udp_address_t::dest_addr () const
const zmq::ip_addr_t *zmq::udp_address_t::bind_addr () const
{
return (sockaddr *) &dest_address;
return &bind_address;
}
socklen_t zmq::udp_address_t::dest_addrlen () const
int zmq::udp_address_t::bind_if () const
{
return sizeof (sockaddr_in);
return bind_interface;
}
const in_addr zmq::udp_address_t::multicast_ip () const
const zmq::ip_addr_t *zmq::udp_address_t::target_addr () const
{
return multicast;
return &target_address;
}
const in_addr zmq::udp_address_t::interface_ip () const
{
return iface;
}
#if defined ZMQ_HAVE_WINDOWS
unsigned short zmq::udp_address_t::family () const
#else
sa_family_t zmq::udp_address_t::family () const
#endif
int zmq::udp_address_t::to_string (std::string &addr_)
{
return AF_INET;
// XXX what do (factor TCP code?)
addr_ = address;
return 0;
}
......@@ -35,6 +35,8 @@
#include <netinet/in.h>
#endif
#include "ip_resolver.hpp"
namespace zmq
{
class udp_address_t
......@@ -43,32 +45,24 @@ class udp_address_t
udp_address_t ();
virtual ~udp_address_t ();
int resolve (const char *name_, bool receiver_);
int resolve (const char *name_, bool receiver_, bool ipv6_);
// The opposite to resolve()
virtual int to_string (std::string &addr_);
#if defined ZMQ_HAVE_WINDOWS
unsigned short family () const;
#else
sa_family_t family () const;
#endif
const sockaddr *bind_addr () const;
socklen_t bind_addrlen () const;
const sockaddr *dest_addr () const;
socklen_t dest_addrlen () const;
int family () const;
bool is_mcast () const;
const in_addr multicast_ip () const;
const in_addr interface_ip () const;
const ip_addr_t *bind_addr () const;
int bind_if () const;
const ip_addr_t *target_addr () const;
private:
in_addr multicast;
in_addr iface;
sockaddr_in bind_address;
sockaddr_in dest_address;
ip_addr_t bind_address;
int bind_interface;
ip_addr_t target_address;
bool is_multicast;
std::string address;
};
......
......@@ -46,6 +46,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "err.hpp"
#include "ip.hpp"
// OSX uses a different name for this socket option
#ifndef IPV6_ADD_MEMBERSHIP
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
#endif
zmq::udp_engine_t::udp_engine_t (const options_t &options_) :
plugged (false),
fd (-1),
......@@ -111,9 +116,11 @@ void zmq::udp_engine_t::plug (io_thread_t *io_thread_, session_base_t *session_)
if (send_enabled) {
if (!options.raw_socket) {
out_address = address->resolved.udp_addr->dest_addr ();
out_addrlen = address->resolved.udp_addr->dest_addrlen ();
const ip_addr_t *out = address->resolved.udp_addr->target_addr ();
out_address = out->as_sockaddr ();
out_addrlen = out->sockaddr_len ();
} else {
/// XXX fixme ?
out_address = (sockaddr *) &raw_address;
out_addrlen = sizeof (sockaddr_in);
}
......@@ -131,12 +138,29 @@ void zmq::udp_engine_t::plug (io_thread_t *io_thread_, session_base_t *session_)
errno_assert (rc == 0);
#endif
const ip_addr_t *bind_addr = address->resolved.udp_addr->bind_addr ();
ip_addr_t any = ip_addr_t::any (bind_addr->family ());
const ip_addr_t *real_bind_addr;
bool multicast = address->resolved.udp_addr->is_mcast ();
if (multicast) {
// In multicast we should bind ANY and use the mreq struct to
// specify the interface
any.set_port (bind_addr->port ());
real_bind_addr = &any;
} else {
real_bind_addr = bind_addr;
}
#ifdef ZMQ_HAVE_VXWORKS
rc = bind (fd, (sockaddr *) address->resolved.udp_addr->bind_addr (),
address->resolved.udp_addr->bind_addrlen ());
rc = bind (fd, (sockaddr *) real_bind_addr->as_sockaddr (),
real_bind_addr->sockaddr_len ());
#else
rc = bind (fd, address->resolved.udp_addr->bind_addr (),
address->resolved.udp_addr->bind_addrlen ());
rc = bind (fd, real_bind_addr->as_sockaddr (),
real_bind_addr->sockaddr_len ());
#endif
#ifdef ZMQ_HAVE_WINDOWS
wsa_assert (rc != SOCKET_ERROR);
......@@ -144,12 +168,37 @@ void zmq::udp_engine_t::plug (io_thread_t *io_thread_, session_base_t *session_)
errno_assert (rc == 0);
#endif
if (address->resolved.udp_addr->is_mcast ()) {
struct ip_mreq mreq;
mreq.imr_multiaddr = address->resolved.udp_addr->multicast_ip ();
mreq.imr_interface = address->resolved.udp_addr->interface_ip ();
rc = setsockopt (fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq,
sizeof (mreq));
if (multicast) {
const ip_addr_t *mcast_addr =
address->resolved.udp_addr->target_addr ();
if (mcast_addr->family () == AF_INET) {
struct ip_mreq mreq;
mreq.imr_multiaddr = mcast_addr->ipv4.sin_addr;
mreq.imr_interface = bind_addr->ipv4.sin_addr;
rc = setsockopt (fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(char *) &mreq, sizeof (mreq));
errno_assert (rc == 0);
} else if (mcast_addr->family () == AF_INET6) {
struct ipv6_mreq mreq;
int iface = address->resolved.udp_addr->bind_if ();
zmq_assert (iface >= -1);
mreq.ipv6mr_multiaddr = mcast_addr->ipv6.sin6_addr;
mreq.ipv6mr_interface = iface;
rc = setsockopt (fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
(char *) &mreq, sizeof (mreq));
errno_assert (rc == 0);
} else {
// Shouldn't happen
abort ();
}
#ifdef ZMQ_HAVE_WINDOWS
wsa_assert (rc != SOCKET_ERROR);
#else
......
......@@ -32,6 +32,18 @@
#include <unity.h>
// Helper macro to define the v4/v6 function pairs
#define MAKE_TEST_V4V6(_test) \
static void _test##_ipv4 () { _test (false); } \
\
static void _test##_ipv6 () \
{ \
if (!is_ipv6_available ()) { \
TEST_IGNORE_MESSAGE ("ipv6 is not available"); \
} \
_test (true); \
}
void setUp ()
{
setup_test_context ();
......@@ -111,16 +123,19 @@ void test_join_twice_fails ()
test_context_socket_close (dish);
}
void test_radio_dish_tcp_poll ()
void test_radio_dish_tcp_poll (int ipv6_)
{
size_t len = MAX_SOCKET_STRING;
char my_endpoint[MAX_SOCKET_STRING];
void *radio = test_context_socket (ZMQ_RADIO);
bind_loopback_ipv4 (radio, my_endpoint, len);
bind_loopback (radio, ipv6_, my_endpoint, len);
void *dish = test_context_socket (ZMQ_DISH);
TEST_ASSERT_SUCCESS_ERRNO (
zmq_setsockopt (dish, ZMQ_IPV6, &ipv6_, sizeof (int)));
// Joining
TEST_ASSERT_SUCCESS_ERRNO (zmq_join (dish, "Movies"));
......@@ -175,22 +190,31 @@ void test_radio_dish_tcp_poll ()
test_context_socket_close (dish);
test_context_socket_close (radio);
}
MAKE_TEST_V4V6 (test_radio_dish_tcp_poll)
void test_dish_connect_fails ()
void test_dish_connect_fails (int ipv6_)
{
void *dish = test_context_socket (ZMQ_DISH);
TEST_ASSERT_SUCCESS_ERRNO (
zmq_setsockopt (dish, ZMQ_IPV6, &ipv6_, sizeof (int)));
const char *url = ipv6_ ? "udp://[::1]:5556" : "udp://127.0.0.1:5556";
// Connecting dish should fail
TEST_ASSERT_FAILURE_ERRNO (ENOCOMPATPROTO,
zmq_connect (dish, "udp://127.0.0.1:5556"));
TEST_ASSERT_FAILURE_ERRNO (ENOCOMPATPROTO, zmq_connect (dish, url));
test_context_socket_close (dish);
}
MAKE_TEST_V4V6 (test_dish_connect_fails)
void test_radio_bind_fails ()
void test_radio_bind_fails (int ipv6_)
{
void *radio = test_context_socket (ZMQ_RADIO);
TEST_ASSERT_SUCCESS_ERRNO (
zmq_setsockopt (radio, ZMQ_IPV6, &ipv6_, sizeof (int)));
// Connecting dish should fail
// Bind radio should fail
TEST_ASSERT_FAILURE_ERRNO (ENOCOMPATPROTO,
......@@ -198,14 +222,22 @@ void test_radio_bind_fails ()
test_context_socket_close (radio);
}
MAKE_TEST_V4V6 (test_radio_bind_fails)
void test_radio_dish_udp ()
void test_radio_dish_udp (int ipv6_)
{
void *radio = test_context_socket (ZMQ_RADIO);
void *dish = test_context_socket (ZMQ_DISH);
TEST_ASSERT_SUCCESS_ERRNO (
zmq_setsockopt (radio, ZMQ_IPV6, &ipv6_, sizeof (int)));
TEST_ASSERT_SUCCESS_ERRNO (
zmq_setsockopt (dish, ZMQ_IPV6, &ipv6_, sizeof (int)));
const char *radio_url = ipv6_ ? "udp://[::1]:5556" : "udp://127.0.0.1:5556";
TEST_ASSERT_SUCCESS_ERRNO (zmq_bind (dish, "udp://*:5556"));
TEST_ASSERT_SUCCESS_ERRNO (zmq_connect (radio, "udp://127.0.0.1:5556"));
TEST_ASSERT_SUCCESS_ERRNO (zmq_connect (radio, radio_url));
msleep (SETTLE_TIME);
......@@ -217,6 +249,7 @@ void test_radio_dish_udp ()
test_context_socket_close (dish);
test_context_socket_close (radio);
}
MAKE_TEST_V4V6 (test_radio_dish_udp)
int main (void)
{
......@@ -226,10 +259,14 @@ int main (void)
RUN_TEST (test_leave_unjoined_fails);
RUN_TEST (test_join_too_long_fails);
RUN_TEST (test_join_twice_fails);
RUN_TEST (test_radio_bind_fails);
RUN_TEST (test_dish_connect_fails);
RUN_TEST (test_radio_dish_tcp_poll);
RUN_TEST (test_radio_dish_udp);
RUN_TEST (test_radio_bind_fails_ipv4);
RUN_TEST (test_radio_bind_fails_ipv6);
RUN_TEST (test_dish_connect_fails_ipv4);
RUN_TEST (test_dish_connect_fails_ipv6);
RUN_TEST (test_radio_dish_tcp_poll_ipv4);
RUN_TEST (test_radio_dish_tcp_poll_ipv6);
RUN_TEST (test_radio_dish_udp_ipv4);
RUN_TEST (test_radio_dish_udp_ipv6);
return UNITY_END ();
}
......@@ -26,7 +26,7 @@ include_directories("${CMAKE_SOURCE_DIR}/include" "${CMAKE_SOURCE_DIR}/src" "${C
foreach(test ${unittests})
# target_sources not supported before CMake 3.1
add_executable(${test} ${test}.cpp)
add_executable(${test} ${test}.cpp "unittest_resolver_common.hpp")
# per-test directories not generated on OS X / Darwin
if (NOT ${CMAKE_CXX_COMPILER_ID} MATCHES "Clang.*")
......
......@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <unity.h>
#include "../tests/testutil.hpp"
#include "../unittests/unittest_resolver_common.hpp"
#include <ip_resolver.hpp>
#include <ip.hpp>
......@@ -155,41 +156,9 @@ static void test_resolve (zmq::ip_resolver_options_t opts_,
TEST_ASSERT_EQUAL (0, rc);
}
#if defined ZMQ_HAVE_WINDOWS
if (family == AF_INET6 && expected_addr_v4_failover_ != NULL &&
addr.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.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 (expected_addr.s_addr, ip4_addr->sin_addr.s_addr);
TEST_ASSERT_EQUAL (htons (expected_port_), ip4_addr->sin_port);
}
validate_address (family, &addr, expected_addr_,
expected_port_, expected_zone_,
expected_addr_v4_failover_);
}
// Helper macro to define the v4/v6 function pairs
......
/*
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/>.
*/
#ifndef __UNITTEST_RESOLVER_COMMON_INCLUDED__
#define __UNITTEST_RESOLVER_COMMON_INCLUDED__
#include <ip_resolver.hpp>
// Attempt a resolution and test the results.
//
// 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.
void validate_address(int family, const zmq::ip_addr_t *addr_,
const char *expected_addr_,
uint16_t expected_port_ = 0,
uint16_t expected_zone_ = 0,
const char *expected_addr_v4_failover_ = NULL)
{
#if defined ZMQ_HAVE_WINDOWS
if (family == AF_INET6 && expected_addr_v4_failover_ != NULL &&
addr_->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_->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 (expected_addr.s_addr, ip4_addr->sin_addr.s_addr);
TEST_ASSERT_EQUAL (htons (expected_port_), ip4_addr->sin_port);
}
}
#endif // __UNITTEST_RESOLVER_COMMON_INCLUDED__
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment