diff options
| author | Garrett D'Amore <garrett@damore.org> | 2018-08-19 08:07:02 -0700 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2018-08-27 08:00:23 -0700 |
| commit | 83b7a9afec7b3659974c614cea69fa3abb904d24 (patch) | |
| tree | 7277bda8deb32c91614f236fb942a4c6cfbbe55b | |
| parent | 1c3350f6f4a738815c39a67dc0ba1a953a1b9f03 (diff) | |
| download | nng-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.
| -rw-r--r-- | src/core/platform.h | 9 | ||||
| -rw-r--r-- | src/platform/posix/posix_tcp.h | 8 | ||||
| -rw-r--r-- | src/platform/posix/posix_tcpdial.c | 46 | ||||
| -rw-r--r-- | src/platform/windows/win_tcp.h | 12 | ||||
| -rw-r--r-- | src/platform/windows/win_tcpdial.c | 73 | ||||
| -rw-r--r-- | src/transport/tcp/tcp.c | 58 | ||||
| -rw-r--r-- | tests/tcp.c | 37 |
7 files changed, 215 insertions, 28 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); } diff --git a/tests/tcp.c b/tests/tcp.c index 0018fce7..d13cc83d 100644 --- a/tests/tcp.c +++ b/tests/tcp.c @@ -59,7 +59,6 @@ check_props_v4(nng_msg *msg) } TestMain("TCP Transport", { - trantest_test_extended("tcp://127.0.0.1:%u", check_props_v4); Convey("We cannot connect to wild cards", { @@ -109,6 +108,42 @@ TestMain("TCP Transport", { nng_strfree(addr); }); + Convey("We can use local interface to connet", { + nng_socket s1; + nng_socket s2; + char addr[NNG_MAXADDRLEN]; + + So(nng_pair_open(&s1) == 0); + So(nng_pair_open(&s2) == 0); + Reset({ + nng_close(s2); + nng_close(s1); + }); + trantest_next_address(addr, "tcp://127.0.0.1:%u"); + So(nng_listen(s1, addr, NULL, 0) == 0); + // reset port back one + trantest_prev_address(addr, "tcp://127.0.0.1;127.0.0.1:%u"); + So(nng_dial(s2, addr, NULL, 0) == 0); + }); + + Convey("Botched local interfaces fail resonably", { + nng_socket s1; + + So(nng_pair_open(&s1) == 0); + Reset({ nng_close(s1); }); + So(nng_dial(s1, "tcp://1x.2;127.0.0.1:80", NULL, 0) == + NNG_EADDRINVAL); + }); + + Convey("Can't specify address that isn't ours", { + nng_socket s1; + + So(nng_pair_open(&s1) == 0); + Reset({ nng_close(s1); }); + So(nng_dial(s1, "tcp://8.8.8.8;127.0.0.1:80", NULL, 0) == + NNG_EADDRINVAL); + }); + Convey("Malformed TCP addresses do not panic", { nng_socket s1; |
