From 92a74b08af8805f3db74c47cbe233a1f67417069 Mon Sep 17 00:00:00 2001 From: Grigorii Demidov <grigorii.demidov@nic.cz> Date: Thu, 30 Aug 2018 17:37:02 +0200 Subject: [PATCH 1/4] daemon/tls: avoid usage of gnutls_record_cork() \ gnutls_record_uncork(); use per-request io buffer --- daemon/tls.c | 42 +++++++++++++++--------------------------- daemon/worker.c | 41 ++++++++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/daemon/tls.c b/daemon/tls.c index 5424cfc59..d442b894b 100644 --- a/daemon/tls.c +++ b/daemon/tls.c @@ -215,53 +215,41 @@ int tls_push(struct qr_task *task, uv_handle_t *handle, knot_pkt_t *pkt) assert (tls_ctx); assert (session->outgoing == tls_ctx->client_side); - const uint16_t pkt_size = htons(pkt->size); + uint8_t *tcp_len_start = (uint8_t *)pkt->wire - 2; const char *logstring = tls_ctx->client_side ? client_logstring : server_logstring; gnutls_session_t tls_session = tls_ctx->tls_session; + const size_t size_to_send = pkt->size + 2; + ssize_t submitted = 0; + ssize_t retries = 0; tls_ctx->task = task; + knot_wire_write_u16(tcp_len_start, pkt->size); - assert(gnutls_record_check_corked(tls_session) == 0); - - gnutls_record_cork(tls_session); - ssize_t count = 0; - 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_session, 0); + ssize_t count = gnutls_record_send(tls_session, &tcp_len_start[submitted], size_to_send); if (count < 0) { if (gnutls_error_is_fatal(count)) { - kr_log_error("[%s] gnutls_record_uncork failed: %s (%zd)\n", + kr_log_error("[%s] gnutls_record_send failed: %s (%zd)\n", logstring, gnutls_strerror_name(count), count); + assert(false); return kr_error(EIO); } if (++retries > TLS_MAX_UNCORK_RETRIES) { - kr_log_error("[%s] gnutls_record_uncork: too many sequential non-fatal errors (%zd), last error is: %s (%zd)\n", + kr_log_error("[%s] gnutls_record_send: too many sequential non-fatal errors (%zd), last error is: %s (%zd)\n", logstring, retries, gnutls_strerror_name(count), count); + assert(false); return kr_error(EIO); } } else if (count != 0) { submitted += count; retries = 0; - } else if (gnutls_record_check_corked(tls_session) != 0) { - if (++retries > TLS_MAX_UNCORK_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("[%s] gnutls_record_uncork didn't send all data(%zd of %zd)\n", - logstring, submitted, sizeof(pkt_size) + pkt->size); + } else if (submitted != size_to_send) { + kr_log_error("[%s] gnutls_record_send didn't send all data(%zd of %zd)\n", + logstring, submitted, size_to_send); + assert(false); return kr_error(EIO); } - } while (submitted != sizeof(pkt_size) + pkt->size); + } while (submitted != size_to_send); return kr_ok(); } diff --git a/daemon/worker.c b/daemon/worker.c index 850d02f56..bae140f88 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -62,6 +62,7 @@ struct qr_task knot_pkt_t *pktbuf; qr_tasklist_t waiting; uv_handle_t *pending[MAX_PENDING]; + void *write_req_buf; uint16_t pending_count; uint16_t addrlist_count; uint16_t addrlist_turn; @@ -586,7 +587,11 @@ static int request_start(struct request_ctx *ctx, knot_pkt_t *query) } req->qsource.size = query->size; - req->answer = knot_pkt_new(NULL, answer_max, &req->pool); + /* Wire buffer + placeholder for tcp message length field. + * Placeholder allows to avoid usage of gnutls_record_cork() \ gnutls_record_cork() + * when TLS is used. */ + char *wire = mm_alloc(&req->pool, answer_max + 2); + req->answer = knot_pkt_new(wire + 2, answer_max, &req->pool); if (!req->answer) { return kr_error(ENOMEM); } @@ -690,8 +695,12 @@ static struct qr_task *qr_task_create(struct request_ctx *ctx) } memset(task, 0, sizeof(*task)); /* avoid accidentally unitialized fields */ - /* Create packet buffers for answer and subrequests */ - knot_pkt_t *pktbuf = knot_pkt_new(NULL, pktbuf_max, &ctx->req.pool); + /* Create packet buffers for answer and subrequests. + * Don't forget about placeholder for tcp message length field. + * This allows to avoid usage of gnutls_record_cork() \ gnutls_record_cork() + * when TLS is used. */ + char *wire = mm_alloc(&ctx->req.pool, pktbuf_max + 2); + knot_pkt_t *pktbuf = knot_pkt_new(wire + 2, pktbuf_max, &ctx->req.pool); if (!pktbuf) { mm_free(&ctx->req.pool, task); return NULL; @@ -895,6 +904,10 @@ static void on_task_write(uv_write_t *req, int status) struct worker_ctx *worker = loop->data; assert(worker == get_worker()); struct qr_task *task = req->data; + if (task->write_req_buf) { + free(task->write_req_buf); + task->write_req_buf = NULL; + } qr_task_on_send(task, handle, status); qr_task_unref(task); iorequest_release(worker, req); @@ -904,6 +917,10 @@ 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; + if (req->data) { + free(req->data); + req->data = NULL; + } struct worker_ctx *worker = loop->data; assert(worker == get_worker()); iorequest_release(worker, req); @@ -912,8 +929,12 @@ static void on_nontask_write(uv_write_t *req, int status) ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len) { struct tls_common_ctx *t = (struct tls_common_ctx *)h; + + void *buf_local = malloc(len); + memcpy(buf_local, buf, len); + const uv_buf_t uv_buf[1] = { - { (char *)buf, len } + { (char *)buf_local, len } }; if (t == NULL) { @@ -942,13 +963,14 @@ ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len uv_write_cb write_cb = on_task_write; if (t->handshake_state == TLS_HS_DONE) { assert(task); + write_req->data = task; + task->write_req_buf = buf_local; } else { task = NULL; write_cb = on_nontask_write; + write_req->data = buf_local; } - 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) { @@ -2266,7 +2288,12 @@ int worker_process_tcp(struct worker_ctx *worker, uv_stream_t *handle, * Previous packet is allocated with mempool, so there's no need to free it manually. */ if (task->pktbuf->max_size < KNOT_WIRE_MAX_PKTSIZE) { knot_mm_t *pool = &task->pktbuf->mm; - pkt_buf = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, pool); + /* Allocate wire buffer + placeholder for tcp message length field. + * Placeholder allows to avoid usage of + * gnutls_record_cork() \ gnutls_record_cork() + * when TLS is used. */ + char *wire = mm_alloc(pool, KNOT_WIRE_MAX_PKTSIZE + 2); + pkt_buf = knot_pkt_new(wire + 2, KNOT_WIRE_MAX_PKTSIZE, pool); if (!pkt_buf) { return kr_error(ENOMEM); } -- GitLab From 34ab5025e41f2655e1bb29000a7ad18ceda73778 Mon Sep 17 00:00:00 2001 From: Grigorii Demidov <grigorii.demidov@nic.cz> Date: Fri, 31 Aug 2018 15:26:49 +0200 Subject: [PATCH 2/4] daemon/tls: minor refactoring of tls_push() --- daemon/tls.c | 42 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/daemon/tls.c b/daemon/tls.c index d442b894b..e034b0594 100644 --- a/daemon/tls.c +++ b/daemon/tls.c @@ -208,6 +208,7 @@ int tls_push(struct qr_task *task, uv_handle_t *handle, knot_pkt_t *pkt) return kr_error(EINVAL); } + int ret = kr_ok(); struct session *session = handle->data; struct tls_common_ctx *tls_ctx = session->outgoing ? &session->tls_client_ctx->c : &session->tls_ctx->c; @@ -219,39 +220,24 @@ int tls_push(struct qr_task *task, uv_handle_t *handle, knot_pkt_t *pkt) const char *logstring = tls_ctx->client_side ? client_logstring : server_logstring; gnutls_session_t tls_session = tls_ctx->tls_session; const size_t size_to_send = pkt->size + 2; - ssize_t submitted = 0; - ssize_t retries = 0; tls_ctx->task = task; knot_wire_write_u16(tcp_len_start, pkt->size); - do { - ssize_t count = gnutls_record_send(tls_session, &tcp_len_start[submitted], size_to_send); - if (count < 0) { - if (gnutls_error_is_fatal(count)) { - kr_log_error("[%s] gnutls_record_send failed: %s (%zd)\n", - logstring, gnutls_strerror_name(count), count); - assert(false); - return kr_error(EIO); - } - if (++retries > TLS_MAX_UNCORK_RETRIES) { - kr_log_error("[%s] gnutls_record_send: too many sequential non-fatal errors (%zd), last error is: %s (%zd)\n", - logstring, retries, gnutls_strerror_name(count), count); - assert(false); - return kr_error(EIO); - } - } else if (count != 0) { - submitted += count; - retries = 0; - } else if (submitted != size_to_send) { - kr_log_error("[%s] gnutls_record_send didn't send all data(%zd of %zd)\n", - logstring, submitted, size_to_send); - assert(false); - return kr_error(EIO); - } - } while (submitted != size_to_send); + /* gnutls_record_send() calls worker_gnutls_push() which actually sends data. + * It either sends all the data either sends nothing and returns error. */ + ssize_t count = gnutls_record_send(tls_session, tcp_len_start, size_to_send); + if (count < 0) { + kr_log_error("[%s] gnutls_record_send failed: %s (%zd)\n", + logstring, gnutls_strerror_name(count), count); + ret = kr_error(EIO); + } else if (count != size_to_send) { + kr_log_error("[%s] gnutls_record_send didn't send all data (%zd of %zd)\n", + logstring, count, size_to_send); + ret = kr_error(EIO); + } - return kr_ok(); + return ret; } int tls_process(struct worker_ctx *worker, uv_stream_t *handle, const uint8_t *buf, ssize_t nread) -- GitLab From 1d4f501cfc5b9715b9b5af177868fe33b413c971 Mon Sep 17 00:00:00 2001 From: Grigorii Demidov <grigorii.demidov@nic.cz> Date: Fri, 31 Aug 2018 16:02:44 +0200 Subject: [PATCH 3/4] daemon/worker: in some circumstances task can be finalized twice, fixed --- daemon/worker.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/daemon/worker.c b/daemon/worker.c index bae140f88..7cd0e983c 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -853,25 +853,26 @@ static int qr_task_on_send(struct qr_task *task, uv_handle_t *handle, int status if (ret != kr_ok()) { while (session->waiting.len > 0) { struct qr_task *t = session->waiting.at[0]; + array_del(session->waiting, 0); + session_del_tasks(session, t); if (session->outgoing) { qr_task_finalize(t, KR_STATE_FAIL); } else { assert(t->ctx->source.session == session); t->ctx->source.session = NULL; } - array_del(session->waiting, 0); - session_del_tasks(session, t); qr_task_unref(t); } while (session->tasks.len > 0) { struct qr_task *t = session->tasks.at[0]; + array_del(session->tasks, 0); if (session->outgoing) { qr_task_finalize(t, KR_STATE_FAIL); } else { assert(t->ctx->source.session == session); t->ctx->source.session = NULL; } - session_del_tasks(session, t); + qr_task_unref(t); } session_close(session); return status; @@ -1767,8 +1768,9 @@ static int qr_task_step(struct qr_task *task, session_del_tasks(session, task); while (session->tasks.len != 0) { struct qr_task *t = session->tasks.at[0]; + array_del(session->tasks, 0); qr_task_finalize(t, KR_STATE_FAIL); - session_del_tasks(session, t); + qr_task_unref(t); } subreq_finalize(task, packet_source, packet); session_close(session); @@ -1784,8 +1786,9 @@ static int qr_task_step(struct qr_task *task, session_del_tasks(session, task); while (session->tasks.len != 0) { struct qr_task *t = session->tasks.at[0]; + array_del(session->tasks, 0); qr_task_finalize(t, KR_STATE_FAIL); - session_del_tasks(session, t); + qr_task_unref(t); } subreq_finalize(task, packet_source, packet); session_close(session); -- GitLab From 6a1b4b92cf025e2ff9ef361356fde8fa424a491a Mon Sep 17 00:00:00 2001 From: Grigorii Demidov <grigorii.demidov@nic.cz> Date: Mon, 3 Sep 2018 17:00:04 +0200 Subject: [PATCH 4/4] daemon: fix memory leaks --- daemon/worker.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/daemon/worker.c b/daemon/worker.c index 7cd0e983c..219e19acc 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -728,6 +728,10 @@ static void qr_task_free(struct qr_task *task) assert(ctx); + if (task->write_req_buf != NULL) { + free(task->write_req_buf); + } + /* Process outbound session. */ struct session *source_session = ctx->source.session; struct worker_ctx *worker = ctx->worker; @@ -930,6 +934,10 @@ static void on_nontask_write(uv_write_t *req, int status) ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len) { struct tls_common_ctx *t = (struct tls_common_ctx *)h; + if (t == NULL) { + errno = EFAULT; + return -1; + } void *buf_local = malloc(len); memcpy(buf_local, buf, len); @@ -938,11 +946,6 @@ ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len { (char *)buf_local, len } }; - if (t == NULL) { - errno = EFAULT; - return -1; - } - assert(t->session && t->session->handle && t->session->handle->type == UV_TCP); @@ -955,6 +958,7 @@ ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len void *ioreq = worker_iohandle_borrow(worker); if (!ioreq) { errno = EFAULT; + free(buf_local); return -1; } @@ -964,6 +968,9 @@ ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len uv_write_cb write_cb = on_task_write; if (t->handshake_state == TLS_HS_DONE) { assert(task); + if (task->write_req_buf != NULL) { + free(task->write_req_buf); + } write_req->data = task; task->write_req_buf = buf_local; } else { @@ -998,6 +1005,7 @@ ssize_t worker_gnutls_push(gnutls_transport_ptr_t h, const void *buf, size_t len VERBOSE_MSG(NULL,"[%s] uv_write: %s\n", t->client_side ? "tls_client" : "tls", uv_strerror(res)); iorequest_release(worker, ioreq); + free(buf_local); errno = EIO; } return ret; -- GitLab