aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2017-01-11 12:55:46 -0800
committerGarrett D'Amore <garrett@damore.org>2017-01-12 20:47:24 -0800
commitd615c5e51268a23887e2d29b5828a0447ba5409b (patch)
treeb990cbbb5c56c99f445f0989783babd203610daa
parent5637b36e2ed59a0c65384804b780e323c9c451c0 (diff)
downloadnng-d615c5e51268a23887e2d29b5828a0447ba5409b.tar.gz
nng-d615c5e51268a23887e2d29b5828a0447ba5409b.tar.bz2
nng-d615c5e51268a23887e2d29b5828a0447ba5409b.zip
Add IPC (UNIX domain sockets) for POSIX, and test suite.
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/core/platform.h38
-rw-r--r--src/core/transport.c2
-rw-r--r--src/platform/posix/posix_impl.h9
-rw-r--r--src/platform/posix/posix_ipc.c342
-rw-r--r--src/transport/ipc/ipc.c375
-rw-r--r--src/transport/tcp/tcp.c2
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/ipc.c18
9 files changed, 789 insertions, 1 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c1b8e204..4380ff04 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -64,6 +64,7 @@ set (NNG_SOURCES
platform/posix/posix_alloc.c
platform/posix/posix_clock.c
platform/posix/posix_debug.c
+ platform/posix/posix_ipc.c
platform/posix/posix_net.c
platform/posix/posix_rand.c
platform/posix/posix_thread.c
@@ -86,6 +87,8 @@ set (NNG_SOURCES
transport/inproc/inproc.c
+ transport/ipc/ipc.c
+
transport/tcp/tcp.c
)
diff --git a/src/core/platform.h b/src/core/platform.h
index 5a504dec..2955215b 100644
--- a/src/core/platform.h
+++ b/src/core/platform.h
@@ -64,6 +64,7 @@ typedef struct nni_plat_mtx nni_plat_mtx;
typedef struct nni_plat_cv nni_plat_cv;
typedef struct nni_plat_thr nni_plat_thr;
typedef struct nni_plat_tcpsock nni_plat_tcpsock;
+typedef struct nni_plat_ipcsock nni_plat_ipcsock;
// Mutex handling.
@@ -208,6 +209,43 @@ extern int nni_plat_tcp_send(nni_plat_tcpsock *, nni_iov *, int);
// full, or an error condition occurs.
extern int nni_plat_tcp_recv(nni_plat_tcpsock *, nni_iov *, int);
+// nni_plat_ipc_init initializes the socket, for example it can
+// set underlying file descriptors to -1, etc.
+extern void nni_plat_ipc_init(nni_plat_ipcsock *);
+
+// nni_plat_ipc_fini just closes an IPC socket, and releases any related
+// resources.
+extern void nni_plat_ipc_fini(nni_plat_ipcsock *);
+
+// nni_plat_ipc_shutdown performs a shutdown of the socket. For
+// BSD sockets, this closes both sides of the IPC connection gracefully,
+// but the underlying file descriptor is left open. (This part is critical
+// to prevention of close() related races.)
+extern void nni_plat_ipc_shutdown(nni_plat_ipcsock *);
+
+// nni_plat_tcp_listen creates an IPC socket in listening mode, bound
+// to the specified path. Note that nni_plat_ipcsock should be defined
+// to whatever your platform uses. For most systems its just "int".
+extern int nni_plat_ipc_listen(nni_plat_ipcsock *, const char *);
+
+// nni_plat_ipc_accept does the accept to accept an inbound connection.
+// The ipcsock used for the server will have been set up with the
+// nni_plat_ipc_listen.
+extern int nni_plat_ipc_accept(nni_plat_ipcsock *, nni_plat_ipcsock *);
+
+// nni_plat_ipc_connect is the client side.
+extern int nni_plat_ipc_connect(nni_plat_ipcsock *, const char *);
+
+// nni_plat_ipc_send sends data to the peer. The platform is responsible
+// for attempting to send all of the data. The iov count will never be
+// larger than 4. The platform may modify the iovs.
+extern int nni_plat_ipc_send(nni_plat_ipcsock *, nni_iov *, int);
+
+// nni_plat_ipc_recv recvs data into the buffers provided by the
+// iovs. The implementation does not return until the iovs are completely
+// full, or an error condition occurs.
+extern int nni_plat_ipc_recv(nni_plat_ipcsock *, nni_iov *, int);
+
// nni_plat_seed_prng seeds the PRNG subsystem. The specified number
// of bytes of entropy should be stashed. When possible, cryptographic
// quality entropy sources should be used. Note that today we prefer
diff --git a/src/core/transport.c b/src/core/transport.c
index cf9f38c3..25a881d6 100644
--- a/src/core/transport.c
+++ b/src/core/transport.c
@@ -15,10 +15,12 @@
// to the system dynamically is something that might be considered later.
extern nni_tran nni_inproc_tran;
extern nni_tran nni_tcp_tran;
+extern nni_tran nni_ipc_tran;
static nni_tran *transports[] = {
&nni_inproc_tran,
&nni_tcp_tran,
+ &nni_ipc_tran,
NULL
};
diff --git a/src/platform/posix/posix_impl.h b/src/platform/posix/posix_impl.h
index 9825b151..c155e15c 100644
--- a/src/platform/posix/posix_impl.h
+++ b/src/platform/posix/posix_impl.h
@@ -20,6 +20,7 @@
#define PLATFORM_POSIX_ALLOC
#define PLATFORM_POSIX_DEBUG
#define PLATFORM_POSIX_CLOCK
+#define PLATFORM_POSIX_IPC
#define PLATFORM_POSIX_NET
#define PLATFORM_POSIX_RANDOM
#define PLATFORM_POSIX_THREAD
@@ -40,6 +41,14 @@ struct nni_plat_tcpsock {
};
#endif
+#ifdef PLATFORM_POSIX_IPC
+struct nni_plat_ipcsock {
+ int fd;
+ int devnull; // used for shutting down blocking accept()
+ char * unlink; // path to unlink at termination
+};
+#endif
+
// Define types that this platform uses.
#ifdef PLATFORM_POSIX_THREAD
diff --git a/src/platform/posix/posix_ipc.c b/src/platform/posix/posix_ipc.c
new file mode 100644
index 00000000..8c40397a
--- /dev/null
+++ b/src/platform/posix/posix_ipc.c
@@ -0,0 +1,342 @@
+//
+// Copyright 2017 Garrett D'Amore <garrett@damore.org>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include "core/nng_impl.h"
+
+#ifdef PLATFORM_POSIX_IPC
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+
+
+#ifdef SOCK_CLOEXEC
+#define NNI_IPC_SOCKTYPE (SOCK_STREAM | SOCK_CLOEXEC)
+#else
+#define NNI_IPC_SOCKTYPE SOCK_STREAM
+#endif
+
+static int
+nni_plat_ipc_path_to_sockaddr(struct sockaddr_un *sun, const char *path)
+{
+ memset(sun, 0, sizeof (*sun));
+ sun->sun_family = PF_UNIX;
+
+ // Technically on some platforms we could support path names larger
+ // than the path, and on others we could skip null termination. We
+ // take a conservative approach, which is that the path must fit in
+ // the supplied character array, and *must* be NULL terminated.
+
+ // TODO: abstract sockets, including autobind sockets.
+ if (strlen(path) >= sizeof (sun->sun_path)) {
+ return (NNG_EADDRINVAL);
+ }
+ if (strlen(path) == 0) {
+ return (-1);
+ }
+ snprintf(sun->sun_path, sizeof (sun->sun_path), "%s", path);
+ return (sizeof (*sun));
+}
+
+
+int
+nni_plat_ipc_send(nni_plat_ipcsock *s, nni_iov *iovs, int cnt)
+{
+ struct iovec iov[4]; // We never have more than 3 at present
+ int i;
+ int offset;
+ int resid = 0;
+ int rv;
+
+ if (cnt > 4) {
+ return (NNG_EINVAL);
+ }
+
+ for (i = 0; i < cnt; i++) {
+ iov[i].iov_base = iovs[i].iov_buf;
+ iov[i].iov_len = iovs[i].iov_len;
+ resid += iov[i].iov_len;
+ }
+
+ i = 0;
+ while (resid) {
+ rv = writev(s->fd, iov, cnt);
+ if (rv < 0) {
+ if (rv == EINTR) {
+ continue;
+ }
+ return (nni_plat_errno(errno));
+ }
+ if (rv > resid) {
+ nni_panic("writev says it wrote too much!");
+ }
+ resid -= rv;
+ while (rv) {
+ if (iov[i].iov_len <= rv) {
+ rv -= iov[i].iov_len;
+ i++;
+ cnt--;
+ } else {
+ iov[i].iov_len -= rv;
+ iov[i].iov_base += rv;
+ rv = 0;
+ }
+ }
+ }
+
+ return (0);
+}
+
+
+int
+nni_plat_ipc_recv(nni_plat_ipcsock *s, nni_iov *iovs, int cnt)
+{
+ struct iovec iov[4]; // We never have more than 3 at present
+ int i;
+ int offset;
+ int resid = 0;
+ int rv;
+
+ if (cnt > 4) {
+ return (NNG_EINVAL);
+ }
+
+ for (i = 0; i < cnt; i++) {
+ iov[i].iov_base = iovs[i].iov_buf;
+ iov[i].iov_len = iovs[i].iov_len;
+ resid += iov[i].iov_len;
+ }
+ i = 0;
+ while (resid) {
+ rv = readv(s->fd, iov, cnt);
+ if (rv < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return (nni_plat_errno(errno));
+ }
+ if (rv == 0) {
+ return (NNG_ECLOSED);
+ }
+ if (rv > resid) {
+ nni_panic("readv says it read too much!");
+ }
+
+ resid -= rv;
+ while (rv) {
+ if (iov[i].iov_len <= rv) {
+ rv -= iov[i].iov_len;
+ i++;
+ cnt--;
+ } else {
+ iov[i].iov_len -= rv;
+ iov[i].iov_base += rv;
+ rv = 0;
+ }
+ }
+ }
+
+ return (0);
+}
+
+
+static void
+nni_plat_ipc_setopts(int fd)
+{
+ int one;
+
+ // Try to ensure that both CLOEXEC is set, and that we don't
+ // generate SIGPIPE. (Note that SIGPIPE suppression in this way
+ // only works on BSD systems. Linux wants us to use sendmsg().)
+ (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
+#if defined(F_SETNOSIGPIPE)
+ (void) fcntl(fd, F_SETNOSIGPIPE, 1);
+#elif defined(SO_NOSIGPIPE)
+ one = 1;
+ (void) setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof (one));
+#endif
+}
+
+
+void
+nni_plat_ipc_init(nni_plat_ipcsock *s)
+{
+ s->fd = -1;
+}
+
+
+void
+nni_plat_ipc_fini(nni_plat_ipcsock *s)
+{
+ if (s->fd != -1) {
+ (void) close(s->fd);
+ s->fd = -1;
+ }
+ if (s->unlink != NULL) {
+ (void) unlink(s->unlink);
+ nni_free(s->unlink, strlen(s->unlink) + 1);
+ }
+}
+
+
+void
+nni_plat_ipc_shutdown(nni_plat_ipcsock *s)
+{
+ if (s->fd != -1) {
+ (void) shutdown(s->fd, SHUT_RDWR);
+ // This causes the equivalent of a close. Hopefully waking
+ // up anything that didn't get the hint with the shutdown.
+ // (macOS does not see the shtudown).
+ (void) dup2(nni_plat_devnull, s->fd);
+ }
+}
+
+
+// nni_plat_ipc_listen creates a file descriptor bound to the given address.
+// This basically does the equivalent of socket, bind, and listen. We have
+// chosen a default value for the listen backlog of 128, which should be
+// plenty. (If it isn't, then the accept thread can't get enough resources
+// to keep up, and your clients are going to experience bad things. Normally
+// the actual backlog should hover near 0 anyway.)
+int
+nni_plat_ipc_listen(nni_plat_ipcsock *s, const char *path)
+{
+ int fd, checkfd;
+ struct sockaddr_un sun;
+ int rv;
+
+ if (nni_plat_ipc_path_to_sockaddr(&sun, path) < 0) {
+ return (NNG_EADDRINVAL);
+ }
+
+ if ((fd = socket(AF_UNIX, NNI_IPC_SOCKTYPE, 0)) < 0) {
+ return (nni_plat_errno(errno));
+ }
+
+ // We are going to check to see if there was a name already there.
+ // If there was, and nothing is listening (ECONNREFUSED), then we
+ // will just try to cleanup the old socket. Note that this is not
+ // perfect in all scenarios, so use this with caution.
+ if ((checkfd = socket(AF_UNIX, NNI_IPC_SOCKTYPE, 0)) < 0) {
+ (void) close(fd);
+ return (nni_plat_errno(errno));
+ }
+
+ // Nonblocking because we don't want to wait for any remote server.
+ (void) fcntl(checkfd, F_SETFL, O_NONBLOCK);
+ if (connect(checkfd, (struct sockaddr *) &sun, sizeof (sun)) < 0) {
+ if (errno == ECONNREFUSED) {
+ (void) unlink(path);
+ }
+ }
+ (void) close(checkfd);
+
+ nni_plat_ipc_setopts(fd);
+
+ if ((s->unlink = nni_alloc(strlen(path) + 1)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ strcpy(s->unlink, path);
+ if (bind(fd, (struct sockaddr *) &sun, sizeof (sun)) < 0) {
+ rv = nni_plat_errno(errno);
+ nni_free(s->unlink, strlen(path) + 1);
+ s->unlink = NULL;
+ (void) close(fd);
+ return (rv);
+ }
+
+ // Listen -- 128 depth is probably sufficient. If it isn't, other
+ // bad things are going to happen.
+ if (listen(fd, 128) < 0) {
+ rv = nni_plat_errno(errno);
+ (void) close(fd);
+ return (rv);
+ }
+ s->fd = fd;
+ return (0);
+}
+
+
+int
+nni_plat_ipc_connect(nni_plat_ipcsock *s, const char *path)
+{
+ int fd;
+ int len;
+ struct sockaddr_un sun;
+ int rv;
+
+ if (nni_plat_ipc_path_to_sockaddr(&sun, path) < 0) {
+ return (NNG_EADDRINVAL);
+ }
+
+ if ((fd = socket(AF_UNIX, NNI_IPC_SOCKTYPE, 0)) < 0) {
+ return (nni_plat_errno(errno));
+ }
+
+ nni_plat_ipc_setopts(fd);
+
+ if (connect(fd, (struct sockaddr *) &sun, sizeof (sun)) != 0) {
+ rv = nni_plat_errno(errno);
+ (void) close(fd);
+ if (rv == NNG_ENOENT) {
+ // In this case we want to treat this the same as
+ // ECONNREFUSED, since they mean the same to us.
+ rv = NNG_ECONNREFUSED;
+ }
+ return (rv);
+ }
+ s->fd = fd;
+ return (0);
+}
+
+
+int
+nni_plat_ipc_accept(nni_plat_ipcsock *s, nni_plat_ipcsock *server)
+{
+ int fd;
+
+ for (;;) {
+#ifdef NNG_USE_ACCEPT4
+ fd = accept4(server->fd, NULL, NULL, SOCK_CLOEXEC);
+ if ((fd < 0) && ((errno == ENOSYS) || (errno == ENOTSUP))) {
+ fd = accept(server->fd, NULL, NULL);
+ }
+#else
+ fd = accept(server->fd, NULL, NULL);
+#endif
+
+ if (fd < 0) {
+ if ((errno == EINTR) || (errno == ECONNABORTED)) {
+ // These are not fatal errors, keep trying
+ continue;
+ }
+ if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
+ continue;
+ }
+ return (nni_plat_errno(errno));
+ } else {
+ break;
+ }
+ }
+
+ nni_plat_ipc_setopts(fd);
+
+ s->fd = fd;
+ return (0);
+}
+
+
+#endif // PLATFORM_POSIX_IPC
diff --git a/src/transport/ipc/ipc.c b/src/transport/ipc/ipc.c
new file mode 100644
index 00000000..8a28dfe5
--- /dev/null
+++ b/src/transport/ipc/ipc.c
@@ -0,0 +1,375 @@
+//
+// Copyright 2017 Garrett D'Amore <garrett@damore.org>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "core/nng_impl.h"
+
+// IPC transport. Platform specific IPC operations must be
+// supplied as well. Normally the IPC is UNIX domain sockets or
+// Windows named pipes. Other platforms could use other mechanisms.
+
+typedef struct nni_ipc_pipe nni_ipc_pipe;
+typedef struct nni_ipc_ep nni_ipc_ep;
+
+// nni_ipc_pipe is one end of an IPC connection.
+struct nni_ipc_pipe {
+ const char * addr;
+ nni_plat_ipcsock fd;
+ uint16_t peer;
+ uint16_t proto;
+ uint32_t rcvmax;
+};
+
+struct nni_ipc_ep {
+ char addr[NNG_MAXADDRLEN+1];
+ nni_plat_ipcsock fd;
+ int closed;
+ uint16_t proto;
+ uint32_t rcvmax;
+};
+
+static int
+nni_ipc_tran_init(void)
+{
+ return (0);
+}
+
+
+static void
+nni_ipc_tran_fini(void)
+{
+}
+
+
+static void
+nni_ipc_pipe_close(void *arg)
+{
+ nni_ipc_pipe *pipe = arg;
+
+ nni_plat_ipc_shutdown(&pipe->fd);
+}
+
+
+static void
+nni_ipc_pipe_destroy(void *arg)
+{
+ nni_ipc_pipe *pipe = arg;
+
+ nni_plat_ipc_fini(&pipe->fd);
+ NNI_FREE_STRUCT(pipe);
+}
+
+
+static int
+nni_ipc_pipe_send(void *arg, nni_msg *msg)
+{
+ nni_ipc_pipe *pipe = arg;
+ uint64_t len;
+ uint8_t buf[sizeof (len)];
+ nni_iov iov[4];
+ int rv;
+ uint8_t msgtype;
+
+ msgtype = 1; // "inband", the only defined option at present
+
+ iov[0].iov_buf = &msgtype;
+ iov[0].iov_len = 1;
+ iov[1].iov_buf = buf;
+ iov[1].iov_len = sizeof (buf);
+ iov[2].iov_buf = nni_msg_header(msg);
+ iov[2].iov_len = nni_msg_header_len(msg);
+ iov[3].iov_buf = nni_msg_body(msg);
+ iov[3].iov_len = nni_msg_len(msg);
+
+ len = (uint64_t) iov[2].iov_len + (uint64_t) iov[3].iov_len;
+ NNI_PUT64(buf, len);
+
+ if ((rv = nni_plat_ipc_send(&pipe->fd, iov, 4)) == 0) {
+ nni_msg_free(msg);
+ }
+ return (rv);
+}
+
+
+static int
+nni_ipc_pipe_recv(void *arg, nni_msg **msgp)
+{
+ nni_ipc_pipe *pipe = arg;
+ nni_msg *msg;
+ uint64_t len;
+ uint8_t buf[sizeof (len)];
+ nni_iov iov[2];
+ int rv;
+ uint8_t msgtype;
+
+ iov[0].iov_buf = &msgtype;
+ iov[0].iov_len = 1;
+ iov[1].iov_buf = buf;
+ iov[1].iov_len = sizeof (buf);
+ if ((rv = nni_plat_ipc_recv(&pipe->fd, iov, 2)) != 0) {
+ return (rv);
+ }
+ if (msgtype != 1) {
+ return (NNG_EPROTO);
+ }
+ NNI_GET64(buf, len);
+ if (len > pipe->rcvmax) {
+ return (NNG_EPROTO);
+ }
+
+ if ((rv = nng_msg_alloc(&msg, len)) != 0) {
+ return (rv);
+ }
+
+ iov[0].iov_len = nng_msg_len(msg);
+ iov[0].iov_buf = nng_msg_body(msg);
+
+ if ((rv = nni_plat_ipc_recv(&pipe->fd, iov, 1)) == 0) {
+ *msgp = msg;
+ } else {
+ nni_msg_free(msg);
+ }
+ return (rv);
+}
+
+
+static uint16_t
+nni_ipc_pipe_peer(void *arg)
+{
+ nni_ipc_pipe *pipe = arg;
+
+ return (pipe->peer);
+}
+
+
+static int
+nni_ipc_pipe_getopt(void *arg, int option, void *buf, size_t *szp)
+{
+#if 0
+ nni_inproc_pipe *pipe = arg;
+ size_t len;
+
+ switch (option) {
+ case NNG_OPT_LOCALADDR:
+ case NNG_OPT_REMOTEADDR:
+ len = strlen(pipe->addr) + 1;
+ if (len > *szp) {
+ (void) memcpy(buf, pipe->addr, *szp);
+ } else {
+ (void) memcpy(buf, pipe->addr, len);
+ }
+ *szp = len;
+ return (0);
+ }
+#endif
+ return (NNG_ENOTSUP);
+}
+
+
+static int
+nni_ipc_ep_init(void **epp, const char *url, uint16_t proto)
+{
+ nni_ipc_ep *ep;
+ int rv;
+
+ if (strlen(url) > NNG_MAXADDRLEN-1) {
+ return (NNG_EADDRINVAL);
+ }
+ if ((ep = NNI_ALLOC_STRUCT(ep)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ ep->closed = 0;
+ ep->proto = proto;
+ ep->rcvmax = 1024 * 1024; // XXX: fix this
+ nni_plat_ipc_init(&ep->fd);
+
+ (void) snprintf(ep->addr, sizeof (ep->addr), "%s", url);
+
+ *epp = ep;
+ return (0);
+}
+
+
+static void
+nni_ipc_ep_fini(void *arg)
+{
+ nni_ipc_ep *ep = arg;
+
+ nni_plat_ipc_fini(&ep->fd);
+ NNI_FREE_STRUCT(ep);
+}
+
+
+static void
+nni_ipc_ep_close(void *arg)
+{
+ nni_ipc_ep *ep = arg;
+
+ nni_plat_ipc_shutdown(&ep->fd);
+}
+
+
+static int
+nni_ipc_negotiate(nni_ipc_pipe *pipe)
+{
+ int rv;
+ nni_iov iov;
+ uint8_t buf[8];
+ uint16_t peer;
+
+ // First send our header..
+ buf[0] = 0;
+ buf[1] = 'S';
+ buf[2] = 'P';
+ buf[3] = 0; // version
+ NNI_PUT16(&buf[4], pipe->proto);
+ NNI_PUT16(&buf[6], 0);
+
+ iov.iov_buf = buf;
+ iov.iov_len = 8;
+ if ((rv = nni_plat_ipc_send(&pipe->fd, &iov, 1)) != 0) {
+ return (rv);
+ }
+
+ iov.iov_buf = buf;
+ iov.iov_len = 8;
+ if ((rv = nni_plat_ipc_recv(&pipe->fd, &iov, 1)) != 0) {
+ return (rv);
+ }
+
+ if ((buf[0] != 0) || (buf[1] != 'S') ||
+ (buf[2] != 'P') || (buf[3] != 0) ||
+ (buf[6] != 0) || (buf[7] != 0)) {
+ return (NNG_EPROTO);
+ }
+
+ NNI_GET16(&buf[4], pipe->peer);
+ return (0);
+}
+
+
+static int
+nni_ipc_ep_connect(void *arg, void **pipep)
+{
+ nni_ipc_ep *ep = arg;
+ nni_ipc_pipe *pipe;
+ int rv;
+ const char *path;
+
+ if (strncmp(ep->addr, "ipc://", strlen("ipc://")) != 0) {
+ return (NNG_EADDRINVAL);
+ }
+ path = ep->addr + strlen("ipc://");
+
+ if ((pipe = NNI_ALLOC_STRUCT(pipe)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ nni_plat_ipc_init(&pipe->fd);
+ pipe->proto = ep->proto;
+ pipe->rcvmax = ep->rcvmax;
+
+ rv = nni_plat_ipc_connect(&pipe->fd, path);
+ if (rv != 0) {
+ NNI_FREE_STRUCT(pipe);
+ return (rv);
+ }
+
+ if ((rv = nni_ipc_negotiate(pipe)) != 0) {
+ nni_plat_ipc_shutdown(&pipe->fd);
+ nni_plat_ipc_fini(&pipe->fd);
+ NNI_FREE_STRUCT(pipe);
+ return (rv);
+ }
+ *pipep = pipe;
+ return (0);
+}
+
+
+static int
+nni_ipc_ep_bind(void *arg)
+{
+ nni_ipc_ep *ep = arg;
+ int rv;
+ const char *path;
+
+ // We want to strok this, so make a copy. Skip the scheme.
+ if (strncmp(ep->addr, "ipc://", strlen("ipc://")) != 0) {
+ return (NNG_EADDRINVAL);
+ }
+ path = ep->addr + strlen("ipc://");
+
+ if ((rv = nni_plat_ipc_listen(&ep->fd, path)) != 0) {
+ return (rv);
+ }
+ return (0);
+}
+
+
+static int
+nni_ipc_ep_accept(void *arg, void **pipep)
+{
+ nni_ipc_ep *ep = arg;
+ nni_ipc_pipe *pipe;
+ int rv;
+
+
+ if ((pipe = NNI_ALLOC_STRUCT(pipe)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ pipe->proto = ep->proto;
+ pipe->rcvmax = ep->rcvmax;
+ nni_plat_ipc_init(&pipe->fd);
+
+ if ((rv = nni_plat_ipc_accept(&pipe->fd, &ep->fd)) != 0) {
+ NNI_FREE_STRUCT(pipe);
+ return (rv);
+ }
+ if ((rv = nni_ipc_negotiate(pipe)) != 0) {
+ nni_plat_ipc_shutdown(&pipe->fd);
+ nni_plat_ipc_fini(&pipe->fd);
+ NNI_FREE_STRUCT(pipe);
+ return (rv);
+ }
+ *pipep = pipe;
+ return (0);
+}
+
+
+static nni_tran_pipe nni_ipc_pipe_ops = {
+ .pipe_destroy = nni_ipc_pipe_destroy,
+ .pipe_send = nni_ipc_pipe_send,
+ .pipe_recv = nni_ipc_pipe_recv,
+ .pipe_close = nni_ipc_pipe_close,
+ .pipe_peer = nni_ipc_pipe_peer,
+ .pipe_getopt = nni_ipc_pipe_getopt,
+};
+
+static nni_tran_ep nni_ipc_ep_ops = {
+ .ep_init = nni_ipc_ep_init,
+ .ep_fini = nni_ipc_ep_fini,
+ .ep_connect = nni_ipc_ep_connect,
+ .ep_bind = nni_ipc_ep_bind,
+ .ep_accept = nni_ipc_ep_accept,
+ .ep_close = nni_ipc_ep_close,
+ .ep_setopt = NULL,
+ .ep_getopt = NULL,
+};
+
+// This is the IPC transport linkage, and should be the only global
+// symbol in this entire file.
+struct nni_tran nni_ipc_tran = {
+ .tran_scheme = "ipc",
+ .tran_ep = &nni_ipc_ep_ops,
+ .tran_pipe = &nni_ipc_pipe_ops,
+ .tran_init = nni_ipc_tran_init,
+ .tran_fini = nni_ipc_tran_fini,
+};
diff --git a/src/transport/tcp/tcp.c b/src/transport/tcp/tcp.c
index fe06cca4..9b8cdb1d 100644
--- a/src/transport/tcp/tcp.c
+++ b/src/transport/tcp/tcp.c
@@ -171,7 +171,7 @@ nni_tcp_ep_init(void **epp, const char *url, uint16_t proto)
int rv;
if (strlen(url) > NNG_MAXADDRLEN-1) {
- return (NNG_EINVAL);
+ return (NNG_EADDRINVAL);
}
if ((ep = NNI_ALLOC_STRUCT(ep)) == NULL) {
return (NNG_ENOMEM);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 16389601..5f8dc7f9 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -55,6 +55,7 @@ endif ()
add_nng_test(bus 5)
add_nng_test(idhash 5)
add_nng_test(inproc 5)
+add_nng_test(ipc 5)
add_nng_test(list 5)
add_nng_test(platform 5)
add_nng_test(reqrep 5)
diff --git a/tests/ipc.c b/tests/ipc.c
new file mode 100644
index 00000000..69fe36d0
--- /dev/null
+++ b/tests/ipc.c
@@ -0,0 +1,18 @@
+//
+// Copyright 2017 Garrett D'Amore <garrett@damore.org>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include "convey.h"
+#include "trantest.h"
+
+
+// Inproc tests.
+
+TestMain("IPC Transport", {
+ trantest_test_all("ipc:///tmp/nng_ipc_test");
+})