aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2018-08-19 08:07:02 -0700
committerGarrett D'Amore <garrett@damore.org>2018-08-27 08:00:23 -0700
commit83b7a9afec7b3659974c614cea69fa3abb904d24 (patch)
tree7277bda8deb32c91614f236fb942a4c6cfbbe55b /src
parent1c3350f6f4a738815c39a67dc0ba1a953a1b9f03 (diff)
downloadnng-83b7a9afec7b3659974c614cea69fa3abb904d24.tar.gz
nng-83b7a9afec7b3659974c614cea69fa3abb904d24.tar.bz2
nng-83b7a9afec7b3659974c614cea69fa3abb904d24.zip
fixes #608 Add TCP support to specify local network interface
This also fixes a leaked TCP connection on a failure path, which we noticed while working this change.
Diffstat (limited to 'src')
-rw-r--r--src/core/platform.h9
-rw-r--r--src/platform/posix/posix_tcp.h8
-rw-r--r--src/platform/posix/posix_tcpdial.c46
-rw-r--r--src/platform/windows/win_tcp.h12
-rw-r--r--src/platform/windows/win_tcpdial.c73
-rw-r--r--src/transport/tcp/tcp.c58
6 files changed, 179 insertions, 27 deletions
diff --git a/src/core/platform.h b/src/core/platform.h
index 70f4e9ef..48f1e1a7 100644
--- a/src/core/platform.h
+++ b/src/core/platform.h
@@ -265,6 +265,15 @@ extern void nni_tcp_dialer_fini(nni_tcp_dialer *);
// connection will be aborted.
extern void nni_tcp_dialer_close(nni_tcp_dialer *);
+// nni_tcp_dialer_set_src_addr sets the source address to use for outgoing
+// connections. Only the IP (or IPv6) address may be specified; the port
+// must be zero. This must be called before calling nni_tcp_dialer_dial.
+// The source address must be associated with one of the addresses on the
+// local system -- this is not checked until bind() is called just prior to
+// the connect() call. Likewise the address family must be the same as the
+// address used when dialing, or errors will occur.
+extern int nni_tcp_dialer_set_src_addr(nni_tcp_dialer *, const nng_sockaddr *);
+
// nni_tcp_dialer_dial attempts to create an outgoing connection,
// asynchronously, to the address specified. On success, the first (and only)
// output will be an nni_tcp_conn * associated with the remote server.
diff --git a/src/platform/posix/posix_tcp.h b/src/platform/posix/posix_tcp.h
index aefce7f7..633a63b5 100644
--- a/src/platform/posix/posix_tcp.h
+++ b/src/platform/posix/posix_tcp.h
@@ -25,9 +25,11 @@ struct nni_tcp_conn {
};
struct nni_tcp_dialer {
- nni_list connq; // pending connections
- bool closed;
- nni_mtx mtx;
+ nni_list connq; // pending connections
+ bool closed;
+ struct sockaddr_storage src;
+ size_t srclen;
+ nni_mtx mtx;
};
struct nni_tcp_listener {
diff --git a/src/platform/posix/posix_tcpdial.c b/src/platform/posix/posix_tcpdial.c
index ab3f3545..918ee9ba 100644
--- a/src/platform/posix/posix_tcpdial.c
+++ b/src/platform/posix/posix_tcpdial.c
@@ -149,6 +149,46 @@ tcp_dialer_cb(nni_posix_pfd *pfd, int ev, void *arg)
nni_aio_finish(aio, 0, 0);
}
+int
+nni_tcp_dialer_set_src_addr(nni_tcp_dialer *d, const nni_sockaddr *sa)
+{
+ struct sockaddr_storage ss;
+ struct sockaddr_in * sin;
+ struct sockaddr_in6 * sin6;
+ size_t sslen;
+
+ if ((sslen = nni_posix_nn2sockaddr(&ss, sa)) == 0) {
+ return (NNG_EADDRINVAL);
+ }
+ // Ensure we are either IPv4 or IPv6, and port is not set. (We
+ // do not allow binding to a specific port.)
+ switch (ss.ss_family) {
+ case AF_INET:
+ sin = (void *) &ss;
+ if (sin->sin_port != 0) {
+ return (NNG_EADDRINVAL);
+ }
+ break;
+ case AF_INET6:
+ sin6 = (void *) &ss;
+ if (sin6->sin6_port != 0) {
+ return (NNG_EADDRINVAL);
+ }
+ break;
+ default:
+ return (NNG_EADDRINVAL);
+ }
+ nni_mtx_lock(&d->mtx);
+ if (d->closed) {
+ nni_mtx_unlock(&d->mtx);
+ return (NNG_ECLOSED);
+ }
+ d->src = ss;
+ d->srclen = sslen;
+ nni_mtx_unlock(&d->mtx);
+ return (0);
+}
+
// We don't give local address binding support. Outbound dialers always
// get an ephemeral port.
void
@@ -196,6 +236,12 @@ nni_tcp_dialer_dial(nni_tcp_dialer *d, const nni_sockaddr *sa, nni_aio *aio)
rv = NNG_ECLOSED;
goto error;
}
+ if (d->srclen != 0) {
+ if ((rv = bind(fd, (void *) &d->src, d->srclen)) != 0) {
+ rv = nni_plat_errno(errno);
+ goto error;
+ }
+ }
if ((rv = nni_aio_schedule(aio, tcp_dialer_cancel, d)) != 0) {
goto error;
}
diff --git a/src/platform/windows/win_tcp.h b/src/platform/windows/win_tcp.h
index 7025af81..9c7a4d0e 100644
--- a/src/platform/windows/win_tcp.h
+++ b/src/platform/windows/win_tcp.h
@@ -39,11 +39,13 @@ struct nni_tcp_conn {
};
struct nni_tcp_dialer {
- LPFN_CONNECTEX connectex; // looked up name via ioctl
- nni_list aios; // in flight connections
- bool closed;
- nni_mtx mtx;
- nni_reap_item reap;
+ LPFN_CONNECTEX connectex; // looked up name via ioctl
+ nni_list aios; // in flight connections
+ bool closed;
+ SOCKADDR_STORAGE src;
+ size_t srclen;
+ nni_mtx mtx;
+ nni_reap_item reap;
};
struct nni_tcp_listener {
diff --git a/src/platform/windows/win_tcpdial.c b/src/platform/windows/win_tcpdial.c
index 99308ceb..1225b560 100644
--- a/src/platform/windows/win_tcpdial.c
+++ b/src/platform/windows/win_tcpdial.c
@@ -145,6 +145,46 @@ tcp_dial_cb(nni_win_io *io, int rv, size_t cnt)
}
}
+int
+nni_tcp_dialer_set_src_addr(nni_tcp_dialer *d, const nni_sockaddr *sa)
+{
+ SOCKADDR_STORAGE ss;
+ struct sockaddr_in * sin;
+ struct sockaddr_in6 *sin6;
+ size_t sslen;
+
+ if ((sslen = nni_win_nn2sockaddr(&ss, sa)) == 0) {
+ return (NNG_EADDRINVAL);
+ }
+ // Ensure we are either IPv4 or IPv6, and port is not set. (We
+ // do not allow binding to a specific port.)
+ switch (ss.ss_family) {
+ case AF_INET:
+ sin = (void *) &ss;
+ if (sin->sin_port != 0) {
+ return (NNG_EADDRINVAL);
+ }
+ break;
+ case AF_INET6:
+ sin6 = (void *) &ss;
+ if (sin6->sin6_port != 0) {
+ return (NNG_EADDRINVAL);
+ }
+ break;
+ default:
+ return (NNG_EADDRINVAL);
+ }
+ nni_mtx_lock(&d->mtx);
+ if (d->closed) {
+ nni_mtx_unlock(&d->mtx);
+ return (NNG_ECLOSED);
+ }
+ d->src = ss;
+ d->srclen = sslen;
+ nni_mtx_unlock(&d->mtx);
+ return (0);
+}
+
void
nni_tcp_dialer_dial(nni_tcp_dialer *d, const nni_sockaddr *sa, nni_aio *aio)
{
@@ -176,19 +216,7 @@ nni_tcp_dialer_dial(nni_tcp_dialer *d, const nni_sockaddr *sa, nni_aio *aio)
c->peername = ss;
- // Windows ConnectEx requires the socket to be bound
- // first. We just bind to an ephemeral address in the
- // same family.
- ZeroMemory(&c->sockname, sizeof(c->sockname));
- c->sockname.ss_family = ss.ss_family;
- if (bind(s, (SOCKADDR *) &c->sockname, len) < 0) {
- rv = nni_win_error(GetLastError());
- nni_tcp_conn_fini(c);
- nni_aio_finish_error(aio, rv);
- return;
- }
if ((rv = nni_win_io_init(&c->conn_io, tcp_dial_cb, c)) != 0) {
- nni_tcp_conn_fini(c);
nni_aio_finish_error(aio, rv);
return;
}
@@ -196,13 +224,34 @@ nni_tcp_dialer_dial(nni_tcp_dialer *d, const nni_sockaddr *sa, nni_aio *aio)
nni_mtx_lock(&d->mtx);
if (d->closed) {
nni_mtx_unlock(&d->mtx);
+ nni_tcp_conn_fini(c);
nni_aio_finish_error(aio, NNG_ECLOSED);
return;
}
+
+ // Windows ConnectEx requires the socket to be bound
+ // first. We just bind to an ephemeral address in the
+ // same family, unless a different default was requested.
+ if (d->srclen != 0) {
+ len = (int) d->srclen;
+ memcpy(&c->sockname, &d->src, len);
+ } else {
+ ZeroMemory(&c->sockname, sizeof(c->sockname));
+ c->sockname.ss_family = ss.ss_family;
+ }
+ if (bind(s, (SOCKADDR *) &c->sockname, len) != 0) {
+ rv = nni_win_error(GetLastError());
+ nni_mtx_unlock(&d->mtx);
+ nni_tcp_conn_fini(c);
+ nni_aio_finish_error(aio, rv);
+ return;
+ }
+
c->dialer = d;
nni_aio_set_prov_extra(aio, 0, c);
if ((rv = nni_aio_schedule(aio, tcp_dial_cancel, d)) != 0) {
nni_mtx_unlock(&d->mtx);
+ nni_tcp_conn_fini(c);
nni_aio_finish_error(aio, rv);
return;
}
diff --git a/src/transport/tcp/tcp.c b/src/transport/tcp/tcp.c
index 3a4e018e..017b4ccf 100644
--- a/src/transport/tcp/tcp.c
+++ b/src/transport/tcp/tcp.c
@@ -61,6 +61,8 @@ struct tcptran_ep {
bool keepalive;
bool fini;
nni_url * url;
+ const char * host; // for dialers
+ nng_sockaddr src;
nng_sockaddr sa;
nng_sockaddr bsa;
nni_list pipes;
@@ -750,9 +752,11 @@ tcptran_ep_close(void *arg)
static int
tcptran_ep_init_dialer(void **dp, nni_url *url, nni_sock *sock)
{
- tcptran_ep *ep;
- int rv;
- uint16_t af;
+ tcptran_ep * ep;
+ int rv;
+ uint16_t af;
+ char * host;
+ nng_sockaddr srcsa;
if (strcmp(url->u_scheme, "tcp") == 0) {
af = NNG_AF_UNSPEC;
@@ -785,11 +789,52 @@ tcptran_ep_init_dialer(void **dp, nni_url *url, nni_sock *sock)
ep->keepalive = false;
ep->url = url;
- if ((rv = nni_tcp_dialer_init(&ep->dialer)) != 0) {
+ // Detect an embedded local interface name in the hostname. This
+ // syntax is only valid with dialers.
+ if ((host = strchr(url->u_hostname, ';')) != NULL) {
+ size_t len;
+ char * src = NULL;
+ nni_aio *aio;
+ len = (uintptr_t) host - (uintptr_t) url->u_hostname;
+ host++;
+ if ((len < 2) || (strlen(host) == 0)) {
+ tcptran_ep_fini(ep);
+ return (NNG_EADDRINVAL);
+ }
+ if ((src = nni_alloc(len + 1)) == NULL) {
+ tcptran_ep_fini(ep);
+ return (NNG_ENOMEM);
+ }
+ memcpy(src, url->u_hostname, len);
+ src[len] = 0;
+
+ if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) {
+ tcptran_ep_fini(ep);
+ nni_strfree(src);
+ return (rv);
+ }
+ nni_aio_set_input(aio, 0, &srcsa);
+ nni_tcp_resolv(src, 0, af, 1, aio);
+ nni_aio_wait(aio);
+ rv = nni_aio_result(aio);
+ nni_aio_fini(aio);
+ nni_strfree(src);
+ ep->host = host;
+ } else {
+ srcsa.s_family = NNG_AF_UNSPEC;
+ ep->host = url->u_hostname;
+ rv = 0;
+ }
+
+ if ((rv != 0) || ((rv = nni_tcp_dialer_init(&ep->dialer)) != 0)) {
+ tcptran_ep_fini(ep);
+ return (rv);
+ }
+ if ((srcsa.s_family != NNG_AF_UNSPEC) &&
+ ((rv = nni_tcp_dialer_set_src_addr(ep->dialer, &srcsa)) != 0)) {
tcptran_ep_fini(ep);
return (rv);
}
-
*dp = ep;
return (0);
}
@@ -897,8 +942,7 @@ tcptran_ep_connect(void *arg, nni_aio *aio)
p->useraio = aio;
// Start the name resolution before we try connecting.
nni_aio_set_input(p->rslvaio, 0, &p->sa);
- nni_tcp_resolv(
- ep->url->u_hostname, ep->url->u_port, ep->af, 0, p->rslvaio);
+ nni_tcp_resolv(ep->host, ep->url->u_port, ep->af, 0, p->rslvaio);
nni_mtx_unlock(&ep->mtx);
}