Skip to content

Commit

Permalink
Problem: UDP transport doesn't let the user specify the local bind ad…
Browse files Browse the repository at this point in the history
…dress

for multicast

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

Fixes zeromq#2212
  • Loading branch information
simias committed May 4, 2018
1 parent 524affc commit 3dc3ab4
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 65 deletions.
70 changes: 49 additions & 21 deletions src/ip_resolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,52 @@ bool zmq::ip_addr_t::is_multicast () const
}
}

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 () :
bindable_wanted (false),
nic_name_allowed (false),
Expand Down Expand Up @@ -197,25 +243,8 @@ int zmq::ip_resolver_t::resolve (ip_addr_t *ip_addr_, const char *name_)

if (options.bindable () && addr == "*") {
// Return an ANY address
*ip_addr_ = ip_addr_t::any (options.ipv6 () ? AF_INET6 : AF_INET);
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 ()) {
Expand All @@ -242,11 +271,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
// we'd still have to do it manually when the address is resolved by
// 'resolve_nic_name'
ip_addr_->set_port (port);

if (ip_addr_->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);
Expand Down
4 changes: 4 additions & 0 deletions src/ip_resolver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ union ip_addr_t

int family () 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
Expand Down
107 changes: 82 additions & 25 deletions src/udp_address.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,50 +58,107 @@ zmq::udp_address_t::~udp_address_t ()

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;

resolver_opts.bindable (bind_)
.allow_dns (!bind_)
.allow_nic_name (bind_)
.expect_port (true)
.ipv6 (false);
.ipv6 (ipv6);

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) {
return -1;
}

if (addr.generic.sa_family != AF_INET) {
// Shouldn't happen
return -1;
is_multicast = target_addr.is_multicast ();
uint16_t port = target_addr.port ();
interface_addr.set_port (port);

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;
}

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) {
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_;

return 0;
Expand Down
Loading

0 comments on commit 3dc3ab4

Please sign in to comment.