Commit 408dea79 authored by Pieter Hintjens's avatar Pieter Hintjens

Merge pull request #993 from klnikita/src_addr_connect

Allow to set up a source address and port for outgoing tcp connections in zmq_connect
parents 69d27b33 acb68070
...@@ -52,6 +52,9 @@ Connecting a socket ...@@ -52,6 +52,9 @@ Connecting a socket
When connecting a socket to a peer address using _zmq_connect()_ with the 'tcp' When connecting a socket to a peer address using _zmq_connect()_ with the 'tcp'
transport, the 'endpoint' shall be interpreted as a 'peer address' followed by transport, the 'endpoint' shall be interpreted as a 'peer address' followed by
a colon and the TCP port number to use. a colon and the TCP port number to use.
You can optionally specify a 'source_endpoint' which will be used as the source
address for your connection; tcp://'source_endpoint';'endpoint', see the
'interface' description above for details.
A 'peer address' may be specified by either of the following: A 'peer address' may be specified by either of the following:
...@@ -84,6 +87,12 @@ assert (rc == 0); ...@@ -84,6 +87,12 @@ assert (rc == 0);
// Connecting using a DNS name // Connecting using a DNS name
rc = zmq_connect(socket, "tcp://server1:5555"); rc = zmq_connect(socket, "tcp://server1:5555");
assert (rc == 0); assert (rc == 0);
// Connecting using a DNS name and bind to eth1
rc = zmq_connect(socket, "tcp://eth1:0;server1:5555");
assert (rc == 0);
// Connecting using a IP address and bind to an IP address
rc = zmq_connect(socket, "tcp://192.168.1.17:5555;192.168.1.1:5555");
assert (rc == 0);
---- ----
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
#include <stdlib.h> #include <stdlib.h>
// On Solaris platform, network interface name can be queried by ioctl. // On Solaris platform, network interface name can be queried by ioctl.
int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_) 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. // TODO: Unused parameter, IPv6 support not implemented for Solaris.
(void) ipv6_; (void) ipv6_;
...@@ -89,7 +89,11 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_) ...@@ -89,7 +89,11 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_)
rc = ioctl (fd, SIOCGLIFADDR, (char*) ifrp); rc = ioctl (fd, SIOCGLIFADDR, (char*) ifrp);
errno_assert (rc != -1); errno_assert (rc != -1);
if (ifrp->lifr_addr.ss_family == AF_INET) { 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; address.ipv4 = *(sockaddr_in*) &ifrp->lifr_addr;
}
found = true; found = true;
break; break;
} }
...@@ -114,7 +118,7 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_) ...@@ -114,7 +118,7 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_)
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <net/if.h> #include <net/if.h>
int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_) int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_)
{ {
// TODO: Unused parameter, IPv6 support not implemented for AIX or HP/UX. // TODO: Unused parameter, IPv6 support not implemented for AIX or HP/UX.
(void) ipv6_; (void) ipv6_;
...@@ -138,8 +142,13 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_) ...@@ -138,8 +142,13 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_)
errno = ENODEV; errno = ENODEV;
return -1; return -1;
} }
if (is_src_) {
memcpy (&source_address.ipv4.sin_addr, &((sockaddr_in*) &ifr.ifr_addr)->sin_addr,
sizeof (in_addr));
} else {
memcpy (&address.ipv4.sin_addr, &((sockaddr_in*) &ifr.ifr_addr)->sin_addr, memcpy (&address.ipv4.sin_addr, &((sockaddr_in*) &ifr.ifr_addr)->sin_addr,
sizeof (in_addr)); sizeof (in_addr));
}
return 0; return 0;
} }
...@@ -153,7 +162,7 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_) ...@@ -153,7 +162,7 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_)
// On these platforms, network interface name can be queried // On these platforms, network interface name can be queried
// using getifaddrs function. // using getifaddrs function.
int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_) int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_)
{ {
// Get the addresses. // Get the addresses.
ifaddrs *ifa = NULL; ifaddrs *ifa = NULL;
...@@ -171,9 +180,15 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_) ...@@ -171,9 +180,15 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_)
int family = ifp->ifa_addr->sa_family; int family = ifp->ifa_addr->sa_family;
if ((family == AF_INET || (ipv6_ && family == AF_INET6)) if ((family == AF_INET || (ipv6_ && family == AF_INET6))
&& !strcmp (nic_, ifp->ifa_name)) { && !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, memcpy (&address, ifp->ifa_addr,
(family == AF_INET) ? sizeof (struct sockaddr_in) (family == AF_INET) ? sizeof (struct sockaddr_in)
: sizeof (struct sockaddr_in6)); : sizeof (struct sockaddr_in6));
}
found = true; found = true;
break; break;
} }
...@@ -193,7 +208,7 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_) ...@@ -193,7 +208,7 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_)
// On other platforms we assume there are no sane interface names. // On other platforms we assume there are no sane interface names.
// This is true especially of Windows. // This is true especially of Windows.
int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_) int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_)
{ {
// All unused parameters. // All unused parameters.
(void) nic_; (void) nic_;
...@@ -205,7 +220,7 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_) ...@@ -205,7 +220,7 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_)
#endif #endif
int zmq::tcp_address_t::resolve_interface (const char *interface_, bool ipv6_) int zmq::tcp_address_t::resolve_interface (const char *interface_, bool ipv6_, bool is_src_)
{ {
// Initialize temporary output pointers with storage address. // Initialize temporary output pointers with storage address.
sockaddr_storage ss; sockaddr_storage ss;
...@@ -233,12 +248,16 @@ int zmq::tcp_address_t::resolve_interface (const char *interface_, bool ipv6_) ...@@ -233,12 +248,16 @@ int zmq::tcp_address_t::resolve_interface (const char *interface_, bool ipv6_)
// "*" resolves to INADDR_ANY or in6addr_any. // "*" resolves to INADDR_ANY or in6addr_any.
if (strcmp (interface_, "*") == 0) { if (strcmp (interface_, "*") == 0) {
zmq_assert (out_addrlen <= sizeof address); zmq_assert (out_addrlen <= sizeof address);
if (is_src_) {
memcpy (&source_address, out_addr, out_addrlen);
} else {
memcpy (&address, out_addr, out_addrlen); memcpy (&address, out_addr, out_addrlen);
}
return 0; return 0;
} }
// Try to resolve the string as a NIC name. // Try to resolve the string as a NIC name.
int rc = resolve_nic_name (interface_, ipv6_); int rc = resolve_nic_name (interface_, ipv6_, is_src_);
if (rc != 0 && errno != ENODEV) if (rc != 0 && errno != ENODEV)
return rc; return rc;
if (rc == 0) if (rc == 0)
...@@ -286,7 +305,11 @@ int zmq::tcp_address_t::resolve_interface (const char *interface_, bool ipv6_) ...@@ -286,7 +305,11 @@ int zmq::tcp_address_t::resolve_interface (const char *interface_, bool ipv6_)
// Use the first result. // Use the first result.
zmq_assert (res != NULL); zmq_assert (res != NULL);
zmq_assert ((size_t) (res->ai_addrlen) <= sizeof (address)); 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); memcpy (&address, res->ai_addr, res->ai_addrlen);
}
// Cleanup getaddrinfo after copying the possibly referenced result. // Cleanup getaddrinfo after copying the possibly referenced result.
freeaddrinfo (res); freeaddrinfo (res);
...@@ -294,7 +317,7 @@ int zmq::tcp_address_t::resolve_interface (const char *interface_, bool ipv6_) ...@@ -294,7 +317,7 @@ int zmq::tcp_address_t::resolve_interface (const char *interface_, bool ipv6_)
return 0; return 0;
} }
int zmq::tcp_address_t::resolve_hostname (const char *hostname_, bool ipv6_) int zmq::tcp_address_t::resolve_hostname (const char *hostname_, bool ipv6_, bool is_src_)
{ {
// Set up the query. // Set up the query.
#if defined ZMQ_HAVE_OPENVMS && defined __ia64 && __INITIAL_POINTER_SIZE == 64 #if defined ZMQ_HAVE_OPENVMS && defined __ia64 && __INITIAL_POINTER_SIZE == 64
...@@ -344,7 +367,11 @@ int zmq::tcp_address_t::resolve_hostname (const char *hostname_, bool ipv6_) ...@@ -344,7 +367,11 @@ int zmq::tcp_address_t::resolve_hostname (const char *hostname_, bool ipv6_)
// Copy first result to output addr with hostname and service. // Copy first result to output addr with hostname and service.
zmq_assert ((size_t) (res->ai_addrlen) <= sizeof (address)); 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); memcpy (&address, res->ai_addr, res->ai_addrlen);
}
freeaddrinfo (res); freeaddrinfo (res);
...@@ -354,6 +381,7 @@ int zmq::tcp_address_t::resolve_hostname (const char *hostname_, bool ipv6_) ...@@ -354,6 +381,7 @@ int zmq::tcp_address_t::resolve_hostname (const char *hostname_, bool ipv6_)
zmq::tcp_address_t::tcp_address_t () zmq::tcp_address_t::tcp_address_t ()
{ {
memset (&address, 0, sizeof (address)); memset (&address, 0, sizeof (address));
memset (&source_address, 0, sizeof (source_address));
} }
zmq::tcp_address_t::tcp_address_t (const sockaddr *sa, socklen_t sa_len) zmq::tcp_address_t::tcp_address_t (const sockaddr *sa, socklen_t sa_len)
...@@ -361,6 +389,7 @@ zmq::tcp_address_t::tcp_address_t (const sockaddr *sa, socklen_t sa_len) ...@@ -361,6 +389,7 @@ zmq::tcp_address_t::tcp_address_t (const sockaddr *sa, socklen_t sa_len)
zmq_assert(sa && sa_len > 0); zmq_assert(sa && sa_len > 0);
memset (&address, 0, sizeof (address)); memset (&address, 0, sizeof (address));
memset (&source_address, 0, sizeof (source_address));
if (sa->sa_family == AF_INET && sa_len >= (socklen_t) sizeof (address.ipv4)) if (sa->sa_family == AF_INET && sa_len >= (socklen_t) sizeof (address.ipv4))
memcpy(&address.ipv4, sa, sizeof (address.ipv4)); memcpy(&address.ipv4, sa, sizeof (address.ipv4));
else else
...@@ -372,14 +401,28 @@ zmq::tcp_address_t::~tcp_address_t () ...@@ -372,14 +401,28 @@ zmq::tcp_address_t::~tcp_address_t ()
{ {
} }
int zmq::tcp_address_t::resolve (const char *name_, bool local_, bool ipv6_) int zmq::tcp_address_t::resolve (const char *name_, bool local_, bool ipv6_, bool is_src_)
{ {
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_);
int rc = resolve(src_name.c_str(), local_, ipv6_, true);
if (rc != 0)
return -1;
name_ = src_delimiter + 1;
_has_src_addr = true;
}
}
// Find the ':' at end that separates address from the port number. // Find the ':' at end that separates address from the port number.
const char *delimiter = strrchr (name_, ':'); const char *delimiter = strrchr (name_, ':');
if (!delimiter) { if (!delimiter) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
// Separate the address/port. // Separate the address/port.
std::string addr_str (name_, delimiter - name_); std::string addr_str (name_, delimiter - name_);
std::string port_str (delimiter + 1); std::string port_str (delimiter + 1);
...@@ -406,17 +449,25 @@ int zmq::tcp_address_t::resolve (const char *name_, bool local_, bool ipv6_) ...@@ -406,17 +449,25 @@ int zmq::tcp_address_t::resolve (const char *name_, bool local_, bool ipv6_)
// Resolve the IP address. // Resolve the IP address.
int rc; int rc;
if (local_) if (local_)
rc = resolve_interface (addr_str.c_str (), ipv6_); rc = resolve_interface (addr_str.c_str (), ipv6_, is_src_);
else else
rc = resolve_hostname (addr_str.c_str (), ipv6_); rc = resolve_hostname (addr_str.c_str (), ipv6_, is_src_);
if (rc != 0) if (rc != 0)
return -1; return -1;
// Set the port into the address structure. // 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);
else
source_address.ipv4.sin_port = htons (port);
} else {
if (address.generic.sa_family == AF_INET6) if (address.generic.sa_family == AF_INET6)
address.ipv6.sin6_port = htons (port); address.ipv6.sin6_port = htons (port);
else else
address.ipv4.sin_port = htons (port); address.ipv4.sin_port = htons (port);
}
return 0; return 0;
} }
...@@ -463,6 +514,24 @@ socklen_t zmq::tcp_address_t::addrlen () const ...@@ -463,6 +514,24 @@ socklen_t zmq::tcp_address_t::addrlen () const
return (socklen_t) sizeof (address.ipv4); return (socklen_t) sizeof (address.ipv4);
} }
const sockaddr *zmq::tcp_address_t::src_addr () const
{
return &source_address.generic;
}
socklen_t zmq::tcp_address_t::src_addrlen () const
{
if (address.generic.sa_family == AF_INET6)
return (socklen_t) sizeof (source_address.ipv6);
else
return (socklen_t) sizeof (source_address.ipv4);
}
bool zmq::tcp_address_t::has_src_addr() const
{
return _has_src_addr;
}
#if defined ZMQ_HAVE_WINDOWS #if defined ZMQ_HAVE_WINDOWS
unsigned short zmq::tcp_address_t::family () const unsigned short zmq::tcp_address_t::family () const
#else #else
......
...@@ -44,7 +44,7 @@ namespace zmq ...@@ -44,7 +44,7 @@ namespace zmq
// strcuture. If 'local' is true, names are resolved as local interface // strcuture. If 'local' is true, names are resolved as local interface
// names. If it is false, names are resolved as remote hostnames. // names. If it is false, names are resolved as remote hostnames.
// If 'ipv6' is true, the name may resolve to IPv6 address. // If 'ipv6' is true, the name may resolve to IPv6 address.
int resolve (const char *name_, bool local_, bool ipv6_); int resolve (const char *name_, bool local_, bool ipv6_, bool is_src_ = false);
// The opposite to resolve() // The opposite to resolve()
virtual int to_string (std::string &addr_); virtual int to_string (std::string &addr_);
...@@ -57,16 +57,27 @@ namespace zmq ...@@ -57,16 +57,27 @@ namespace zmq
const sockaddr *addr () const; const sockaddr *addr () const;
socklen_t addrlen () const; socklen_t addrlen () const;
const sockaddr *src_addr () const;
socklen_t src_addrlen () const;
bool has_src_addr () const;
protected: protected:
int resolve_nic_name (const char *nic_, bool ipv6_); int resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_ = false);
int resolve_interface (const char *interface_, bool ipv6_); int resolve_interface (const char *interface_, bool ipv6_, bool is_src_ = false);
int resolve_hostname (const char *hostname_, bool ipv6_); int resolve_hostname (const char *hostname_, bool ipv6_, bool is_src_ = false);
union { union {
sockaddr generic; sockaddr generic;
sockaddr_in ipv4; sockaddr_in ipv4;
sockaddr_in6 ipv6; sockaddr_in6 ipv6;
} address; } address;
union {
sockaddr generic;
sockaddr_in ipv4;
sockaddr_in6 ipv6;
} source_address;
bool _has_src_addr;
}; };
class tcp_address_mask_t : public tcp_address_t class tcp_address_mask_t : public tcp_address_t
......
...@@ -261,6 +261,15 @@ int zmq::tcp_connecter_t::open () ...@@ -261,6 +261,15 @@ int zmq::tcp_connecter_t::open ()
if (options.tos != 0) if (options.tos != 0)
set_ip_type_of_service (s, options.tos); set_ip_type_of_service (s, options.tos);
// Set a source address for conversations
if (addr->resolved.tcp_addr->has_src_addr ()) {
rc = ::bind (s, addr->resolved.tcp_addr->src_addr (), addr->resolved.tcp_addr->src_addrlen ());
if (rc == -1) {
return -1;
}
}
// Connect to the remote peer. // Connect to the remote peer.
rc = ::connect ( rc = ::connect (
s, addr->resolved.tcp_addr->addr (), s, addr->resolved.tcp_addr->addr (),
......
...@@ -14,6 +14,7 @@ noinst_PROGRAMS = test_system \ ...@@ -14,6 +14,7 @@ noinst_PROGRAMS = test_system \
test_invalid_rep \ test_invalid_rep \
test_msg_flags \ test_msg_flags \
test_connect_resolve \ test_connect_resolve \
test_bind_src_address \
test_immediate \ test_immediate \
test_last_endpoint \ test_last_endpoint \
test_term_endpoint \ test_term_endpoint \
...@@ -114,6 +115,7 @@ test_many_sockets_SOURCES = test_many_sockets.cpp ...@@ -114,6 +115,7 @@ test_many_sockets_SOURCES = test_many_sockets.cpp
test_ipc_wildcard_SOURCES = test_ipc_wildcard.cpp test_ipc_wildcard_SOURCES = test_ipc_wildcard.cpp
test_diffserv_SOURCES = test_diffserv.cpp test_diffserv_SOURCES = test_diffserv.cpp
test_connect_rid_SOURCES = test_connect_rid.cpp test_connect_rid_SOURCES = test_connect_rid.cpp
test_bind_src_address_SOURCES = test_bind_src_address.cpp
if !ON_MINGW if !ON_MINGW
test_shutdown_stress_SOURCES = test_shutdown_stress.cpp test_shutdown_stress_SOURCES = test_shutdown_stress.cpp
test_pair_ipc_SOURCES = test_pair_ipc.cpp testutil.hpp test_pair_ipc_SOURCES = test_pair_ipc.cpp testutil.hpp
......
/*
Copyright (c) 2007-2014 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 "testutil.hpp"
int main (void)
{
setup_test_environment();
void *ctx = zmq_ctx_new ();
assert (ctx);
void *sock = zmq_socket (ctx, ZMQ_PUB);
assert (sock);
int rc = zmq_connect (sock, "tcp://127.0.0.1:0;localhost:1234");
assert (rc == 0);
rc = zmq_connect (sock, "tcp://localhost:5555;localhost:1235");
assert (rc == 0);
rc = zmq_connect (sock, "tcp://lo:5555;localhost:1235");
assert (rc == 0);
rc = zmq_close (sock);
assert (rc == 0);
rc = zmq_ctx_term (ctx);
assert (rc == 0);
return 0;
}
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