diff --git a/daemon/io.c b/daemon/io.c index 13909a3a1508e5a9e996d3bc6a77a4a1acff25e5..d0235f94809e0c8cff17b56a70f27f2218faa056 100644 --- a/daemon/io.c +++ b/daemon/io.c @@ -298,6 +298,7 @@ static void _tcp_accept(uv_stream_t *master, int status, bool tls) session->has_tls = tls; if (tls && !session->tls_ctx) { session->tls_ctx = tls_new(master->loop->data); + session->tls_ctx->session = session; } uv_timer_t *timer = &session->timeout; uv_timer_start(timer, tcp_timeout_trigger, KR_CONN_RTT_MAX/2, KR_CONN_RTT_MAX/2); diff --git a/daemon/tls.c b/daemon/tls.c index b5378f4d4c6771cdac1ea69bbbac9ad75d63ba3b..56117a1b6e0684c236c1026cca170a0870d79ebf 100644 --- a/daemon/tls.c +++ b/daemon/tls.c @@ -38,19 +38,6 @@ #define EPHEMERAL_CERT_EXPIRATION_SECONDS_RENEW_BEFORE 60*60*24*7 #define GNUTLS_PIN_MIN_VERSION 0x030400 -struct tls_client_ctx_t { - gnutls_session_t tls_session; - tls_client_hs_state_t handshake_state; - - struct session *session; - tls_handshake_cb handshake_cb; - const uint8_t *buf; - ssize_t nread; - ssize_t consumed; - uint8_t recv_buf[4096]; - const struct tls_client_paramlist_entry *params; -}; - /** @internal Debugging facility. */ #ifdef DEBUG #define DEBUG_MSG(fmt...) kr_log_verbose("[tls] " fmt) @@ -79,32 +66,6 @@ static int kres_gnutls_set_priority(gnutls_session_t session) { return err; } - -static ssize_t kres_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len) -{ - struct tls_ctx_t *t = (struct tls_ctx_t *)h; - const uv_buf_t ub = {(void *)buf, len}; - - DEBUG_MSG("[tls] push %zu <%p>\n", len, h); - if (t == NULL) { - errno = EFAULT; - return -1; - } - - int ret = uv_try_write(t->handle, &ub, 1); - if (ret > 0) { - return (ssize_t) ret; - } - if (ret == UV_EAGAIN) { - errno = EAGAIN; - } else { - kr_log_error("[tls] uv_try_write: %s\n", uv_strerror(ret)); - errno = EIO; - } - return -1; -} - - static ssize_t kres_gnutls_pull(gnutls_transport_ptr_t h, void *buf, size_t len) { struct tls_ctx_t *t = (struct tls_ctx_t *)h; @@ -168,32 +129,48 @@ struct tls_ctx_t *tls_new(struct worker_ctx *worker) return NULL; } - int err = gnutls_init(&tls->session, GNUTLS_SERVER | GNUTLS_NONBLOCK); + int err = gnutls_init(&tls->tls_session, GNUTLS_SERVER | GNUTLS_NONBLOCK); if (err != GNUTLS_E_SUCCESS) { kr_log_error("[tls] gnutls_init(): %s (%d)\n", gnutls_strerror_name(err), err); tls_free(tls); return NULL; } tls->credentials = tls_credentials_reserve(net->tls_credentials); - err = gnutls_credentials_set(tls->session, GNUTLS_CRD_CERTIFICATE, tls->credentials->credentials); + err = gnutls_credentials_set(tls->tls_session, GNUTLS_CRD_CERTIFICATE, tls->credentials->credentials); if (err != GNUTLS_E_SUCCESS) { kr_log_error("[tls] gnutls_credentials_set(): %s (%d)\n", gnutls_strerror_name(err), err); tls_free(tls); return NULL; } - if (kres_gnutls_set_priority(tls->session) != GNUTLS_E_SUCCESS) { + if (kres_gnutls_set_priority(tls->tls_session) != GNUTLS_E_SUCCESS) { tls_free(tls); return NULL; } tls->worker = worker; - gnutls_transport_set_pull_function(tls->session, kres_gnutls_pull); - gnutls_transport_set_push_function(tls->session, worker_gnutls_push); - gnutls_transport_set_ptr(tls->session, tls); + gnutls_transport_set_pull_function(tls->tls_session, kres_gnutls_pull); + gnutls_transport_set_push_function(tls->tls_session, worker_gnutls_push); + gnutls_transport_set_ptr(tls->tls_session, tls); return tls; } +void tls_close(struct tls_ctx_t *ctx) +{ + if (ctx == NULL || ctx->tls_session == NULL) { + return; + } + + assert(ctx->session); + + if (ctx->handshake_done) { + DEBUG_MSG("[tls] closing tls connection to `%s`\n", + kr_straddr(&ctx->session->peer.ip)); + ctx->handshake_done = false; + gnutls_bye(ctx->tls_session, GNUTLS_SHUT_RDWR); + } +} + void tls_free(struct tls_ctx_t *tls) { if (!tls) { @@ -202,15 +179,16 @@ void tls_free(struct tls_ctx_t *tls) if (tls->session) { /* Don't terminate TLS connection, just tear it down */ - gnutls_deinit(tls->session); - tls->session = NULL; + gnutls_deinit(tls->tls_session); + tls->tls_session = NULL; } tls_credentials_release(tls->credentials); free(tls); } -int tls_push(struct qr_task *task, uv_handle_t *handle, knot_pkt_t *pkt) +int tls_push(struct qr_task *task, uv_handle_t *handle, knot_pkt_t *pkt, + bool server_side) { if (!pkt || !handle || !handle->data) { return kr_error(EINVAL); @@ -218,51 +196,66 @@ int tls_push(struct qr_task *task, uv_handle_t *handle, knot_pkt_t *pkt) struct session *session = handle->data; const uint16_t pkt_size = htons(pkt->size); - struct tls_ctx_t *tls_p = session->tls_ctx; - if (!tls_p) { - kr_log_error("[tls] no tls context on push\n"); - return kr_error(ENOENT); + static char const server_logstring[] = "tls"; + static char const client_logstring[] = "tls-client"; + const char *logstring = client_logstring; + gnutls_session_t tls_session = NULL; + + if (server_side) { + logstring = server_logstring; + if (!session->tls_ctx) { + kr_log_error("[%s] no tls context on push\n", logstring); + return kr_error(ENOENT); + } + session->tls_ctx->task = task; + tls_session = session->tls_ctx->tls_session; + } else { + if (!session->tls_client_ctx) { + kr_log_error("[%s] no tls context on push\n", logstring); + return kr_error(ENOENT); + } + session->tls_client_ctx->task = task; + tls_session = session->tls_client_ctx->tls_session; } - assert(gnutls_record_check_corked(tls_p->session) == 0); + assert(gnutls_record_check_corked(tls_session) == 0); - tls_p->task = task; - - gnutls_record_cork(tls_p->session); + gnutls_record_cork(tls_session); ssize_t count = 0; - if ((count = gnutls_record_send(tls_p->session, &pkt_size, sizeof(pkt_size)) < 0) || - (count = gnutls_record_send(tls_p->session, pkt->wire, pkt->size) < 0)) { - kr_log_error("[tls] gnutls_record_send failed: %s (%zd)\n", gnutls_strerror_name(count), count); + if ((count = gnutls_record_send(tls_session, &pkt_size, sizeof(pkt_size)) < 0) || + (count = gnutls_record_send(tls_session, pkt->wire, pkt->size) < 0)) { + kr_log_error("[%s] gnutls_record_send failed: %s (%zd)\n", + logstring, gnutls_strerror_name(count), count); return kr_error(EIO); } ssize_t submitted = 0; ssize_t retries = 0; do { - count = gnutls_record_uncork(tls_p->session, 0); + count = gnutls_record_uncork(tls_session, 0); if (count < 0) { if (gnutls_error_is_fatal(count)) { - kr_log_error("[tls] gnutls_record_uncork failed: %s (%zd)\n", - gnutls_strerror_name(count), count); + kr_log_error("[%s] gnutls_record_uncork failed: %s (%zd)\n", + logstring, gnutls_strerror_name(count), count); return kr_error(EIO); } if (++retries > TLS_MAX_UNCORK_RETRIES) { - kr_log_error("[tls] gnutls_record_uncork: too many sequential non-fatal errors (%zd), last error is: %s (%zd)\n", - retries, gnutls_strerror_name(count), count); + kr_log_error("[%s] gnutls_record_uncork: too many sequential non-fatal errors (%zd), last error is: %s (%zd)\n", + logstring, retries, gnutls_strerror_name(count), count); return kr_error(EIO); } } else if (count != 0) { submitted += count; retries = 0; - } else if (gnutls_record_check_corked(tls_p->session) != 0) { + } else if (gnutls_record_check_corked(tls_session) != 0) { if (++retries > TLS_MAX_UNCORK_RETRIES) { - kr_log_error("[tls] gnutls_record_uncork: too many retries (%zd)\n", - retries); + kr_log_error("[%s] gnutls_record_uncork: too many retries (%zd)\n", + logstring, retries); return kr_error(EIO); } } else if (submitted != sizeof(pkt_size) + pkt->size) { - kr_log_error("[tls] gnutls_record_uncork didn't send all data(%zd of %zd)\n", - submitted, sizeof(pkt_size) + pkt->size); + kr_log_error("[%s] gnutls_record_uncork didn't send all data(%zd of %zd)\n", + logstring, submitted, sizeof(pkt_size) + pkt->size); return kr_error(EIO); } } while (submitted != sizeof(pkt_size) + pkt->size); @@ -278,15 +271,18 @@ int tls_process(struct worker_ctx *worker, uv_stream_t *handle, const uint8_t *b return kr_error(ENOSYS); } + assert(tls_p->session == session); + tls_p->buf = buf; tls_p->nread = nread >= 0 ? nread : 0; - tls_p->handle = handle; tls_p->consumed = 0; /* TODO: doesn't handle split TLS records */ /* Ensure TLS handshake is performed before receiving data. */ while (!tls_p->handshake_done) { - int err = gnutls_handshake(tls_p->session); + int err = gnutls_handshake(tls_p->tls_session); if (err == GNUTLS_E_SUCCESS) { + DEBUG_MSG("[tls] TLS handshake with %s has completed\n", + kr_straddr(&session->peer.ip)); tls_p->handshake_done = true; } else if (err == GNUTLS_E_AGAIN) { return 0; /* No data, bail out */ @@ -297,7 +293,7 @@ int tls_process(struct worker_ctx *worker, uv_stream_t *handle, const uint8_t *b int submitted = 0; while (true) { - ssize_t count = gnutls_record_recv(tls_p->session, tls_p->recv_buf, sizeof(tls_p->recv_buf)); + ssize_t count = gnutls_record_recv(tls_p->tls_session, tls_p->recv_buf, sizeof(tls_p->recv_buf)); if (count == GNUTLS_E_AGAIN) { break; /* No data available */ } else if (count == GNUTLS_E_INTERRUPTED) { @@ -806,38 +802,13 @@ static int client_verify_certificate(gnutls_session_t tls_session) return GNUTLS_E_CERTIFICATE_ERROR; } -static ssize_t kres_gnutls_client_push(gnutls_transport_ptr_t h, const void *buf, size_t len) -{ - struct tls_client_ctx_t *t = (struct tls_client_ctx_t *)h; - const uv_buf_t ub = {(void *)buf, len}; - - DEBUG_MSG("[tls_client] push %zu <%p>\n", len, h); - if (t == NULL) { - errno = EFAULT; - return -1; - } - - int ret = uv_try_write((uv_stream_t *)t->session->handle, &ub, 1); - if (ret > 0) { - return (ssize_t) ret; - } - if (ret == UV_EAGAIN) { - errno = EAGAIN; - } else { - kr_log_error("[tls_client] uv_try_write: %s\n", uv_strerror(ret)); - errno = EIO; - } - return -1; -} - - static ssize_t kres_gnutls_client_pull(gnutls_transport_ptr_t h, void *buf, size_t len) { struct tls_client_ctx_t *t = (struct tls_client_ctx_t *)h; assert(t != NULL); ssize_t avail = t->nread - t->consumed; - DEBUG_MSG("[tls] pull wanted: %zu available: %zu\n", len, avail); + DEBUG_MSG("[tls_client] pull wanted: %zu available: %zu\n", len, avail); if (t->nread <= t->consumed) { errno = EAGAIN; return -1; @@ -933,7 +904,8 @@ int tls_client_process(struct worker_ctx *worker, uv_stream_t *handle, const uin if (ctx->handshake_cb) { ctx->handshake_cb(ctx->session, 0); } - DEBUG_MSG("[tls_client] TLS handshake with %s has completed.\n", kr_straddr(&session->peer.ip)); + DEBUG_MSG("[tls_client] TLS handshake with %s has completed.\n", + kr_straddr(&session->peer.ip)); } int submitted = 0; @@ -961,7 +933,8 @@ int tls_client_process(struct worker_ctx *worker, uv_stream_t *handle, const uin return submitted; } -struct tls_client_ctx_t *tls_client_ctx_new(const struct tls_client_paramlist_entry *entry) +struct tls_client_ctx_t *tls_client_ctx_new(const struct tls_client_paramlist_entry *entry, + struct worker_ctx *worker) { struct tls_client_ctx_t *ctx = calloc(1, sizeof (struct tls_client_ctx_t)); if (!ctx) { @@ -987,8 +960,10 @@ struct tls_client_ctx_t *tls_client_ctx_new(const struct tls_client_paramlist_en return NULL; } + ctx->worker = worker; + gnutls_transport_set_pull_function(ctx->tls_session, kres_gnutls_client_pull); - gnutls_transport_set_push_function(ctx->tls_session, kres_gnutls_client_push); + gnutls_transport_set_push_function(ctx->tls_session, worker_gnutls_client_push); gnutls_transport_set_ptr(ctx->tls_session, ctx); return ctx; } @@ -999,8 +974,9 @@ void tls_client_ctx_free(struct tls_client_ctx_t *ctx) return; } - if (ctx->session != NULL) { + if (ctx->tls_session != NULL) { gnutls_deinit(ctx->tls_session); + ctx->tls_session = NULL; } free (ctx); @@ -1035,11 +1011,16 @@ int tls_client_connect_start(struct tls_client_ctx_t *ctx, void tls_client_close(struct tls_client_ctx_t *ctx) { - if (ctx == NULL || ctx->session == NULL) { + if (ctx == NULL || ctx->tls_session == NULL) { return; } + assert(ctx->session); + if (ctx->handshake_state == TLS_HS_DONE) { + DEBUG_MSG("[tls client] closing tls connection to `%s`\n", + kr_straddr(&ctx->session->peer.ip)); + ctx->handshake_state = TLS_HS_CLOSING; gnutls_bye(ctx->tls_session, GNUTLS_SHUT_RDWR); } } diff --git a/daemon/tls.h b/daemon/tls.h index 45ffcb142cc286ae85790bc1e6a4f8d9e64e7fcc..7285b40482e0135ba4c8b55e0a6cbc690dd50abf 100644 --- a/daemon/tls.h +++ b/daemon/tls.h @@ -49,10 +49,10 @@ struct qr_task; /* gnutls_record_recv and gnutls_record_send */ struct tls_ctx_t { - gnutls_session_t session; + gnutls_session_t tls_session; bool handshake_done; - uv_stream_t *handle; + struct session *session; /* for reading from the network */ const uint8_t *buf; @@ -68,19 +68,39 @@ typedef enum tls_client_hs_state { TLS_HS_NOT_STARTED = 0, TLS_HS_IN_PROGRESS, TLS_HS_DONE, + TLS_HS_CLOSING, TLS_HS_LAST } tls_client_hs_state_t; typedef int (*tls_handshake_cb) (struct session *session, int status); +struct tls_client_ctx_t { + gnutls_session_t tls_session; + tls_client_hs_state_t handshake_state; + + struct session *session; + tls_handshake_cb handshake_cb; + const uint8_t *buf; + ssize_t nread; + ssize_t consumed; + uint8_t recv_buf[4096]; + const struct tls_client_paramlist_entry *params; + struct worker_ctx *worker; + struct qr_task *task; +}; + /*! Create an empty TLS context in query context */ struct tls_ctx_t* tls_new(struct worker_ctx *worker); -/*! Close a TLS context */ +/*! Close a TLS context (call gnutls_bye()) */ +void tls_close(struct tls_ctx_t *tls); + +/*! Release a TLS context */ void tls_free(struct tls_ctx_t* tls); /*! Push new data to TLS context for sending */ -int tls_push(struct qr_task *task, uv_handle_t* handle, knot_pkt_t * pkt); +int tls_push(struct qr_task *task, uv_handle_t* handle, knot_pkt_t * pkt, + bool server_side); /*! Unwrap incoming data from a TLS stream and pass them to TCP session. * @return the number of newly-completed requests (>=0) or an error code @@ -115,7 +135,8 @@ int tls_client_params_set(map_t *tls_client_paramlist, int tls_client_params_free(map_t *tls_client_paramlist); /*! Allocate new client TLS context */ -struct tls_client_ctx_t *tls_client_ctx_new(const struct tls_client_paramlist_entry *entry); +struct tls_client_ctx_t *tls_client_ctx_new(const struct tls_client_paramlist_entry *entry, + struct worker_ctx *worker); int tls_client_process(struct worker_ctx *worker, uv_stream_t *handle, const uint8_t *buf, ssize_t nread); diff --git a/daemon/worker.c b/daemon/worker.c index e35524b1471e6ef75bb7ef81dbf62cb373195e9f..df73a5babe5e680523488a309ddb29d853d167a4 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -369,6 +369,10 @@ static void session_close(struct session *session) if (session->tls_client_ctx) { tls_client_close(session->tls_client_ctx); } + if (session->tls_ctx) { + tls_close(session->tls_ctx); + } + session->timeout.data = session; uv_close((uv_handle_t *)&session->timeout, on_session_timer_close); } @@ -870,7 +874,7 @@ static void on_send(uv_udp_send_t *req, int status) iorequest_release(worker, req); } -void on_write(uv_write_t *req, int status) +static void on_task_write(uv_write_t *req, int status) { uv_handle_t *handle = (uv_handle_t *)(req->handle); uv_loop_t *loop = handle->loop; @@ -882,10 +886,21 @@ void on_write(uv_write_t *req, int status) iorequest_release(worker, req); } +static void on_nontask_write(uv_write_t *req, int status) +{ + uv_handle_t *handle = (uv_handle_t *)(req->handle); + uv_loop_t *loop = handle->loop; + struct worker_ctx *worker = loop->data; + assert(worker == get_worker()); + iorequest_release(worker, req); +} + ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len) { struct tls_ctx_t *t = (struct tls_ctx_t *)h; - const uv_buf_t ub = {(void *)buf, len}; + const uv_buf_t uv_buf[1] = { + { (char *)buf, len } + }; VERBOSE_MSG(NULL,"[tls] push %zu <%p>\n", len, h); if (t == NULL) { @@ -893,27 +908,69 @@ ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len return -1; } - assert(t->handle); - assert(t->handle->type == UV_TCP); + assert(t->session && t->session->handle && + t->session->handle->type == UV_TCP); + + struct worker_ctx *worker = t->worker; + assert(worker); - if (!t->handshake_done) { - int ret = uv_try_write(t->handle, &ub, 1); - if (ret > 0) { - return (ssize_t) ret; + void *ioreq = worker_iohandle_borrow(worker); + if (!ioreq) { + errno = EFAULT; + return -1; + } + + uv_write_t *write_req = (uv_write_t *)ioreq; + + struct qr_task *task = t->task; + uv_write_cb write_cb = on_task_write; + if (t->handshake_done) { + assert(task); + } else { + task = NULL; + write_cb = on_nontask_write; + } + + write_req->data = task; + + ssize_t ret = -1; + int res = uv_write(write_req, (uv_stream_t *)t->session->handle, uv_buf, 1, write_cb); + if (res == 0) { + if (task) { + qr_task_ref(task); /* Pending ioreq on current task */ } - if (ret == UV_EAGAIN) { - errno = EAGAIN; - } else { - kr_log_error("[tls] uv_try_write: %s\n", uv_strerror(ret)); - errno = EIO; + if (worker->too_many_open && + worker->stats.rconcurrent < + worker->rconcurrent_highwatermark - 10) { + worker->too_many_open = false; } + ret = len; + } else { + VERBOSE_MSG(NULL,"[tls] uv_write: %s\n", uv_strerror(res)); + iorequest_release(worker, ioreq); + errno = EIO; + /* TODO ret == UV_EMFILE */ + } + return ret; +} + +ssize_t worker_gnutls_client_push(gnutls_transport_ptr_t h, const void *buf, size_t len) +{ + struct tls_client_ctx_t *t = (struct tls_client_ctx_t *)h; + const uv_buf_t uv_buf[1] = { + { (char *)buf, len } + }; + + VERBOSE_MSG(NULL,"[tls client] push %zu <%p>\n", len, h); + if (t == NULL) { + errno = EFAULT; return -1; } + assert(t->session && t->session->handle && + t->session->handle->type == UV_TCP); struct worker_ctx *worker = t->worker; - struct qr_task *task = t->task; - - assert(worker && task); + assert(worker); void *ioreq = worker_iohandle_borrow(worker); if (!ioreq) { @@ -922,16 +979,24 @@ ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len } uv_write_t *write_req = (uv_write_t *)ioreq; - uv_buf_t uv_buf[1] = { - { (char *)buf, len } - }; + + struct qr_task *task = t->task; + uv_write_cb write_cb = on_task_write; + if (t->handshake_state == TLS_HS_DONE) { + assert(task); + } else { + task = NULL; + write_cb = on_nontask_write; + } write_req->data = task; ssize_t ret = -1; - int res = uv_write(write_req, t->handle, uv_buf, 1, &on_write); + int res = uv_write(write_req, (uv_stream_t *)t->session->handle, uv_buf, 1, write_cb); if (res == 0) { - qr_task_ref(task); /* Pending ioreq on current task */ + if (task) { + qr_task_ref(task); /* Pending ioreq on current task */ + } if (worker->too_many_open && worker->stats.rconcurrent < worker->rconcurrent_highwatermark - 10) { @@ -939,7 +1004,7 @@ ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len } ret = len; } else { - VERBOSE_MSG(NULL,"[tls] uv_write: %s\n", uv_strerror(res)); + VERBOSE_MSG(NULL,"[tls_client] uv_write: %s\n", uv_strerror(res)); iorequest_release(worker, ioreq); errno = EIO; /* TODO ret == UV_EMFILE */ @@ -958,18 +1023,14 @@ static int qr_task_send(struct qr_task *task, uv_handle_t *handle, struct sockad assert(session->closing == false); if (session->has_tls) { struct kr_request *req = &task->ctx->req; - int ret = kr_ok(); - if (!session->outgoing) { - ret = tls_push(task, handle, pkt); - } else { - ret = kr_resolve_checkout(req, NULL, addr, - SOCK_STREAM, pkt); + if (session->outgoing) { + int ret = kr_resolve_checkout(req, NULL, addr, + SOCK_STREAM, pkt); if (ret != kr_ok()) { return ret; } - ret = tls_client_push(task, handle, pkt); } - return ret; + return tls_push(task, handle, pkt, !session->outgoing); } int ret = 0; @@ -1015,7 +1076,7 @@ static int qr_task_send(struct qr_task *task, uv_handle_t *handle, struct sockad { (char *)pkt->wire, pkt->size } }; write_req->data = task; - ret = uv_write(write_req, (uv_stream_t *)handle, buf, 2, &on_write); + ret = uv_write(write_req, (uv_stream_t *)handle, buf, 2, &on_task_write); } else { assert(false); } @@ -1698,7 +1759,7 @@ static int qr_task_step(struct qr_task *task, struct tls_client_paramlist_entry *entry = map_get(&net->tls_client_params, key); if (entry) { assert(session->tls_client_ctx == NULL); - struct tls_client_ctx_t *tls_ctx = tls_client_ctx_new(entry); + struct tls_client_ctx_t *tls_ctx = tls_client_ctx_new(entry, worker); if (!tls_ctx) { session_del_tasks(session, task); session_del_waiting(session, task); diff --git a/daemon/worker.h b/daemon/worker.h index cac2a624423814a7ba4caba27584704c7907b83b..1a8161ac5103cadbd7a60efc49a01ee8c23e091e 100644 --- a/daemon/worker.h +++ b/daemon/worker.h @@ -92,6 +92,8 @@ void worker_iohandle_release(struct worker_ctx *worker, void *h); ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len); +ssize_t worker_gnutls_client_push(gnutls_transport_ptr_t h, const void *buf, size_t len); + /** @cond internal */ /** Number of request within timeout window. */