aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2024-12-30 11:45:36 -0800
committerGarrett D'Amore <garrett@damore.org>2024-12-30 12:12:07 -0800
commit9c6b2929d8019dcc11935550bc3520ae39c2964e (patch)
treec8311a91c02e7f78dbc8c26d8456b373ef33b5f4
parent60c9c1c4054a5dbb3c1cad2068e1a793789618ff (diff)
downloadnng-9c6b2929d8019dcc11935550bc3520ae39c2964e.tar.gz
nng-9c6b2929d8019dcc11935550bc3520ae39c2964e.tar.bz2
nng-9c6b2929d8019dcc11935550bc3520ae39c2964e.zip
fixes #863 socket activation: for TCP and IPC (POSIX only)
This introduces a new option "NNG_OPT_LISTEN_FD", understood by TCP, TLS, and (on POSIX systems) IPC. This option is used to pass a file descriptor or handle (Windows) that is already listening (ready for ACCEPT to be called). For TCP and TLS, the socket must be of type AF_INET or AF_INET6, and for IPC it must be of type AF_UNIX.
-rw-r--r--docs/man/nng_tcp_options.5.adoc10
-rw-r--r--docs/ref/tran/ipc.md1
-rw-r--r--include/nng/nng.h7
-rw-r--r--src/platform/ipc_stream_test.c169
-rw-r--r--src/platform/posix/posix_debug.c3
-rw-r--r--src/platform/posix/posix_ipclisten.c61
-rw-r--r--src/platform/posix/posix_tcplisten.c66
-rw-r--r--src/platform/tcp_stream_test.c251
-rw-r--r--src/platform/windows/win_tcplisten.c76
9 files changed, 644 insertions, 0 deletions
diff --git a/docs/man/nng_tcp_options.5.adoc b/docs/man/nng_tcp_options.5.adoc
index ad5fabfd..37228b82 100644
--- a/docs/man/nng_tcp_options.5.adoc
+++ b/docs/man/nng_tcp_options.5.adoc
@@ -101,6 +101,16 @@ system.
While the value is of type `int`, it will be a legal TCP port number, that
is a value between 1 and 65535, inclusive.
+[[NNG_OPT_LISTEN_FD]]
+((`NNG_OPT_LISTEN_FD`)):
+(`int`)
+This option is a write-only option for listeners, that can be used on a
+listener that has not yet been started. It is used to pass in a file descriptor
+(or for Windows, a `SOCKET`) that has already been set up for listening.
+The intended use for this is with configurations such as socket activation,
+where some system service pre-establishes the listening sockets before starting
+the application.
+
=== Inherited Options
Generally, the following option values are also available for TCP objects,
diff --git a/docs/ref/tran/ipc.md b/docs/ref/tran/ipc.md
index ec0a2700..3d52f106 100644
--- a/docs/ref/tran/ipc.md
+++ b/docs/ref/tran/ipc.md
@@ -83,6 +83,7 @@ where supported by the underlying platform.
- [`NNG_OPT_PEER_PID`][NNG_OPT_PEER_PID]
- [`NNG_OPT_PEER_UID`][NNG_OPT_PEER_UID]
- [`NNG_OPT_PEER_ZONEID`][NNG_OPT_PEER_ZONEID]
+- [`NNG_OPT_LISTEN_FD`][NNG_OPT_LISTEN_FD]
### Other Configuration Parameters
diff --git a/include/nng/nng.h b/include/nng/nng.h
index d7deefd5..61c1171f 100644
--- a/include/nng/nng.h
+++ b/include/nng/nng.h
@@ -894,6 +894,13 @@ NNG_DECL nng_listener nng_pipe_listener(nng_pipe);
// are not supported.
#define NNG_OPT_SOCKET_FD "socket:fd"
+// NNG_OPT_LISTEN_FD is a write-only integer property that can be used
+// with some transports to pass a file descriptor that is already listening
+// for inbound connections. The transport will then call accept on it.
+// The file descriptor has to be of a suitable type. The intended use
+// for this is socket activation. Not all transports support this.
+#define NNG_OPT_LISTEN_FD "listen-fd"
+
// XXX: TBD: priorities, ipv4only
// Statistics. These are for informational purposes only, and subject
diff --git a/src/platform/ipc_stream_test.c b/src/platform/ipc_stream_test.c
index caf7861a..77c307bc 100644
--- a/src/platform/ipc_stream_test.c
+++ b/src/platform/ipc_stream_test.c
@@ -97,6 +97,8 @@ test_ipc_stream(void)
nng_stream_listener_close(l);
nng_stream_dialer_close(d);
+ nng_stream_listener_stop(l);
+ nng_stream_dialer_stop(d);
nng_stream_listener_free(l);
nng_stream_dialer_free(d);
nng_stream_close(c1);
@@ -105,7 +107,174 @@ test_ipc_stream(void)
nng_stream_free(c2);
}
+void
+test_ipc_listen_activation(void)
+{
+#if defined(NNG_PLATFORM_POSIX)
+ nng_stream_listener *l1;
+ nng_stream_listener *l2;
+ char *addr;
+ int fd;
+ nng_aio *aio1;
+ nng_aio *aio2;
+ nng_stream_dialer *d;
+ nng_stream *c1, *c2;
+
+ NUTS_ADDR(addr, "ipc");
+ NUTS_PASS(nng_aio_alloc(&aio1, NULL, NULL));
+ NUTS_PASS(nng_aio_alloc(&aio2, NULL, NULL));
+
+ nng_aio_set_timeout(aio1, 2000);
+ nng_aio_set_timeout(aio2, 2000);
+
+ NUTS_PASS(nng_stream_listener_alloc(&l1, addr));
+ NUTS_PASS(nng_stream_listener_listen(l1));
+
+ NUTS_PASS(nng_stream_dialer_alloc(&d, addr));
+
+ NUTS_PASS(nng_stream_listener_get_int(l1, NNG_OPT_LISTEN_FD, &fd));
+ NUTS_PASS(nng_stream_listener_alloc(&l2, addr));
+ NUTS_PASS(nng_stream_listener_set_int(l2, NNG_OPT_LISTEN_FD, fd));
+ nng_stream_dialer_dial(d, aio2);
+ nng_stream_listener_accept(l2, aio1);
+
+ nng_aio_wait(aio1);
+ nng_aio_wait(aio2);
+
+ NUTS_PASS(nng_aio_result(aio1));
+ NUTS_PASS(nng_aio_result(aio2));
+
+ c1 = nng_aio_get_output(aio1, 0);
+ c2 = nng_aio_get_output(aio2, 0);
+
+ char buf1[4];
+ char buf2[4];
+ nng_iov iov1;
+ nng_iov iov2;
+
+ iov1.iov_buf = buf1;
+ iov1.iov_len = sizeof(buf1);
+
+ iov2.iov_buf = buf2;
+ iov2.iov_len = sizeof(buf2);
+
+ nng_aio_set_iov(aio1, 1, &iov1);
+ nng_aio_set_iov(aio2, 1, &iov2);
+
+ snprintf(buf1, sizeof(buf1), "abc");
+
+ nng_stream_send(c1, aio1);
+ nng_stream_recv(c2, aio2);
+
+ nng_aio_wait(aio1);
+ nng_aio_wait(aio2);
+
+ NUTS_PASS(nng_aio_result(aio1));
+ NUTS_PASS(nng_aio_result(aio2));
+
+ NUTS_MATCH(buf1, buf2);
+
+ nng_stream_listener_free(l1);
+ nng_stream_listener_free(l2);
+ nng_stream_free(c1);
+ nng_stream_free(c2);
+ nng_aio_free(aio1);
+ nng_aio_free(aio2);
+#else
+ NUTS_SKIP("Not POSIX");
+#endif
+}
+
+void
+test_ipc_listen_activation_busy(void)
+{
+#if defined(NNG_PLATFORM_POSIX)
+ nng_stream_listener *l1;
+ int fd;
+ char *addr;
+
+ NUTS_ADDR(addr, "ipc");
+ NUTS_PASS(nng_stream_listener_alloc(&l1, "tcp://"));
+ NUTS_PASS(nng_stream_listener_listen(l1));
+ NUTS_PASS(nng_stream_listener_get_int(l1, NNG_OPT_LISTEN_FD, &fd));
+ NUTS_FAIL(
+ nng_stream_listener_set_int(l1, NNG_OPT_LISTEN_FD, fd), NNG_EBUSY);
+ nng_stream_listener_free(l1);
+#else
+ NUTS_SKIP("Not POSIX");
+#endif
+}
+
+void
+test_ipc_listen_activation_closed(void)
+{
+#if defined(NNG_PLATFORM_POSIX)
+ nng_stream_listener *l1;
+ nng_stream_listener *l2;
+ int fd;
+ char *addr;
+
+ NUTS_ADDR(addr, "ipc");
+ NUTS_PASS(nng_stream_listener_alloc(&l1, "ipc:///"));
+ NUTS_PASS(nng_stream_listener_alloc(&l2, addr));
+ NUTS_PASS(nng_stream_listener_listen(l2));
+ NUTS_PASS(nng_stream_listener_get_int(l2, NNG_OPT_LISTEN_FD, &fd));
+ nng_stream_listener_close(l1);
+ NUTS_FAIL(nng_stream_listener_set_int(l1, NNG_OPT_LISTEN_FD, fd),
+ NNG_ECLOSED);
+ nng_stream_listener_free(l1);
+ nng_stream_listener_free(l2);
+#else
+ NUTS_SKIP("Not POSIX");
+#endif
+}
+
+void
+test_ipc_listen_activation_wrong_family(void)
+{
+#if !defined(NNG_PLATFORM_POSIX) || !defined(NNG_TRANSPORT_TCP)
+ NUTS_SKIP("Not POSIX or no TCP");
+#else
+ nng_stream_listener *l1;
+ nng_stream_listener *l2;
+ int fd;
+ char *addr;
+
+ NUTS_ADDR(addr, "tcp");
+ NUTS_PASS(nng_stream_listener_alloc(&l1, "ipc:///"));
+ NUTS_PASS(nng_stream_listener_alloc(&l2, addr));
+ NUTS_PASS(nng_stream_listener_listen(l2));
+ NUTS_PASS(nng_stream_listener_get_int(l2, NNG_OPT_LISTEN_FD, &fd));
+ NUTS_FAIL(nng_stream_listener_set_int(l1, NNG_OPT_LISTEN_FD, fd),
+ NNG_EADDRINVAL);
+ nng_stream_listener_free(l1);
+ nng_stream_listener_free(l2);
+#endif
+}
+
+void
+test_ipc_listen_activation_bogus_fd(void)
+{
+#if defined(NNG_PLATFORM_POSIX)
+ nng_stream_listener *l1;
+
+ NUTS_PASS(nng_stream_listener_alloc(&l1, "ipc:///"));
+ NUTS_FAIL(nng_stream_listener_set_int(l1, NNG_OPT_LISTEN_FD, 12345),
+ NNG_ECLOSED);
+ nng_stream_listener_free(l1);
+#else
+ NUTS_SKIP("Not POSIX");
+#endif
+}
+
NUTS_TESTS = {
{ "ipc stream", test_ipc_stream },
+ { "ipc socket activation", test_ipc_listen_activation },
+ { "ipc socket activation busy", test_ipc_listen_activation_busy },
+ { "pc socket activation closed", test_ipc_listen_activation_closed },
+ { "ipc socket activation wrong family",
+ test_ipc_listen_activation_wrong_family },
+ { "ipc socket activation bogus fd",
+ test_ipc_listen_activation_bogus_fd },
{ NULL, NULL },
};
diff --git a/src/platform/posix/posix_debug.c b/src/platform/posix/posix_debug.c
index 912000cc..3c0a6604 100644
--- a/src/platform/posix/posix_debug.c
+++ b/src/platform/posix/posix_debug.c
@@ -90,6 +90,9 @@ static struct {
{ ENFILE, NNG_ENOFILES },
{ EMFILE, NNG_ENOFILES },
{ EEXIST, NNG_EEXIST },
+#ifdef ENOTSOCK
+ { ENOTSOCK, NNG_EINVAL },
+#endif
// must be last
{ 0, 0 },
// clang-format on
diff --git a/src/platform/posix/posix_ipclisten.c b/src/platform/posix/posix_ipclisten.c
index aa6ffda8..d13ac063 100644
--- a/src/platform/posix/posix_ipclisten.c
+++ b/src/platform/posix/posix_ipclisten.c
@@ -252,6 +252,60 @@ ipc_listener_set_perms(void *arg, const void *buf, size_t sz, nni_type t)
return (0);
}
+static int
+ipc_listener_set_listen_fd(void *arg, const void *buf, size_t sz, nni_type t)
+{
+ ipc_listener *l = arg;
+ int fd;
+ struct sockaddr_storage ss;
+ socklen_t len = sizeof(ss);
+ int rv;
+
+ if ((rv = nni_copyin_int(&fd, buf, sz, 0, NNI_MAXINT, t)) != 0) {
+ return (rv);
+ }
+
+ if (getsockname(fd, (void *) &ss, &len) != 0) {
+ return (nni_plat_errno(errno));
+ }
+
+ if (((nni_posix_sockaddr2nn(&l->sa, &ss, len)) != 0) ||
+ (ss.ss_family != AF_UNIX)) {
+ return (NNG_EADDRINVAL);
+ }
+
+ nni_mtx_lock(&l->mtx);
+ if (l->started) {
+ nni_mtx_unlock(&l->mtx);
+ return (NNG_EBUSY);
+ }
+ if (l->closed) {
+ nni_mtx_unlock(&l->mtx);
+ return (NNG_ECLOSED);
+ }
+ nni_posix_pfd_init(&l->pfd, fd, ipc_listener_cb, l);
+ l->started = true;
+ nni_mtx_unlock(&l->mtx);
+ return (0);
+}
+
+#ifdef NNG_TEST_LIB
+// this is readable only for test code -- user code should never rely on this
+static int
+ipc_listener_get_listen_fd(void *arg, void *buf, size_t *szp, nni_type t)
+{
+ int rv;
+ ipc_listener *l = arg;
+
+ nni_mtx_lock(&l->mtx);
+ NNI_ASSERT(l->started);
+ NNI_ASSERT(!l->closed);
+ rv = nni_copyout_int(nni_posix_pfd_fd(&l->pfd), buf, szp, t);
+ nni_mtx_unlock(&l->mtx);
+ return (rv);
+}
+#endif
+
static const nni_option ipc_listener_options[] = {
{
.o_name = NNG_OPT_LOCADDR,
@@ -262,6 +316,13 @@ static const nni_option ipc_listener_options[] = {
.o_set = ipc_listener_set_perms,
},
{
+ .o_name = NNG_OPT_LISTEN_FD,
+ .o_set = ipc_listener_set_listen_fd,
+#ifdef NNG_TEST_LIB
+ .o_get = ipc_listener_get_listen_fd,
+#endif
+ },
+ {
.o_name = NULL,
},
};
diff --git a/src/platform/posix/posix_tcplisten.c b/src/platform/posix/posix_tcplisten.c
index 6e7f4d14..29b5ea65 100644
--- a/src/platform/posix/posix_tcplisten.c
+++ b/src/platform/posix/posix_tcplisten.c
@@ -10,6 +10,7 @@
//
#include "core/nng_impl.h"
+#include "nng/nng.h"
#include <arpa/inet.h>
#include <errno.h>
@@ -402,6 +403,64 @@ tcp_listener_get_port(void *arg, void *buf, size_t *szp, nni_type t)
return (nni_copyout_int(port, buf, szp, t));
}
+static int
+tcp_listener_set_listen_fd(void *arg, const void *buf, size_t sz, nni_type t)
+{
+ tcp_listener *l = arg;
+ int fd;
+ struct sockaddr_storage ss;
+ socklen_t len = sizeof(ss);
+ int rv;
+
+ if ((rv = nni_copyin_int(&fd, buf, sz, 0, NNI_MAXINT, t)) != 0) {
+ return (rv);
+ }
+
+ if (getsockname(fd, (void *) &ss, &len) != 0) {
+ return (nni_plat_errno(errno));
+ }
+
+ if (((nni_posix_sockaddr2nn(&l->sa, &ss, len)) != 0) ||
+#ifdef NNG_ENABLE_IPV6
+ ((ss.ss_family != AF_INET) && (ss.ss_family != AF_INET6))
+#else
+ (ss.ss_family != AF_INET)
+#endif
+ ) {
+ return (NNG_EADDRINVAL);
+ }
+
+ nni_mtx_lock(&l->mtx);
+ if (l->started) {
+ nni_mtx_unlock(&l->mtx);
+ return (NNG_EBUSY);
+ }
+ if (l->closed) {
+ nni_mtx_unlock(&l->mtx);
+ return (NNG_ECLOSED);
+ }
+ nni_posix_pfd_init(&l->pfd, fd, tcp_listener_cb, l);
+ l->started = true;
+ nni_mtx_unlock(&l->mtx);
+ return (0);
+}
+
+#ifdef NNG_TEST_LIB
+// this is readable only for test code -- user code should never rely on this
+static int
+tcp_listener_get_listen_fd(void *arg, void *buf, size_t *szp, nni_type t)
+{
+ int rv;
+ tcp_listener *l = arg;
+ nni_mtx_lock(&l->mtx);
+ NNI_ASSERT(l->started);
+ NNI_ASSERT(!l->closed);
+ rv = nni_copyout_int(nni_posix_pfd_fd(&l->pfd), buf, szp, t);
+ nni_mtx_unlock(&l->mtx);
+ return (rv);
+}
+#endif
+
static const nni_option tcp_listener_options[] = {
{
.o_name = NNG_OPT_LOCADDR,
@@ -422,6 +481,13 @@ static const nni_option tcp_listener_options[] = {
.o_get = tcp_listener_get_port,
},
{
+ .o_name = NNG_OPT_LISTEN_FD,
+ .o_set = tcp_listener_set_listen_fd,
+#ifdef NNG_TEST_LIB
+ .o_get = tcp_listener_get_listen_fd,
+#endif
+ },
+ {
.o_name = NULL,
},
};
diff --git a/src/platform/tcp_stream_test.c b/src/platform/tcp_stream_test.c
index e28fb46c..a60cf1bd 100644
--- a/src/platform/tcp_stream_test.c
+++ b/src/platform/tcp_stream_test.c
@@ -172,10 +172,261 @@ test_tcp_listen_port_zero_not_bound(void)
nng_stream_listener_free(l);
}
+void
+test_tcp_listen_empty_address(void)
+{
+ nng_stream_listener *l;
+
+ // start a listening stream listener but do not call accept
+ NUTS_PASS(nng_stream_listener_alloc(&l, "tcp4://"));
+ NUTS_PASS(nng_stream_listener_listen(l));
+ nng_stream_listener_free(l);
+}
+
+void
+test_tcp_listen_activation(void)
+{
+#if defined(NNG_PLATFORM_POSIX)
+ nng_stream_listener *l1;
+ nng_stream_listener *l2;
+ char *addr;
+ int fd;
+ int port;
+ nng_aio *aio1;
+ nng_aio *aio2;
+ nng_stream_dialer *d;
+ nng_stream *c1, *c2;
+ char url[32];
+
+ NUTS_ADDR_ZERO(addr, "tcp4");
+ NUTS_PASS(nng_aio_alloc(&aio1, NULL, NULL));
+ NUTS_PASS(nng_aio_alloc(&aio2, NULL, NULL));
+
+ nng_aio_set_timeout(aio1, 2000);
+ nng_aio_set_timeout(aio2, 2000);
+
+ NUTS_PASS(nng_stream_listener_alloc(&l1, addr));
+ NUTS_PASS(nng_stream_listener_listen(l1));
+ NUTS_PASS(
+ nng_stream_listener_get_int(l1, NNG_OPT_TCP_BOUND_PORT, &port));
+
+ snprintf(url, sizeof(url), "tcp4://127.0.0.1:%u", port);
+ NUTS_PASS(nng_stream_dialer_alloc(&d, url));
+
+ NUTS_PASS(nng_stream_listener_get_int(l1, NNG_OPT_LISTEN_FD, &fd));
+ NUTS_PASS(nng_stream_listener_alloc(&l2, "tcp4://"));
+ NUTS_PASS(nng_stream_listener_set_int(l2, NNG_OPT_LISTEN_FD, fd));
+ nng_stream_dialer_dial(d, aio2);
+ nng_stream_listener_accept(l2, aio1);
+
+ nng_aio_wait(aio1);
+ nng_aio_wait(aio2);
+
+ NUTS_PASS(nng_aio_result(aio1));
+ NUTS_PASS(nng_aio_result(aio2));
+
+ c1 = nng_aio_get_output(aio1, 0);
+ c2 = nng_aio_get_output(aio2, 0);
+
+ char buf1[4];
+ char buf2[4];
+ nng_iov iov1;
+ nng_iov iov2;
+
+ iov1.iov_buf = buf1;
+ iov1.iov_len = sizeof(buf1);
+
+ iov2.iov_buf = buf2;
+ iov2.iov_len = sizeof(buf2);
+
+ nng_aio_set_iov(aio1, 1, &iov1);
+ nng_aio_set_iov(aio2, 1, &iov2);
+
+ snprintf(buf1, sizeof(buf1), "abc");
+
+ nng_stream_send(c1, aio1);
+ nng_stream_recv(c2, aio2);
+
+ nng_aio_wait(aio1);
+ nng_aio_wait(aio2);
+
+ NUTS_PASS(nng_aio_result(aio1));
+ NUTS_PASS(nng_aio_result(aio2));
+
+ NUTS_MATCH(buf1, buf2);
+
+ nng_stream_listener_free(l1);
+ nng_stream_listener_free(l2);
+ nng_stream_dialer_free(d);
+ nng_stream_free(c1);
+ nng_stream_free(c2);
+ nng_aio_free(aio1);
+ nng_aio_free(aio2);
+#elif defined(NNG_PLATFORM_WINDOWS)
+ // Windows requires that we not have created an I/O completion port yet
+ // on the incoming FD.
+ nng_stream_listener *l2;
+ SOCKET s;
+ char *addr;
+ int port;
+ nng_aio *aio1;
+ nng_aio *aio2;
+ nng_stream_dialer *d;
+ nng_stream *c1, *c2;
+ char url[32];
+ SOCKADDR_IN sin;
+
+ NUTS_ADDR_ZERO(addr, "tcp4");
+ NUTS_PASS(nng_aio_alloc(&aio1, NULL, NULL));
+ NUTS_PASS(nng_aio_alloc(&aio2, NULL, NULL));
+
+ nng_aio_set_timeout(aio1, 2000);
+ nng_aio_set_timeout(aio2, 2000);
+
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = 0;
+ int len = sizeof(sin);
+ NUTS_ASSERT(bind(s, (SOCKADDR *) &sin, sizeof(sin)) == 0);
+ NUTS_ASSERT(getsockname(s, (SOCKADDR *) &sin, &len) == 0);
+ port = ntohs(sin.sin_port);
+ NUTS_ASSERT(listen(s, SOMAXCONN) == 0);
+
+ NUTS_PASS(nng_stream_listener_alloc(&l2, "tcp4://"));
+ NUTS_PASS(nng_stream_listener_set_int(l2, NNG_OPT_LISTEN_FD, (int) s));
+ snprintf(url, sizeof(url), "tcp://127.0.0.1:%u", port);
+ NUTS_PASS(nng_stream_dialer_alloc(&d, url));
+ nng_stream_listener_accept(l2, aio1);
+
+ nng_stream_dialer_dial(d, aio2);
+
+ nng_aio_wait(aio1);
+ NUTS_PASS(nng_aio_result(aio1));
+
+ nng_aio_wait(aio2);
+ NUTS_PASS(nng_aio_result(aio2));
+
+ c1 = nng_aio_get_output(aio1, 0);
+ c2 = nng_aio_get_output(aio2, 0);
+
+ char buf1[4];
+ char buf2[4];
+ nng_iov iov1;
+ nng_iov iov2;
+
+ iov1.iov_buf = buf1;
+ iov1.iov_len = sizeof(buf1);
+
+ iov2.iov_buf = buf2;
+ iov2.iov_len = sizeof(buf2);
+
+ nng_aio_set_iov(aio1, 1, &iov1);
+ nng_aio_set_iov(aio2, 1, &iov2);
+
+ snprintf(buf1, sizeof(buf1), "abc");
+
+ nng_stream_send(c1, aio1);
+ nng_stream_recv(c2, aio2);
+
+ nng_aio_wait(aio1);
+ nng_aio_wait(aio2);
+
+ NUTS_PASS(nng_aio_result(aio1));
+ NUTS_PASS(nng_aio_result(aio2));
+
+ NUTS_MATCH(buf1, buf2);
+
+ nng_stream_listener_free(l2);
+ nng_stream_dialer_free(d);
+ nng_stream_free(c1);
+ nng_stream_free(c2);
+ nng_aio_free(aio1);
+ nng_aio_free(aio2);
+#else
+ NUTS_SKIP("Not Windows or POSIX");
+#endif
+}
+
+void
+test_tcp_listen_activation_busy(void)
+{
+ nng_stream_listener *l1;
+ int fd;
+
+ NUTS_PASS(nng_stream_listener_alloc(&l1, "tcp://"));
+ NUTS_PASS(nng_stream_listener_listen(l1));
+ NUTS_PASS(nng_stream_listener_get_int(l1, NNG_OPT_LISTEN_FD, &fd));
+ NUTS_FAIL(
+ nng_stream_listener_set_int(l1, NNG_OPT_LISTEN_FD, fd), NNG_EBUSY);
+ nng_stream_listener_free(l1);
+}
+
+void
+test_tcp_listen_activation_closed(void)
+{
+ nng_stream_listener *l1;
+ nng_stream_listener *l2;
+ int fd;
+
+ NUTS_PASS(nng_stream_listener_alloc(&l1, "tcp://"));
+ NUTS_PASS(nng_stream_listener_alloc(&l2, "tcp://"));
+ NUTS_PASS(nng_stream_listener_listen(l2));
+ NUTS_PASS(nng_stream_listener_get_int(l2, NNG_OPT_LISTEN_FD, &fd));
+ nng_stream_listener_close(l1);
+ NUTS_FAIL(nng_stream_listener_set_int(l1, NNG_OPT_LISTEN_FD, fd),
+ NNG_ECLOSED);
+ nng_stream_listener_free(l1);
+ nng_stream_listener_free(l2);
+}
+
+void
+test_tcp_listen_activation_wrong_family(void)
+{
+
+#if !defined(NNG_PLATFORM_POSIX) || !defined(NNG_TRANSPORT_IPC)
+ NUTS_SKIP("Not posix or no IPC");
+#else
+ nng_stream_listener *l1;
+ nng_stream_listener *l2;
+ int fd;
+ char *addr;
+
+ NUTS_ADDR(addr, "ipc");
+ NUTS_PASS(nng_stream_listener_alloc(&l1, "tcp://"));
+ NUTS_PASS(nng_stream_listener_alloc(&l2, addr));
+ NUTS_PASS(nng_stream_listener_listen(l2));
+ NUTS_PASS(nng_stream_listener_get_int(l2, NNG_OPT_LISTEN_FD, &fd));
+ NUTS_FAIL(nng_stream_listener_set_int(l1, NNG_OPT_LISTEN_FD, fd),
+ NNG_EADDRINVAL);
+ nng_stream_listener_free(l1);
+ nng_stream_listener_free(l2);
+#endif
+}
+
+void
+test_tcp_listen_activation_bogus_fd(void)
+{
+ nng_stream_listener *l1;
+
+ NUTS_PASS(nng_stream_listener_alloc(&l1, "tcp://"));
+ NUTS_FAIL(nng_stream_listener_set_int(l1, NNG_OPT_LISTEN_FD, 12345),
+ NNG_ECLOSED);
+ nng_stream_listener_free(l1);
+}
+
NUTS_TESTS = {
{ "tcp stream", test_tcp_stream },
{ "tcp listen accept cancel", test_tcp_listen_accept_cancel },
{ "tcp listen port zero not bound",
test_tcp_listen_port_zero_not_bound },
+ { "tcp listen empty address", test_tcp_listen_empty_address },
+ { "tcp socket activation", test_tcp_listen_activation },
+ { "tcp socket activation busy", test_tcp_listen_activation_busy },
+ { "tcp socket activation closed", test_tcp_listen_activation_closed },
+ { "tcp socket activation wrong family",
+ test_tcp_listen_activation_wrong_family },
+ { "tcp socket activation bogus fd",
+ test_tcp_listen_activation_bogus_fd },
{ NULL, NULL },
};
diff --git a/src/platform/windows/win_tcplisten.c b/src/platform/windows/win_tcplisten.c
index 0dd5ae16..91ecccb0 100644
--- a/src/platform/windows/win_tcplisten.c
+++ b/src/platform/windows/win_tcplisten.c
@@ -429,6 +429,75 @@ tcp_listener_get_port(void *arg, void *buf, size_t *szp, nni_type t)
return (nni_copyout_int(port, buf, szp, t));
}
+static int
+tcp_listener_set_listen_fd(void *arg, const void *buf, size_t sz, nni_type t)
+{
+ tcp_listener *l = arg;
+ int fd;
+ SOCKADDR_STORAGE ss;
+ int len = sizeof(ss);
+ int rv;
+
+ if ((rv = nni_copyin_int(&fd, buf, sz, 0, NNI_MAXINT, t)) != 0) {
+ return (rv);
+ }
+
+ if (getsockname(fd, (void *) &ss, &len) != 0) {
+ return (nni_win_error(GetLastError()));
+ }
+
+ if (((nni_win_sockaddr2nn(&l->sa, &ss, len)) != 0) ||
+#ifdef NNG_ENABLE_IPV6
+ ((ss.ss_family != AF_INET) && (ss.ss_family != AF_INET6))
+#else
+ (ss.ss_family != AF_INET)
+#endif
+ ) {
+ return (NNG_EADDRINVAL);
+ }
+
+ nni_mtx_lock(&l->mtx);
+ if (l->started) {
+ nni_mtx_unlock(&l->mtx);
+ return (NNG_EBUSY);
+ }
+ if (l->closed) {
+ nni_mtx_unlock(&l->mtx);
+ return (NNG_ECLOSED);
+ }
+
+ int yes = 1;
+ (void) setsockopt(
+ l->s, IPPROTO_TCP, TCP_NODELAY, (char *) &yes, sizeof(yes));
+
+ l->ss = ss;
+ l->s = (SOCKET) fd;
+ if ((rv = nni_win_io_register((HANDLE) l->s)) != 0) {
+ l->s = INVALID_SOCKET;
+ nni_mtx_unlock(&l->mtx);
+ return (rv);
+ }
+ l->started = true;
+ nni_mtx_unlock(&l->mtx);
+ return (0);
+}
+
+#ifdef NNG_TEST_LIB
+// this is readable only for test code -- user code should never rely on this
+static int
+tcp_listener_get_listen_fd(void *arg, void *buf, size_t *szp, nni_type t)
+{
+ int rv;
+ tcp_listener *l = arg;
+ nni_mtx_lock(&l->mtx);
+ NNI_ASSERT(l->started);
+ NNI_ASSERT(!l->closed);
+ rv = nni_copyout_int((int) l->s, buf, szp, t);
+ nni_mtx_unlock(&l->mtx);
+ return (rv);
+}
+#endif
+
static const nni_option tcp_listener_options[] = {
{
.o_name = NNG_OPT_LOCADDR,
@@ -449,6 +518,13 @@ static const nni_option tcp_listener_options[] = {
.o_get = tcp_listener_get_port,
},
{
+ .o_name = NNG_OPT_LISTEN_FD,
+ .o_set = tcp_listener_set_listen_fd,
+#ifdef NNG_TEST_LIB
+ .o_get = tcp_listener_get_listen_fd,
+#endif
+ },
+ {
.o_name = NULL,
},
};