Commit 746d4a0f authored by Lionel Flandrin's avatar Lionel Flandrin

Problem: UDP transport doesn't let the user specify the local bind address

for multicast

Solution: augment the UDP URL syntax to accept an interface specifier with a
syntax similar to the PGM urls.

Fixes #2212
parent 524affc4
...@@ -28,17 +28,59 @@ int zmq::ip_addr_t::family () const ...@@ -28,17 +28,59 @@ int zmq::ip_addr_t::family () const
bool zmq::ip_addr_t::is_multicast () const bool zmq::ip_addr_t::is_multicast () const
{ {
if (family () == AF_INET) { if (family () == AF_INET) {
uint32_t addr = ntohl (ipv4.sin_addr.s_addr);
// IPv4 Multicast: address MSBs are 1110 // IPv4 Multicast: address MSBs are 1110
// Range: 224.0.0.0 - 239.255.255.255 // Range: 224.0.0.0 - 239.255.255.255
return (addr & 0xf0000000) == 0xe0000000; return IN_MULTICAST (ntohl (ipv4.sin_addr.s_addr));
} else { } else {
const uint8_t *addr = (const uint8_t *) &ipv6.sin6_addr;
// IPv6 Multicast: ff00::/8 // IPv6 Multicast: ff00::/8
return addr[0] == 0xff; return IN6_IS_ADDR_MULTICAST (&ipv6.sin6_addr);
}
}
uint16_t zmq::ip_addr_t::port () const
{
if (family () == AF_INET6) {
return ntohs (ipv6.sin6_port);
} else {
return ntohs (ipv4.sin_port);
}
}
void zmq::ip_addr_t::set_port (uint16_t port)
{
if (family () == AF_INET6) {
ipv6.sin6_port = htons (port);
} else {
ipv4.sin_port = htons (port);
}
}
// Construct an "ANY" address for the given family
zmq::ip_addr_t zmq::ip_addr_t::any (int family)
{
ip_addr_t addr;
if (family == AF_INET) {
sockaddr_in *ip4_addr = &addr.ipv4;
memset (ip4_addr, 0, sizeof (*ip4_addr));
ip4_addr->sin_family = AF_INET;
ip4_addr->sin_addr.s_addr = htonl (INADDR_ANY);
} else if (family == AF_INET6) {
sockaddr_in6 *ip6_addr = &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 {
assert (0 == "unsupported address family");
} }
return addr;
} }
zmq::ip_resolver_options_t::ip_resolver_options_t () : zmq::ip_resolver_options_t::ip_resolver_options_t () :
...@@ -197,25 +239,8 @@ int zmq::ip_resolver_t::resolve (ip_addr_t *ip_addr_, const char *name_) ...@@ -197,25 +239,8 @@ int zmq::ip_resolver_t::resolve (ip_addr_t *ip_addr_, const char *name_)
if (options.bindable () && addr == "*") { if (options.bindable () && addr == "*") {
// Return an ANY address // Return an ANY address
*ip_addr_ = ip_addr_t::any (options.ipv6 () ? AF_INET6 : AF_INET);
resolved = true; 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 ()) { if (!resolved && options.allow_nic_name ()) {
...@@ -242,11 +267,10 @@ int zmq::ip_resolver_t::resolve (ip_addr_t *ip_addr_, const char *name_) ...@@ -242,11 +267,10 @@ int zmq::ip_resolver_t::resolve (ip_addr_t *ip_addr_, const char *name_)
// for us but since we don't resolve service names it's a bit overkill and // 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 // we'd still have to do it manually when the address is resolved by
// 'resolve_nic_name' // 'resolve_nic_name'
ip_addr_->set_port (port);
if (ip_addr_->family () == AF_INET6) { if (ip_addr_->family () == AF_INET6) {
ip_addr_->ipv6.sin6_port = htons (port);
ip_addr_->ipv6.sin6_scope_id = zone_id; ip_addr_->ipv6.sin6_scope_id = zone_id;
} else {
ip_addr_->ipv4.sin_port = htons (port);
} }
assert (resolved == true); assert (resolved == true);
......
...@@ -45,6 +45,10 @@ union ip_addr_t ...@@ -45,6 +45,10 @@ union ip_addr_t
int family () const; int family () const;
bool is_multicast () const; bool is_multicast () const;
uint16_t port () const;
void set_port (uint16_t);
static ip_addr_t any (int family);
}; };
class ip_resolver_options_t class ip_resolver_options_t
......
...@@ -58,50 +58,108 @@ zmq::udp_address_t::~udp_address_t () ...@@ -58,50 +58,108 @@ 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_)
{ {
// No IPv6 support yet
int family = AF_INET;
bool ipv6 = family == AF_INET6;
bool has_interface = false;
ip_addr_t interface_addr;
// If we have a semicolon then we should have an interface specifier in the
// URL
const char *src_delimiter = strrchr (name_, ';');
if (src_delimiter) {
std::string src_name (name_, src_delimiter - name_);
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 (false);
ip_resolver_t src_resolver (src_resolver_opts);
const int rc =
src_resolver.resolve (&interface_addr, src_name.c_str ());
if (rc != 0) {
return -1;
}
if (interface_addr.is_multicast ()) {
// It doesn't make sense to have a multicast address as a source
errno = EINVAL;
return -1;
}
has_interface = true;
name_ = src_delimiter + 1;
}
ip_resolver_options_t resolver_opts; ip_resolver_options_t resolver_opts;
resolver_opts.bindable (bind_) resolver_opts.bindable (bind_)
.allow_dns (!bind_) .allow_dns (!bind_)
.allow_nic_name (bind_) .allow_nic_name (bind_)
.expect_port (true) .expect_port (true)
.ipv6 (false); .ipv6 (ipv6);
ip_resolver_t resolver (resolver_opts); ip_resolver_t resolver (resolver_opts);
ip_addr_t addr;
int rc = resolver.resolve (&addr, name_); ip_addr_t target_addr;
int rc = resolver.resolve (&target_addr, name_);
if (rc != 0) { if (rc != 0) {
return -1; return -1;
} }
if (addr.generic.sa_family != AF_INET) { is_multicast = target_addr.is_multicast ();
// Shouldn't happen uint16_t port = target_addr.port ();
return -1;
if (has_interface) {
// If we have an interface specifier then the target address must be a
// multicast address
if (!is_multicast) {
errno = EINVAL;
return -1;
}
interface_addr.set_port (port);
dest_address = target_addr.ipv4;
bind_address = interface_addr.ipv4;
} 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;
} else {
if (bind_) {
dest_address = target_addr.ipv4;
bind_address = target_addr.ipv4;
} else {
dest_address = target_addr.ipv4;
bind_address = any.ipv4;
}
}
} }
is_multicast = addr.is_multicast ();
dest_address = addr.ipv4;
if (is_multicast) { if (is_multicast) {
multicast = dest_address.sin_addr; multicast = dest_address.sin_addr;
} }
iface.s_addr = htonl (INADDR_ANY);
if (iface.s_addr == INADDR_NONE) {
errno = EINVAL;
return -1;
}
// If a should bind and not a multicast, the dest address
// is actually the bind address
if (bind_ && !is_multicast)
bind_address = dest_address;
else {
bind_address.sin_family = AF_INET;
bind_address.sin_port = dest_address.sin_port;
bind_address.sin_addr.s_addr = htonl (INADDR_ANY);
}
address = name_; address = name_;
return 0; return 0;
......
...@@ -31,18 +31,16 @@ void tearDown () ...@@ -31,18 +31,16 @@ void tearDown ()
{ {
} }
// Test an UDP address resolution. If 'bind_addr_' is not NULL // Test an UDP address resolution. If 'dest_addr_' is NULL assume the
// request a bind address. If 'dest_addr_' is NULL assume the
// resolution is supposed to fail. // resolution is supposed to fail.
static void test_resolve (const char *name_, const char *dest_addr_, static void test_resolve (bool bind_, const char *name_, const char *dest_addr_,
uint16_t expected_port_ = 0, uint16_t expected_port_,
const char *bind_addr_ = NULL, const char *bind_addr_,
bool multicast_ = false) bool multicast_)
{ {
zmq::udp_address_t addr; zmq::udp_address_t addr;
bool bound = bind_addr_ != NULL;
int rc = addr.resolve (name_, bound); int rc = addr.resolve (name_, bind_);
if (dest_addr_ == NULL) { if (dest_addr_ == NULL) {
TEST_ASSERT_EQUAL (-1, rc); TEST_ASSERT_EQUAL (-1, rc);
...@@ -77,55 +75,73 @@ static void test_resolve (const char *name_, const char *dest_addr_, ...@@ -77,55 +75,73 @@ static void test_resolve (const char *name_, const char *dest_addr_,
TEST_ASSERT_EQUAL (htons (expected_port_), bind->sin_port); TEST_ASSERT_EQUAL (htons (expected_port_), bind->sin_port);
} }
static void test_resolve_bind (const char *name_, const char *dest_addr_,
uint16_t expected_port_ = 0,
const char *bind_addr_ = NULL,
bool multicast_ = false)
{
test_resolve (true, name_, dest_addr_, expected_port_, bind_addr_,
multicast_);
}
static void test_resolve_connect (const char *name_, const char *dest_addr_,
uint16_t expected_port_ = 0,
const char *bind_addr_ = NULL,
bool multicast_ = false)
{
test_resolve (false, name_, dest_addr_, expected_port_, bind_addr_,
multicast_);
}
static void test_resolve_ipv4_simple () static void test_resolve_ipv4_simple ()
{ {
test_resolve ("127.0.0.1:5555", "127.0.0.1", 5555); test_resolve_connect ("127.0.0.1:5555", "127.0.0.1", 5555);
} }
static void test_resolve_ipv4_bind () static void test_resolve_ipv4_bind ()
{ {
test_resolve ("127.0.0.1:5555", "127.0.0.1", 5555, "127.0.0.1"); test_resolve_bind ("127.0.0.1:5555", "127.0.0.1", 5555, "127.0.0.1");
} }
static void test_resolve_ipv4_bind_any () static void test_resolve_ipv4_bind_any ()
{ {
test_resolve ("*:*", "0.0.0.0", 0, "0.0.0.0"); test_resolve_bind ("*:*", "0.0.0.0", 0, "0.0.0.0");
} }
static void test_resolve_ipv4_bind_anyport () static void test_resolve_ipv4_bind_anyport ()
{ {
test_resolve ("127.0.0.1:*", "127.0.0.1", 0, "127.0.0.1"); test_resolve_bind ("127.0.0.1:*", "127.0.0.1", 0, "127.0.0.1");
} }
static void test_resolve_ipv4_bind_any_port () static void test_resolve_ipv4_bind_any_port ()
{ {
test_resolve ("*:5555", "0.0.0.0", 5555, "0.0.0.0"); test_resolve_bind ("*:5555", "0.0.0.0", 5555, "0.0.0.0");
} }
static void test_resolve_ipv4_connect_any () static void test_resolve_ipv4_connect_any ()
{ {
// Cannot use wildcard for connection // Cannot use wildcard for connection
test_resolve ("*:5555", NULL); test_resolve_connect ("*:5555", NULL);
} }
static void test_resolve_ipv4_connect_anyport () static void test_resolve_ipv4_connect_anyport ()
{ {
test_resolve ("127.0.0.1:*", NULL); test_resolve_connect ("127.0.0.1:*", NULL);
} }
static void test_resolve_ipv4_connect_port0 () static void test_resolve_ipv4_connect_port0 ()
{ {
test_resolve ("127.0.0.1:0", "127.0.0.1", 0); test_resolve_connect ("127.0.0.1:0", "127.0.0.1", 0);
} }
static void test_resolve_ipv4_bind_mcast () static void test_resolve_ipv4_bind_mcast ()
{ {
test_resolve ("239.0.0.1:1234", "239.0.0.1", 1234, "0.0.0.0", true); test_resolve_bind ("239.0.0.1:1234", "239.0.0.1", 1234, "0.0.0.0", true);
} }
static void test_resolve_ipv4_connect_mcast () static void test_resolve_ipv4_connect_mcast ()
{ {
test_resolve ("239.0.0.1:2222", "239.0.0.1", 2222, NULL, true); test_resolve_connect ("239.0.0.1:2222", "239.0.0.1", 2222, NULL, true);
} }
static void test_resolve_ipv6_simple () static void test_resolve_ipv6_simple ()
...@@ -135,7 +151,41 @@ static void test_resolve_ipv6_simple () ...@@ -135,7 +151,41 @@ static void test_resolve_ipv6_simple ()
} }
// IPv6 not yet supported // IPv6 not yet supported
test_resolve ("::1", NULL); test_resolve_connect ("::1", NULL);
}
static void test_resolve_ipv4_mcast_src_bind ()
{
test_resolve_bind ("127.0.0.1;230.2.8.12:5555", "230.2.8.12", 5555,
"127.0.0.1", true);
}
static void test_resolve_ipv4_mcast_src_bind_any ()
{
test_resolve_bind ("*;230.2.8.12:5555", "230.2.8.12", 5555,
"0.0.0.0", true);
}
static void test_resolve_ipv4_mcast_src_connect ()
{
test_resolve_connect ("8.9.10.11;230.2.8.12:5555", "230.2.8.12", 5555,
"8.9.10.11", true);
}
static void test_resolve_ipv4_mcast_src_connect_any ()
{
test_resolve_connect ("*;230.2.8.12:5555", "230.2.8.12", 5555,
"0.0.0.0", true);
}
static void test_resolve_ipv4_mcast_src_bind_bad ()
{
test_resolve_bind ("127.0.0.1;1.2.3.4:5555", NULL);
}
static void test_resolve_ipv4_mcast_src_connect_bad ()
{
test_resolve_connect ("127.0.0.1;1.2.3.4:5555", NULL);
} }
int main (void) int main (void)
...@@ -156,6 +206,12 @@ int main (void) ...@@ -156,6 +206,12 @@ int main (void)
RUN_TEST (test_resolve_ipv4_bind_mcast); RUN_TEST (test_resolve_ipv4_bind_mcast);
RUN_TEST (test_resolve_ipv4_connect_mcast); RUN_TEST (test_resolve_ipv4_connect_mcast);
RUN_TEST (test_resolve_ipv6_simple); RUN_TEST (test_resolve_ipv6_simple);
RUN_TEST (test_resolve_ipv4_mcast_src_bind);
RUN_TEST (test_resolve_ipv4_mcast_src_bind_any);
RUN_TEST (test_resolve_ipv4_mcast_src_connect);
RUN_TEST (test_resolve_ipv4_mcast_src_connect_any);
RUN_TEST (test_resolve_ipv4_mcast_src_bind_bad);
RUN_TEST (test_resolve_ipv4_mcast_src_connect_bad);
zmq::shutdown_network (); zmq::shutdown_network ();
......
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