diff --git a/cmake/NNGOptions.cmake b/cmake/NNGOptions.cmake index b8067bca0..12075f4c1 100644 --- a/cmake/NNGOptions.cmake +++ b/cmake/NNGOptions.cmake @@ -1,5 +1,5 @@ # -# Copyright 2020 Staysail Systems, Inc. +# Copyright 2023 Staysail Systems, Inc. # # This software is supplied under the terms of the MIT License, a # copy of which should be located in the distribution where this @@ -127,6 +127,9 @@ CMAKE_DEPENDENT_OPTION(NNG_TRANSPORT_WSS "Enable WSS transport." ON "NNG_ENABLE_TLS" OFF) mark_as_advanced(NNG_TRANSPORT_WSS) +option (NNG_TRANSPORT_FDC "Enable File Descriptor transport (EXPERIMENTAL)" ON) +mark_as_advanced(NNG_TRANSPORT_FDC) + # ZeroTier option (NNG_TRANSPORT_ZEROTIER "Enable ZeroTier transport (requires libzerotiercore)." OFF) mark_as_advanced(NNG_TRANSPORT_ZEROTIER) diff --git a/include/nng/nng.h b/include/nng/nng.h index ce58fb3aa..349c7fe9f 100644 --- a/include/nng/nng.h +++ b/include/nng/nng.h @@ -709,8 +709,8 @@ NNG_DECL nng_listener nng_pipe_listener(nng_pipe); // TLS options are only used when the underlying transport supports TLS. -// NNG_OPT_TLS_CONFIG is a pointer to an nng_tls_config object. Generally -// this can used with endpoints, although once an endpoint is started, or +// NNG_OPT_TLS_CONFIG is a pointer to a nng_tls_config object. Generally +// this can be used with endpoints, although once an endpoint is started, or // once a configuration is used, the value becomes read-only. Note that // when configuring the object, a hold is placed on the TLS configuration, // using a reference count. When retrieving the object, no such hold is @@ -730,7 +730,7 @@ NNG_DECL nng_listener nng_pipe_listener(nng_pipe); // NNG_OPT_TLS_CERT_KEY_FILE names a single file that contains a certificate // and key identifying the endpoint. This is a write-only value. This can be -// set multiple times for times for different keys/certs corresponding to +// set multiple times for different keys/certs corresponding to // different algorithms on listeners, whereas dialers only support one. The // file must contain both cert and key as PEM blocks, and the key must // not be encrypted. (If more flexibility is needed, use the TLS configuration @@ -750,13 +750,13 @@ NNG_DECL nng_listener nng_pipe_listener(nng_pipe); #define NNG_OPT_TLS_SERVER_NAME "tls-server-name" // NNG_OPT_TLS_VERIFIED returns a boolean indicating whether the peer has -// been verified (true) or not (false). Typically this is read-only, and +// been verified (true) or not (false). Typically, this is read-only, and // only available for pipes. This option may return incorrect results if // peer authentication is disabled with `NNG_TLS_AUTH_MODE_NONE`. #define NNG_OPT_TLS_VERIFIED "tls-verified" // NNG_OPT_TLS_PEER_CN returns the string with the common name -// of the peer certificate. Typically this is read-only and +// of the peer certificate. Typically, this is read-only and // only available for pipes. This option may return incorrect results if // peer authentication is disabled with `NNG_TLS_AUTH_MODE_NONE`. #define NNG_OPT_TLS_PEER_CN "tls-peer-cn" @@ -893,6 +893,16 @@ NNG_DECL nng_listener nng_pipe_listener(nng_pipe); // peers that cannot be coerced into sending binary frames. #define NNG_OPT_WS_RECV_TEXT "ws:recv-text" +// NNG_OPT_FDC_FD is a write-only integer property that is used to +// file descriptors (or FILE HANDLE objects on Windows) to a +// fdconn:// based listener. This file descriptor will be taken +// over and used as a stream connection. The protocol is compatible +// with SP over TCP. This facility is experimental, and intended to +// allow use with descriptors created via socketpair() or similar. +// Note that unidirectional pipes (such as those from pipe(2) or mkfifo) +// are not supported. +#define NNG_OPT_FDC_FD "fdc:fd" + // XXX: TBD: priorities, ipv4only // Statistics. These are for informational purposes only, and subject diff --git a/src/core/dialer.c b/src/core/dialer.c index 55a46efb3..91d18dc8c 100644 --- a/src/core/dialer.c +++ b/src/core/dialer.c @@ -411,7 +411,7 @@ int nni_dialer_start(nni_dialer *d, unsigned flags) { int rv = 0; - nni_aio *aio; + nni_aio *aio = NULL; if (nni_atomic_flag_test_and_set(&d->d_started)) { return (NNG_ESTATE); diff --git a/src/core/fdconn.c b/src/core/fdconn.c index d2fb6227d..574c82c19 100644 --- a/src/core/fdconn.c +++ b/src/core/fdconn.c @@ -83,12 +83,13 @@ fdc_start_conn(fdc_listener *l, nni_aio *aio) l->listen_q[i] = l->listen_q[i + 1]; } l->listen_cnt--; - if ((rv = nni_fdc_conn_alloc(&c, fd)) == 0) { + if ((rv = nni_fdc_conn_alloc(&c, fd)) != 0) { nni_aio_finish_error(aio, rv); nni_fdc_close_fd(fd); + } else { + nni_aio_set_output(aio, 0, c); + nni_aio_finish(aio, 0, 0); } - nni_aio_set_output(aio, 0, c); - nni_aio_finish(aio, 0, 0); } static void diff --git a/src/core/fdconn.h b/src/core/fdconn.h index 3e5c49648..40911af00 100644 --- a/src/core/fdconn.h +++ b/src/core/fdconn.h @@ -12,17 +12,17 @@ #include "core/nng_impl.h" -#define NNG_OPT_FDC_FD "fdc:fd" - // the nni_fdc_conn struct is provided by platform code to wrap // an arbitrary byte stream file descriptor (UNIX) or handle (Windows) // with a nng_stream. typedef struct nni_fdc_conn nni_fdc_conn; extern int nni_fdc_conn_alloc(nni_fdc_conn **cp, int fd); +extern int nni_fdc_dialer_alloc(nng_stream_dialer **, const nng_url *); +extern int nni_fdc_listener_alloc(nng_stream_listener **, const nng_url *); // this is used to close a file descriptor, in case we cannot // create a connection (or if the listener is closed before the // connection is accepted.) -extern int nni_fdc_close_fd(int fd); +extern void nni_fdc_close_fd(int fd); #endif // CORE_FDC_H diff --git a/src/core/stream.c b/src/core/stream.c index 418bfb159..c401a1429 100644 --- a/src/core/stream.c +++ b/src/core/stream.c @@ -1,5 +1,5 @@ // -// Copyright 2020 Staysail Systems, Inc. +// Copyright 2023 Staysail Systems, Inc. // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -15,6 +15,7 @@ #include "core/nng_impl.h" #include +#include "core/fdconn.h" #include "core/tcp.h" #include "supplemental/tls/tls_api.h" #include "supplemental/websocket/websocket.h" @@ -94,6 +95,13 @@ static struct { .dialer_alloc = nni_ws_dialer_alloc, .listener_alloc = nni_ws_listener_alloc, }, +#ifdef NNG_TRANSPORT_FDC + { + .scheme = "fdconn", + .dialer_alloc = nni_fdc_dialer_alloc, + .listener_alloc = nni_fdc_listener_alloc, + }, +#endif { .scheme = NULL, }, diff --git a/src/platform/posix/posix_fdconn.c b/src/platform/posix/posix_fdconn.c index 0f93da8ed..b815f954a 100644 --- a/src/platform/posix/posix_fdconn.c +++ b/src/platform/posix/posix_fdconn.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "core/fdconn.h" #include "platform/posix/posix_aio.h" @@ -380,3 +381,9 @@ nni_fdc_conn_alloc(nni_fdc_conn **cp, int fd) *cp = c; return (0); } + +void +nni_fdc_close_fd(int fd) +{ + close(fd); +} \ No newline at end of file diff --git a/src/sp/transport.c b/src/sp/transport.c index 9f4c65225..755e3ae7a 100644 --- a/src/sp/transport.c +++ b/src/sp/transport.c @@ -1,5 +1,5 @@ // -// Copyright 2021 Staysail Systems, Inc. +// Copyright 2023 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // Copyright 2019 Devolutions // @@ -70,6 +70,9 @@ extern void nni_sp_wss_register(void); #ifdef NNG_TRANSPORT_ZEROTIER extern void nni_sp_zt_register(void); #endif +#ifdef NNG_TRANSPORT_FDC +extern void nni_sp_fdc_register(void); +#endif void nni_sp_tran_sys_init(void) @@ -95,6 +98,9 @@ nni_sp_tran_sys_init(void) #ifdef NNG_TRANSPORT_ZEROTIER nni_sp_zt_register(); #endif +#ifdef NNG_TRANSPORT_FDC + nni_sp_fdc_register(); +#endif } // nni_sp_tran_sys_fini finalizes the entire transport system, including all diff --git a/src/sp/transport/fdconn/CMakeLists.txt b/src/sp/transport/fdconn/CMakeLists.txt index dbeb3abb9..264dd455c 100644 --- a/src/sp/transport/fdconn/CMakeLists.txt +++ b/src/sp/transport/fdconn/CMakeLists.txt @@ -10,7 +10,6 @@ # File Descriptor (or Handle) based connections nng_directory(fdconn) -nng_sources(fdconn.c) -# nng_headers_if(NNG_TRANSPORT_FDCONN nng/transport/fdconn/fdconn.h) -nng_defines(NNG_TRANSPORT_FDCONN) +nng_sources_if(NNG_TRANSPORT_FDC fdconn.c) +nng_defines_if(NNG_TRANSPORT_FDC NNG_TRANSPORT_FDC) nng_test(fdc_test) \ No newline at end of file diff --git a/src/sp/transport/fdconn/fdc_test.c b/src/sp/transport/fdconn/fdc_test.c index daccf89e0..6f3511195 100644 --- a/src/sp/transport/fdconn/fdc_test.c +++ b/src/sp/transport/fdconn/fdc_test.c @@ -1,5 +1,5 @@ // -// Copyright 2020 Staysail Systems, Inc. +// Copyright 2023 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // Copyright 2018 Devolutions // Copyright 2018 Cody Piersall @@ -10,7 +10,6 @@ // found online at https://opensource.org/licenses/MIT. // - #include // FDC tests. @@ -46,67 +45,123 @@ test_tcp_local_address_connect(void) } void -test_tcp_non_local_address(void) +test_fdc_malformed_address(void) { nng_socket s1; NUTS_OPEN(s1); - NUTS_FAIL(nng_dial(s1, "tcp://8.8.8.8;127.0.0.1:80", NULL, 0), - NNG_EADDRINVAL); + NUTS_FAIL(nng_listen(s1, "fdconn://junk", NULL, 0), NNG_EADDRINVAL); NUTS_CLOSE(s1); } void -test_fdc_malformed_address(void) +test_fdc_listen(void) { nng_socket s1; NUTS_OPEN(s1); - NUTS_FAIL( - nng_dial(s1, "fdconn://junk", NULL, 0), NNG_EADDRINVAL); + NUTS_PASS(nng_listen(s1, "fdconn://", NULL, 0)); + NUTS_CLOSE(s1); +} + +#include +void +test_fdc_accept(void) +{ + nng_socket s1, s2; + nng_listener l; + int fds[2]; + + NUTS_PASS(socketpair(PF_UNIX, SOCK_STREAM, 0, fds)); + // make sure we won't have to deal with SIGPIPE - EPIPE is better + signal(SIGPIPE, SIG_IGN); + NUTS_OPEN(s1); + NUTS_OPEN(s2); + NUTS_PASS(nng_listener_create(&l, s1, "fdconn://")); + NUTS_PASS(nng_listener_start(l, 0)); + NUTS_PASS(nng_listener_set_int(l, NNG_OPT_FDC_FD, fds[0])); + NUTS_SLEEP(10); NUTS_CLOSE(s1); + close(fds[1]); } + void -test_tcp_recv_max(void) +test_fdc_exchange(void) +{ + nng_socket s1, s2; + nng_listener l1, l2; + int fds[2]; + + NUTS_PASS(socketpair(PF_UNIX, SOCK_STREAM, 0, fds)); + // make sure we won't have to deal with SIGPIPE - EPIPE is better + signal(SIGPIPE, SIG_IGN); + NUTS_OPEN(s1); + NUTS_OPEN(s2); + NUTS_PASS(nng_listener_create(&l1, s1, "fdconn://")); + NUTS_PASS(nng_listener_start(l1, 0)); + NUTS_PASS(nng_listener_set_int(l1, NNG_OPT_FDC_FD, fds[0])); + NUTS_PASS(nng_listener_create(&l2, s2, "fdconn://")); + NUTS_PASS(nng_listener_start(l2, 0)); + NUTS_PASS(nng_listener_set_int(l2, NNG_OPT_FDC_FD, fds[1])); + NUTS_SLEEP(10); + NUTS_SEND(s1, "hello"); + NUTS_RECV(s2, "hello"); + NUTS_SEND(s2, "there"); + NUTS_RECV(s1, "there"); + + NUTS_CLOSE(s1); + NUTS_CLOSE(s2); + close(fds[1]); + +} +void +test_fdc_recv_max(void) { char msg[256]; char buf[256]; nng_socket s0; nng_socket s1; - nng_listener l; + nng_listener l0; + nng_listener l1; size_t sz; - char *addr; + int fds[2]; - NUTS_ADDR(addr, "tcp"); + NUTS_PASS(socketpair(PF_UNIX, SOCK_STREAM, 0, fds)); NUTS_OPEN(s0); NUTS_PASS(nng_socket_set_ms(s0, NNG_OPT_RECVTIMEO, 100)); NUTS_PASS(nng_socket_set_size(s0, NNG_OPT_RECVMAXSZ, 200)); - NUTS_PASS(nng_listener_create(&l, s0, addr)); + NUTS_PASS(nng_listener_create(&l0, s0, "fdconn://")); NUTS_PASS(nng_socket_get_size(s0, NNG_OPT_RECVMAXSZ, &sz)); NUTS_TRUE(sz == 200); - NUTS_PASS(nng_listener_set_size(l, NNG_OPT_RECVMAXSZ, 100)); - NUTS_PASS(nng_listener_start(l, 0)); + NUTS_PASS(nng_listener_set_size(l0, NNG_OPT_RECVMAXSZ, 100)); + NUTS_PASS(nng_listener_start(l0, 0)); + NUTS_PASS(nng_listener_set_int(l0, NNG_OPT_FDC_FD, fds[0])); NUTS_OPEN(s1); - NUTS_PASS(nng_dial(s1, addr, NULL, 0)); + NUTS_PASS(nng_listener_create(&l1, s1, "fdconn://")); + NUTS_PASS(nng_listener_start(l1, 0)); + NUTS_PASS(nng_listener_set_int(l1, NNG_OPT_FDC_FD, fds[1])); NUTS_PASS(nng_send(s1, msg, 95, 0)); NUTS_PASS(nng_socket_set_ms(s1, NNG_OPT_SENDTIMEO, 100)); NUTS_PASS(nng_recv(s0, buf, &sz, 0)); NUTS_TRUE(sz == 95); NUTS_PASS(nng_send(s1, msg, 150, 0)); NUTS_FAIL(nng_recv(s0, buf, &sz, 0), NNG_ETIMEDOUT); - NUTS_PASS(nng_close(s0)); + NUTS_CLOSE(s0); NUTS_CLOSE(s1); } NUTS_TESTS = { - { "tcp fdc connect fail", test_fdc_connect_fail }, - { "tcp local address connect", test_tcp_local_address_connect }, - // { "tcp non-local address", test_tcp_non_local_address }, + { "fdc connect fail", test_fdc_connect_fail }, { "fdc malformed address", test_fdc_malformed_address }, + { "fdc listen", test_fdc_listen }, + { "fdc accept", test_fdc_accept }, + { "fdc exchange", test_fdc_exchange }, + { "fdc recv max", test_fdc_recv_max }, + // { "fdc recv max", test_tcp_recv_max }, { NULL, NULL }, }; \ No newline at end of file diff --git a/src/sp/transport/fdconn/fdconn.c b/src/sp/transport/fdconn/fdconn.c index 1c4c70333..ef89f318c 100644 --- a/src/sp/transport/fdconn/fdconn.c +++ b/src/sp/transport/fdconn/fdconn.c @@ -812,11 +812,9 @@ fdc_tran_listener_init(void **lp, nng_url *url, nni_listener *nlistener) int rv; nni_sock *sock = nni_listener_sock(nlistener); - // Check for invalid URL components. - if ((strlen(url->u_path) != 0) && (strcmp(url->u_path, "/") != 0)) { - return (NNG_EADDRINVAL); - } - if ((url->u_fragment != NULL) || (url->u_userinfo != NULL) || + // Check for invalid URL components -- we only accept a bare scheme + if ((strlen(url->u_host) != 0) || (strlen(url->u_path) != 0) || + (url->u_fragment != NULL) || (url->u_userinfo != NULL) || (url->u_query != NULL)) { return (NNG_EADDRINVAL); }