diff options
| author | Garrett D'Amore <garrett@damore.org> | 2018-05-03 14:28:44 -0700 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2018-05-03 15:14:45 -0700 |
| commit | afd555af4fba0acbf16c174dd9dece24181a1a38 (patch) | |
| tree | 9d49fec85c58ecad9a034a98e092627968494bc0 | |
| parent | fa986e725f08e30eab68e16765b2cf71613b871c (diff) | |
| download | nng-afd555af4fba0acbf16c174dd9dece24181a1a38.tar.gz nng-afd555af4fba0acbf16c174dd9dece24181a1a38.tar.bz2 nng-afd555af4fba0acbf16c174dd9dece24181a1a38.zip | |
fixes #383 Would like peerid for IPC
We offer uid, gid, process id, and even zone id where we have them.
Docs and tests are provided.
| -rw-r--r-- | CMakeLists.txt | 4 | ||||
| -rw-r--r-- | docs/man/nng_ipc.7.adoc | 47 | ||||
| -rw-r--r-- | src/core/platform.h | 15 | ||||
| -rw-r--r-- | src/platform/posix/posix_aio.h | 2 | ||||
| -rw-r--r-- | src/platform/posix/posix_ipc.c | 64 | ||||
| -rw-r--r-- | src/platform/posix/posix_pipedesc.c | 68 | ||||
| -rw-r--r-- | src/platform/windows/win_ipc.c | 60 | ||||
| -rw-r--r-- | src/transport/ipc/ipc.c | 68 | ||||
| -rw-r--r-- | src/transport/ipc/ipc.h | 19 | ||||
| -rw-r--r-- | tests/ipc.c | 67 |
10 files changed, 406 insertions, 8 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cb8f95b..f1bde337 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -390,6 +390,10 @@ else () nng_check_struct_member(msghdr msg_control sys/socket.h NNG_HAVE_MSG_CONTROL) nng_check_sym (kqueue sys/event.h NNG_HAVE_KQUEUE) nng_check_sym (epoll_wait sys/epoll.h NNG_HAVE_EPOLL) + nng_check_sym (getpeereid unistd.h NNG_HAVE_GETPEEREID) + nng_check_sym (SO_PEERCRED sys/socket.h NNG_HAVE_SOPEERCRED) + nng_check_sym (LOCAL_PEERCRED sys/un.h NNG_HAVE_LOCALPEERCRED) + nng_check_sym (getpeerucred ucred.h NNG_HAVE_GETPEERUCRED) endif () nng_check_sym (strlcat string.h NNG_HAVE_STRLCAT) diff --git a/docs/man/nng_ipc.7.adoc b/docs/man/nng_ipc.7.adoc index 5e711dc2..6bfa338b 100644 --- a/docs/man/nng_ipc.7.adoc +++ b/docs/man/nng_ipc.7.adoc @@ -68,8 +68,9 @@ the actual structure is of type `<<nng_sockaddr_ipc.5#,nng_sockaddr_ipc>>`. ((`NNG_OPT_IPC_PERMISSIONS`)):: -This write-only option may be used to configure the permissions that -are used on the UNIX domain socket created by a listener. +(`int`) +This write-only option may be applied to a listener to configure the +permissions that are used on the UNIX domain socket created by that listener. This property is only supported on POSIX systems. The value is of type `int`, representing the normal permission bits on a file, such as `0600` (typically meaning read-write to the owner, and @@ -84,12 +85,50 @@ NOTE: The _umask_ of the process is *not* applied to these bits. ((`NNG_OPT_IPC_SECURITY_DESCRIPTOR`)):: -This write-only option may be used on Windows platforms to configure -the `SECURITY_DESCRIPTOR` that is used when creating the underying +(`PSECURITY_DESCRIPTOR`) +This write-only option may be used on listeners on Windows platforms to +configure the `SECURITY_DESCRIPTOR` that is used when creating the underlying named pipe. The value is a pointer, `PSECURITY_DESCRIPTOR`, and may only be applied to listeners that have not been started yet. +((`NNG_OPT_IPC_PEER_UID`)):: + +(`uint64_t`) +This read-only option may be read from a pipe to determine the peer user id. +This is the effective user id of the peer when either the underlying +`listen()` or `connect()` calls were made, and is not forgeable. +This option is generally only available on POSIX systems. + +((`NNG_OPT_IPC_PEER_GID`)):: + +(`uint64_t`) +This read-only option may be read from a pipe to determine the peer primary +group id. +This is the effective group id of the peer when either the underlying +`listen()` or `connect()` calls were made, and is not forgeable. +This option is generally only available on POSIX systems. + +((`NNG_OPT_IPC_PEER_PID`)):: + +(`uint64_t`) +This read-only option may be read from a pipe to determine the process id +of the peer. +This option is only available on Windows, Linux, and certain other systems. + +NOTE: Applications should not assume that the process ID does not change, +as it is possible (although unsupported!) for a nefarious process to pass a +file descriptor between processes. +However, it is not possible for a nefarious application to forge the identity +of a well-behaved one using this method. + +((`NNG_OPT_IPC_PEER_ZONEID`)):: + +(`uint64_t`) +This read-only option may be read from a pipe to determine the zone id +of the peer. +Zones (and this option) are only supported on Solaris and illumos systems. + == SEE ALSO <<nng_sockaddr.5#,nng_sockaddr(5)>>, diff --git a/src/core/platform.h b/src/core/platform.h index 671556d8..6e7acdbf 100644 --- a/src/core/platform.h +++ b/src/core/platform.h @@ -341,6 +341,21 @@ extern void nni_plat_ipc_pipe_send(nni_plat_ipc_pipe *, nni_aio *); // The platform may modify the iovs. extern void nni_plat_ipc_pipe_recv(nni_plat_ipc_pipe *, nni_aio *); +// nni_plat_ipc_pipe_get_peer_uid obtains the peer user id, if possible. +// NB: Only POSIX systems support user IDs. +extern int nni_plat_ipc_pipe_get_peer_uid(nni_plat_ipc_pipe *, uint64_t *); + +// nni_plat_ipc_pipe_get_peer_gid obtains the peer group id, if possible. +// NB: Only POSIX systems support group IDs. +extern int nni_plat_ipc_pipe_get_peer_gid(nni_plat_ipc_pipe *, uint64_t *); + +// nni_plat_ipc_pipe_get_peer_pid obtains the peer process id, if possible. +extern int nni_plat_ipc_pipe_get_peer_pid(nni_plat_ipc_pipe *, uint64_t *); + +// nni_plat_ipc_pipe_get_peer_zoneid obtains the peer zone id, if possible. +// NB: Only illumos & SunOS systems have the notion of "zones". +extern int nni_plat_ipc_pipe_get_peer_zoneid(nni_plat_ipc_pipe *, uint64_t *); + // // UDP support. UDP is not connection oriented, and only has the notion // of being bound, sendto, and recvfrom. (It is possible to set up a diff --git a/src/platform/posix/posix_aio.h b/src/platform/posix/posix_aio.h index 3954f225..7d6a7231 100644 --- a/src/platform/posix/posix_aio.h +++ b/src/platform/posix/posix_aio.h @@ -33,6 +33,8 @@ extern int nni_posix_pipedesc_peername(nni_posix_pipedesc *, nni_sockaddr *); extern int nni_posix_pipedesc_sockname(nni_posix_pipedesc *, nni_sockaddr *); extern int nni_posix_pipedesc_set_nodelay(nni_posix_pipedesc *, bool); extern int nni_posix_pipedesc_set_keepalive(nni_posix_pipedesc *, bool); +extern int nni_posix_pipedesc_get_peerid( + nni_posix_pipedesc *, uint64_t *, uint64_t *, uint64_t *, uint64_t *); extern int nni_posix_epdesc_init(nni_posix_epdesc **, int); extern void nni_posix_epdesc_set_local(nni_posix_epdesc *, void *, size_t); diff --git a/src/platform/posix/posix_ipc.c b/src/platform/posix/posix_ipc.c index c1bb9292..5ba3c8fb 100644 --- a/src/platform/posix/posix_ipc.c +++ b/src/platform/posix/posix_ipc.c @@ -183,4 +183,68 @@ nni_plat_ipc_pipe_recv(nni_plat_ipc_pipe *p, nni_aio *aio) nni_posix_pipedesc_recv((void *) p, aio); } +int +nni_plat_ipc_pipe_get_peer_uid(nni_plat_ipc_pipe *p, uint64_t *uid) +{ + int rv; + uint64_t ignore; + + if ((rv = nni_posix_pipedesc_get_peerid( + (void *) p, uid, &ignore, &ignore, &ignore)) != 0) { + return (rv); + } + return (0); +} + +int +nni_plat_ipc_pipe_get_peer_gid(nni_plat_ipc_pipe *p, uint64_t *gid) +{ + int rv; + uint64_t ignore; + + if ((rv = nni_posix_pipedesc_get_peerid( + (void *) p, &ignore, gid, &ignore, &ignore)) != 0) { + return (rv); + } + return (0); +} + +int +nni_plat_ipc_pipe_get_peer_zoneid(nni_plat_ipc_pipe *p, uint64_t *zid) +{ + int rv; + uint64_t ignore; + uint64_t id; + + if ((rv = nni_posix_pipedesc_get_peerid( + (void *) p, &ignore, &ignore, &ignore, &id)) != 0) { + return (rv); + } + if (id == (uint64_t) -1) { + // NB: -1 is not a legal zone id (illumos/Solaris) + return (NNG_ENOTSUP); + } + *zid = id; + return (0); +} + +int +nni_plat_ipc_pipe_get_peer_pid(nni_plat_ipc_pipe *p, uint64_t *pid) +{ + int rv; + uint64_t ignore; + uint64_t id; + + if ((rv = nni_posix_pipedesc_get_peerid( + (void *) p, &ignore, &ignore, &id, &ignore)) != 0) { + return (rv); + } + if (id == (uint64_t) -1) { + // NB: -1 is not a legal process id + return (NNG_ENOTSUP); + } + *pid = id; + return (0); +} + #endif // NNG_PLATFORM_POSIX diff --git a/src/platform/posix/posix_pipedesc.c b/src/platform/posix/posix_pipedesc.c index f9cbb94b..61005ca8 100644 --- a/src/platform/posix/posix_pipedesc.c +++ b/src/platform/posix/posix_pipedesc.c @@ -26,6 +26,12 @@ #include <sys/types.h> #include <sys/uio.h> #include <unistd.h> +#if defined(NNG_HAVE_GETPEERUCRED) +#include <ucred.h> +#elif defined(NNG_HAVE_LOCALPEERCRED) +#include <sys/ucred.h> +#include <sys/un.h> +#endif // nni_posix_pipedesc is a descriptor kept one per transport pipe (i.e. open // file descriptor for TCP socket, etc.) This contains the list of pending @@ -402,6 +408,68 @@ nni_posix_pipedesc_init(nni_posix_pipedesc **pdp, int fd) return (0); } +int +nni_posix_pipedesc_get_peerid(nni_posix_pipedesc *pd, uint64_t *euid, + uint64_t *egid, uint64_t *prid, uint64_t *znid) +{ + int fd = pd->node.fd; +#if defined(NNG_HAVE_GETPEEREID) + uid_t uid; + gid_t gid; + + if (getpeereid(fd, &uid, &gid) != 0) { + return (nni_plat_errno(errno)); + } + *euid = uid; + *egid = gid; + *prid = (uint64_t) -1; + *znid = (uint64_t) -1; + return (0); +#elif defined(NNG_HAVE_GETPEERUCRED) + ucred *ucp; + if (getpeerucred(fd, &ucp) != 0) { + return (nni_plat_errno(errno)); + } + *euid = ucred_geteuid(ucp); + *egid = ucred_geteuid(ucp); + *prid = ucred_getpid(ucp); + *znid = ucred_getzoneid(ucp); + ucred_free(ucp); + return (0); +#elif defined(NNG_HAVE_SOPEERCRED) + struct ucred uc; + socklen_t len = sizeof(uc); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) != 0) { + return (nni_plat_errno(errno)); + } + *euid = uc.uid; + *egid = uc.gid; + *prid = uc.pid; + *znid = (uint64_t) -1; + return (0); +#elif defined(NNG_HAVE_LOCALPEERCRED) + struct xucred xu; + socklen_t len = sizeof(xu); + if (getsockopt(fd, SOL_LOCAL, LOCAL_PEERCRED, &xu, &len) != 0) { + return (nni_plat_errno(errno)); + } + *euid = xu.cr_uid; + *egid = xu.cr_gid; + *prid = (uint64_t) -1; // XXX: macOS has undocumented LOCAL_PEERPID... + *znid = (uint64_t) -1; + return (0); +#else + if (fd < 0) { + return (NNG_ECLOSED); + } + NNI_ARG_UNUSED(euid); + NNI_ARG_UNUSED(egid); + NNI_ARG_UNUSED(prid); + NNI_ARG_UNUSED(znid); + return (NNG_ENOTSUP); +#endif +} + void nni_posix_pipedesc_fini(nni_posix_pipedesc *pd) { diff --git a/src/platform/windows/win_ipc.c b/src/platform/windows/win_ipc.c index 7d118672..c6376cc7 100644 --- a/src/platform/windows/win_ipc.c +++ b/src/platform/windows/win_ipc.c @@ -16,6 +16,7 @@ struct nni_plat_ipc_pipe { HANDLE p; + int mode; nni_win_event rcv_ev; nni_win_event snd_ev; }; @@ -128,7 +129,7 @@ nni_win_ipc_pipe_finish(nni_win_event *evt, nni_aio *aio) } static int -nni_win_ipc_pipe_init(nni_plat_ipc_pipe **pipep, HANDLE p) +nni_win_ipc_pipe_init(nni_plat_ipc_pipe **pipep, HANDLE p, int mode) { nni_plat_ipc_pipe *pipe; int rv; @@ -136,6 +137,7 @@ nni_win_ipc_pipe_init(nni_plat_ipc_pipe **pipep, HANDLE p) if ((pipe = NNI_ALLOC_STRUCT(pipe)) == NULL) { return (NNG_ENOMEM); } + pipe->mode = mode; rv = nni_win_event_init(&pipe->rcv_ev, &nni_win_ipc_pipe_ops, pipe); if (rv != 0) { nni_plat_ipc_pipe_fini(pipe); @@ -189,6 +191,56 @@ nni_plat_ipc_pipe_fini(nni_plat_ipc_pipe *pipe) } int +nni_plat_ipc_pipe_get_peer_uid(nni_plat_ipc_pipe *pipe, uint64_t *id) +{ + NNI_ARG_UNUSED(pipe); + NNI_ARG_UNUSED(id); + return (NNG_ENOTSUP); +} + +int +nni_plat_ipc_pipe_get_peer_gid(nni_plat_ipc_pipe *pipe, uint64_t *id) +{ + NNI_ARG_UNUSED(pipe); + NNI_ARG_UNUSED(id); + return (NNG_ENOTSUP); +} + +int +nni_plat_ipc_pipe_get_peer_zoneid(nni_plat_ipc_pipe *pipe, uint64_t *id) +{ + NNI_ARG_UNUSED(pipe); + NNI_ARG_UNUSED(id); + return (NNG_ENOTSUP); +} + +// nni_plat_ipc_pipe_get_peer_gid obtains the peer group id, if possible. +// NB: Only POSIX systems support group IDs. +int +nni_plat_ipc_pipe_get_peer_pid(nni_plat_ipc_pipe *pipe, uint64_t *pid) +{ + ULONG id; + switch (pipe->mode) { + case NNI_EP_MODE_DIAL: + if (!GetNamedPipeServerProcessId(pipe->p, &id)) { + return (nni_win_error(GetLastError())); + } + *pid = id; + break; + case NNI_EP_MODE_LISTEN: + if (!GetNamedPipeClientProcessId(pipe->p, &id)) { + return (nni_win_error(GetLastError())); + } + *pid = id; + break; + default: + // Should never occur! + return (NNG_EINVAL); + } + return (0); +} + +int nni_plat_ipc_ep_init(nni_plat_ipc_ep **epp, const nni_sockaddr *sa, int mode) { const char * path; @@ -331,7 +383,8 @@ nni_win_ipc_acc_finish(nni_win_event *evt, nni_aio *aio) oldp = ep->p; ep->p = newp; - if ((rv = nni_win_ipc_pipe_init(&pipe, oldp)) != 0) { + if ((rv = nni_win_ipc_pipe_init(&pipe, oldp, NNI_EP_MODE_LISTEN)) != + 0) { // The new pipe is already fine for us. Discard // the old one, since failed to be able to use it. DisconnectNamedPipe(oldp); @@ -466,7 +519,8 @@ nni_win_ipc_conn_thr(void *arg) } goto fail; } - if (((rv = nni_win_ipc_pipe_init(&pipe, p)) != 0) || + if (((rv = nni_win_ipc_pipe_init( + &pipe, p, NNI_EP_MODE_DIAL)) != 0) || ((rv = nni_win_iocp_register(p)) != 0)) { goto fail; } diff --git a/src/transport/ipc/ipc.c b/src/transport/ipc/ipc.c index 3dbccb50..e5f3d533 100644 --- a/src/transport/ipc/ipc.c +++ b/src/transport/ipc/ipc.c @@ -537,6 +537,54 @@ nni_ipc_pipe_get_addr(void *arg, void *buf, size_t *szp, int typ) return (nni_copyout_sockaddr(&p->sa, buf, szp, typ)); } +static int +nni_ipc_pipe_get_peer_uid(void *arg, void *buf, size_t *szp, int typ) +{ + nni_ipc_pipe *p = arg; + uint64_t id; + int rv; + if ((rv = nni_plat_ipc_pipe_get_peer_uid(p->ipp, &id)) != 0) { + return (rv); + } + return (nni_copyout_u64(id, buf, szp, typ)); +} + +static int +nni_ipc_pipe_get_peer_gid(void *arg, void *buf, size_t *szp, int typ) +{ + nni_ipc_pipe *p = arg; + uint64_t id; + int rv; + if ((rv = nni_plat_ipc_pipe_get_peer_gid(p->ipp, &id)) != 0) { + return (rv); + } + return (nni_copyout_u64(id, buf, szp, typ)); +} + +static int +nni_ipc_pipe_get_peer_pid(void *arg, void *buf, size_t *szp, int typ) +{ + nni_ipc_pipe *p = arg; + uint64_t id; + int rv; + if ((rv = nni_plat_ipc_pipe_get_peer_pid(p->ipp, &id)) != 0) { + return (rv); + } + return (nni_copyout_u64(id, buf, szp, typ)); +} + +static int +nni_ipc_pipe_get_peer_zoneid(void *arg, void *buf, size_t *szp, int typ) +{ + nni_ipc_pipe *p = arg; + uint64_t id; + int rv; + if ((rv = nni_plat_ipc_pipe_get_peer_zoneid(p->ipp, &id)) != 0) { + return (rv); + } + return (nni_copyout_u64(id, buf, szp, typ)); +} + static void nni_ipc_ep_fini(void *arg) { @@ -785,6 +833,26 @@ static nni_tran_pipe_option nni_ipc_pipe_options[] = { .po_type = NNI_TYPE_SOCKADDR, .po_getopt = nni_ipc_pipe_get_addr, }, + { + .po_name = NNG_OPT_IPC_PEER_UID, + .po_type = NNI_TYPE_UINT64, + .po_getopt = nni_ipc_pipe_get_peer_uid, + }, + { + .po_name = NNG_OPT_IPC_PEER_GID, + .po_type = NNI_TYPE_UINT64, + .po_getopt = nni_ipc_pipe_get_peer_gid, + }, + { + .po_name = NNG_OPT_IPC_PEER_PID, + .po_type = NNI_TYPE_UINT64, + .po_getopt = nni_ipc_pipe_get_peer_pid, + }, + { + .po_name = NNG_OPT_IPC_PEER_ZONEID, + .po_type = NNI_TYPE_UINT64, + .po_getopt = nni_ipc_pipe_get_peer_zoneid, + }, // terminate list { .po_name = NULL, diff --git a/src/transport/ipc/ipc.h b/src/transport/ipc/ipc.h index 42cbdb08..497fb2b5 100644 --- a/src/transport/ipc/ipc.h +++ b/src/transport/ipc/ipc.h @@ -28,4 +28,23 @@ NNG_DECL int nng_ipc_register(void); // this for security. #define NNG_OPT_IPC_PERMISSIONS "ipc:permissions" +// Peer UID. This is only available on POSIX style systems. +#define NNG_OPT_IPC_PEER_UID "ipc:peer-uid" + +// Peer GID (primary group). This is only available on POSIX style systems. +#define NNG_OPT_IPC_PEER_GID "ipc:peer-gid" + +// Peer process ID. Available on Windows, Linux, and SunOS. +// In theory we could obtain this with the first message sent, +// but we have elected not to do this for now. (Nice RFE for a FreeBSD +// guru though.) +#define NNG_OPT_IPC_PEER_PID "ipc:peer-pid" + +// Peer Zone ID. Only on SunOS systems. (Linux containers have no +// definable kernel identity; they are a user-land fabrication made up +// from various pieces of different namespaces. FreeBSD does have +// something called JailIDs, but it isn't obvious how to determine this, +// or even if processes can use IPC across jail boundaries.) +#define NNG_OPT_IPC_PEER_ZONEID "ipc:peer-zoneid" + #endif // NNG_TRANSPORT_IPC_IPC_H diff --git a/tests/ipc.c b/tests/ipc.c index 54996ee3..4c8f5e41 100644 --- a/tests/ipc.c +++ b/tests/ipc.c @@ -10,11 +10,76 @@ #include "convey.h" #include "trantest.h" +#ifdef _WIN32 +#else +#include <unistd.h> +#ifdef NNG_HAVE_GETPEERUCRED +#include <zone.h> +#endif +#endif + +#include "transport/ipc/ipc.h" // Inproc tests. +static int +check_props(nng_msg *msg) +{ + nng_pipe p; + size_t z; + nng_sockaddr la; + nng_sockaddr ra; + uint64_t id; + + p = nng_msg_get_pipe(msg); + So(nng_pipe_id(p) > 0); + So(nng_pipe_getopt_sockaddr(p, NNG_OPT_LOCADDR, &la) == 0); + So(la.s_family == NNG_AF_IPC); + // untyped + z = sizeof(nng_sockaddr); + So(nng_pipe_getopt(p, NNG_OPT_REMADDR, &ra, &z) == 0); + So(z == sizeof(ra)); + So(ra.s_family == NNG_AF_IPC); + + So(nng_pipe_getopt_size(p, NNG_OPT_REMADDR, &z) == NNG_EBADTYPE); + z = 1; + So(nng_pipe_getopt(p, NNG_OPT_REMADDR, &ra, &z) == NNG_EINVAL); + +#ifdef _WIN32 + So(nng_pipe_getopt_uint64(p, NNG_OPT_IPC_PEER_UID, &id) == + NNG_ENOTSUP); + So(nng_pipe_getopt_uint64(p, NNG_OPT_IPC_PEER_GID, &id) == + NNG_ENOTSUP); + So(nng_pipe_getopt_uint64(p, NNG_OPT_IPC_PEER_ZONEID, &id) == + NNG_ENOTSUP); + So(nng_pipe_getopt_uint64(p, NNG_OPT_IPC_PEER_PID, &id) == 0); + So(id == GetCurrentProcessId()); +#else + So(nng_pipe_getopt_uint64(p, NNG_OPT_IPC_PEER_UID, &id) == 0); + So(id == (uint64_t) getuid()); + So(nng_pipe_getopt_uint64(p, NNG_OPT_IPC_PEER_GID, &id) == 0); + So(id == (uint64_t) getgid()); + +#if defined(NNG_HAVE_SOPEERCRED) || defined(NNG_HAVE_GETPEERUCRED) + So(nng_pipe_getopt_uint64(p, NNG_OPT_IPC_PEER_PID, &id) == 0); + So(id == (uint64_t) getpid()); +#else + So(nng_pipe_getopt_uint64(p, NNG_OPT_IPC_PEER_PID, &id) == + NNG_ENOTSUP); +#endif + +#ifdef NNG_HAVE_GETPEERUCRED + So(nng_pipe_getopt_uint64(p, NNG_OPT_IPC_PEER_ZONEID, &id) == 0); + So(id == getzoneid()); +#else + So(nng_pipe_getopt_uint64(p, NNG_OPT_IPC_PEER_ZONEID, &id) == + NNG_ENOTSUP); +#endif +#endif + return (0); +} TestMain("IPC Transport", { - trantest_test_all("ipc:///tmp/nng_ipc_test_%u"); + trantest_test_extended("ipc:///tmp/nng_ipc_test_%u", check_props); nng_fini(); }) |
