diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-07-17 07:11:12 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-07-17 07:11:12 +0000 |
commit | b72837827d9290c7ec50b9e4b9e1a0f194ddccca (patch) | |
tree | 8df663325413d0f92086d740638555de6b779df7 /src/output/dnssim | |
parent | Adding upstream version 1.1.0+debian. (diff) | |
download | dnsjit-b72837827d9290c7ec50b9e4b9e1a0f194ddccca.tar.xz dnsjit-b72837827d9290c7ec50b9e4b9e1a0f194ddccca.zip |
Adding upstream version 1.2.1.upstream/1.2.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/output/dnssim')
-rw-r--r-- | src/output/dnssim/CHANGELOG.md | 16 | ||||
-rw-r--r-- | src/output/dnssim/common.c | 384 | ||||
-rw-r--r-- | src/output/dnssim/connection.c | 471 | ||||
-rw-r--r-- | src/output/dnssim/https2.c | 592 | ||||
-rw-r--r-- | src/output/dnssim/internal.h | 343 | ||||
-rw-r--r-- | src/output/dnssim/ll.h | 83 | ||||
-rw-r--r-- | src/output/dnssim/tcp.c | 356 | ||||
-rw-r--r-- | src/output/dnssim/tls.c | 475 | ||||
-rw-r--r-- | src/output/dnssim/udp.c | 156 |
9 files changed, 0 insertions, 2876 deletions
diff --git a/src/output/dnssim/CHANGELOG.md b/src/output/dnssim/CHANGELOG.md deleted file mode 100644 index 9cbfa55..0000000 --- a/src/output/dnssim/CHANGELOG.md +++ /dev/null @@ -1,16 +0,0 @@ -dnssim v20210129 -================ - -- Added DNS-over-HTTPS support with https2() -- Added IPv4 support -- Abort operation on insufficient file descriptors -- Match QUESTION section of received responses -- Improvements in connection state handling -- Deprecate udp_only() in favor of udp() -- Allow setting logger name with log(name) -- Added check_version() and check_json_version() - -dnssim v20200723 -================ - -- First released dnssim version with UDP, TCP and DoT support diff --git a/src/output/dnssim/common.c b/src/output/dnssim/common.c deleted file mode 100644 index e170aec..0000000 --- a/src/output/dnssim/common.c +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o. - * All rights reserved. - * - * This file is part of dnsjit. - * - * dnsjit is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * dnsjit is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with dnsjit. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include "output/dnssim.h" -#include "output/dnssim/internal.h" -#include "output/dnssim/ll.h" -#include "core/assert.h" - -#include <string.h> - -#define MAX_LABELS 127 - -static core_log_t _log = LOG_T_INIT("output.dnssim"); - -static void _close_request(_output_dnssim_request_t* req); - -static void _on_request_timeout(uv_timer_t* handle) -{ - _close_request((_output_dnssim_request_t*)handle->data); -} - -static ssize_t parse_qsection(core_object_dns_t* dns) -{ - core_object_dns_q_t q; - static core_object_dns_label_t labels[MAX_LABELS]; - const uint8_t* start; - int i; - int ret; - - if (!dns || !dns->have_qdcount) - return -1; - - start = dns->at; - - for (i = 0; i < dns->qdcount; i++) { - ret = core_object_dns_parse_q(dns, &q, labels, MAX_LABELS); - if (ret < 0) - return -1; - } - - return (dns->at - start); -} - -int _output_dnssim_answers_request(_output_dnssim_request_t* req, core_object_dns_t* response) -{ - const uint8_t* question; - ssize_t len; - - if (!response->have_id || !response->have_qdcount) - return _ERR_MALFORMED; - - if (req->dns_q->id != response->id) - return _ERR_MSGID; - - if (req->dns_q->qdcount != response->qdcount) - return _ERR_QUESTION; - - question = response->at; - len = parse_qsection(response); - - if (req->question_len != len) - return _ERR_QUESTION; - - if (memcmp(req->question, question, len) != 0) - return _ERR_QUESTION; - - return 0; -} - -void _output_dnssim_create_request(output_dnssim_t* self, _output_dnssim_client_t* client, core_object_payload_t* payload) -{ - int ret; - _output_dnssim_request_t* req; - mlassert_self(); - lassert(client, "client is nil"); - lassert(payload, "payload is nil"); - - lfatal_oom(req = calloc(1, sizeof(_output_dnssim_request_t))); - req->dnssim = self; - req->client = client; - req->payload = payload; - req->dns_q = core_object_dns_new(); - req->dns_q->obj_prev = (core_object_t*)req->payload; - req->dnssim->ongoing++; - req->state = _OUTPUT_DNSSIM_REQ_ONGOING; - req->stats = self->stats_current; - - ret = core_object_dns_parse_header(req->dns_q); - if (ret != 0) { - ldebug("discarded malformed dns query: couldn't parse header"); - goto failure; - } - - req->question = req->dns_q->at; - req->question_len = parse_qsection(req->dns_q); - if (req->question_len < 0) { - ldebug("discarded malformed dns query: invalid question"); - goto failure; - } - - req->dnssim->stats_sum->requests++; - req->stats->requests++; - - switch (_self->transport) { - case OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY: - case OUTPUT_DNSSIM_TRANSPORT_UDP: - ret = _output_dnssim_create_query_udp(self, req); - break; - case OUTPUT_DNSSIM_TRANSPORT_TCP: - ret = _output_dnssim_create_query_tcp(self, req); - break; - case OUTPUT_DNSSIM_TRANSPORT_TLS: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - ret = _output_dnssim_create_query_tls(self, req); -#else - lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - case OUTPUT_DNSSIM_TRANSPORT_HTTPS2: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - ret = _output_dnssim_create_query_https2(self, req); -#else - lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - default: - lfatal("unsupported dnssim transport"); - break; - } - if (ret < 0) { - goto failure; - } - - req->created_at = uv_now(&_self->loop); - req->ended_at = req->created_at + self->timeout_ms; - lfatal_oom(req->timer = malloc(sizeof(uv_timer_t))); - uv_timer_init(&_self->loop, req->timer); - req->timer->data = req; - uv_timer_start(req->timer, _on_request_timeout, self->timeout_ms, 0); - - return; -failure: - self->discarded++; - _close_request(req); - return; -} - -/* Bind before connect to be able to send from different source IPs. */ -int _output_dnssim_bind_before_connect(output_dnssim_t* self, uv_handle_t* handle) -{ - mlassert_self(); - lassert(handle, "handle is nil"); - - if (_self->source != NULL) { - struct sockaddr* addr = (struct sockaddr*)&_self->source->addr; - struct sockaddr* dest = (struct sockaddr*)&_self->target; - int ret = -1; - if (addr->sa_family != dest->sa_family) { - lfatal("failed to bind: source/desitnation address family mismatch"); - } - switch (handle->type) { - case UV_UDP: - ret = uv_udp_bind((uv_udp_t*)handle, addr, 0); - break; - case UV_TCP: - ret = uv_tcp_bind((uv_tcp_t*)handle, addr, 0); - break; - default: - lfatal("failed to bind: unsupported handle type"); - break; - } - if (ret < 0) { - /* This typically happens when we run out of file descriptors. - * Quit to prevent skewed results or unexpected behaviour. */ - lfatal("failed to bind: %s", uv_strerror(ret)); - return ret; - } - _self->source = _self->source->next; - } - return 0; -} - -void _output_dnssim_maybe_free_request(_output_dnssim_request_t* req) -{ - mlassert(req, "req is nil"); - - if (req->qry == NULL && req->timer == NULL) { - if (req->dnssim->free_after_use) { - core_object_payload_free(req->payload); - } - core_object_dns_free(req->dns_q); - free(req); - } -} - -static void _close_query(_output_dnssim_query_t* qry) -{ - mlassert(qry, "qry is nil"); - - switch (qry->transport) { - case OUTPUT_DNSSIM_TRANSPORT_UDP: - _output_dnssim_close_query_udp((_output_dnssim_query_udp_t*)qry); - break; - case OUTPUT_DNSSIM_TRANSPORT_TCP: - _output_dnssim_close_query_tcp((_output_dnssim_query_tcp_t*)qry); - break; - case OUTPUT_DNSSIM_TRANSPORT_TLS: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - _output_dnssim_close_query_tls((_output_dnssim_query_tcp_t*)qry); -#else - mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - case OUTPUT_DNSSIM_TRANSPORT_HTTPS2: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - _output_dnssim_close_query_https2((_output_dnssim_query_tcp_t*)qry); -#else - mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - default: - mlfatal("invalid query transport"); - break; - } -} - -static void _on_request_timer_closed(uv_handle_t* handle) -{ - _output_dnssim_request_t* req = (_output_dnssim_request_t*)handle->data; - mlassert(req, "req is nil"); - free(handle); - req->timer = NULL; - _output_dnssim_maybe_free_request(req); -} - -static void _close_request(_output_dnssim_request_t* req) -{ - if (req == NULL || req->state == _OUTPUT_DNSSIM_REQ_CLOSING) - return; - mlassert(req->state == _OUTPUT_DNSSIM_REQ_ONGOING, "request to be closed must be ongoing"); - req->state = _OUTPUT_DNSSIM_REQ_CLOSING; - req->dnssim->ongoing--; - - /* Calculate latency. */ - uint64_t latency; - req->ended_at = uv_now(&((_output_dnssim_t*)req->dnssim)->loop); - latency = req->ended_at - req->created_at; - if (latency > req->dnssim->timeout_ms) { - req->ended_at = req->created_at + req->dnssim->timeout_ms; - latency = req->dnssim->timeout_ms; - } - req->stats->latency[latency]++; - req->dnssim->stats_sum->latency[latency]++; - - if (req->timer != NULL) { - uv_timer_stop(req->timer); - uv_close((uv_handle_t*)req->timer, _on_request_timer_closed); - } - - /* Finish any queries in flight. */ - _output_dnssim_query_t* qry = req->qry; - if (qry != NULL) - _close_query(qry); - - _output_dnssim_maybe_free_request(req); -} - -void _output_dnssim_request_answered(_output_dnssim_request_t* req, core_object_dns_t* msg) -{ - mlassert(req, "req is nil"); - mlassert(msg, "msg is nil"); - - req->dnssim->stats_sum->answers++; - req->stats->answers++; - - switch (msg->rcode) { - case CORE_OBJECT_DNS_RCODE_NOERROR: - req->dnssim->stats_sum->rcode_noerror++; - req->stats->rcode_noerror++; - break; - case CORE_OBJECT_DNS_RCODE_FORMERR: - req->dnssim->stats_sum->rcode_formerr++; - req->stats->rcode_formerr++; - break; - case CORE_OBJECT_DNS_RCODE_SERVFAIL: - req->dnssim->stats_sum->rcode_servfail++; - req->stats->rcode_servfail++; - break; - case CORE_OBJECT_DNS_RCODE_NXDOMAIN: - req->dnssim->stats_sum->rcode_nxdomain++; - req->stats->rcode_nxdomain++; - break; - case CORE_OBJECT_DNS_RCODE_NOTIMP: - req->dnssim->stats_sum->rcode_notimp++; - req->stats->rcode_notimp++; - break; - case CORE_OBJECT_DNS_RCODE_REFUSED: - req->dnssim->stats_sum->rcode_refused++; - req->stats->rcode_refused++; - break; - case CORE_OBJECT_DNS_RCODE_YXDOMAIN: - req->dnssim->stats_sum->rcode_yxdomain++; - req->stats->rcode_yxdomain++; - break; - case CORE_OBJECT_DNS_RCODE_YXRRSET: - req->dnssim->stats_sum->rcode_yxrrset++; - req->stats->rcode_yxrrset++; - break; - case CORE_OBJECT_DNS_RCODE_NXRRSET: - req->dnssim->stats_sum->rcode_nxrrset++; - req->stats->rcode_nxrrset++; - break; - case CORE_OBJECT_DNS_RCODE_NOTAUTH: - req->dnssim->stats_sum->rcode_notauth++; - req->stats->rcode_notauth++; - break; - case CORE_OBJECT_DNS_RCODE_NOTZONE: - req->dnssim->stats_sum->rcode_notzone++; - req->stats->rcode_notzone++; - break; - case CORE_OBJECT_DNS_RCODE_BADVERS: - req->dnssim->stats_sum->rcode_badvers++; - req->stats->rcode_badvers++; - break; - case CORE_OBJECT_DNS_RCODE_BADKEY: - req->dnssim->stats_sum->rcode_badkey++; - req->stats->rcode_badkey++; - break; - case CORE_OBJECT_DNS_RCODE_BADTIME: - req->dnssim->stats_sum->rcode_badtime++; - req->stats->rcode_badtime++; - break; - case CORE_OBJECT_DNS_RCODE_BADMODE: - req->dnssim->stats_sum->rcode_badmode++; - req->stats->rcode_badmode++; - break; - case CORE_OBJECT_DNS_RCODE_BADNAME: - req->dnssim->stats_sum->rcode_badname++; - req->stats->rcode_badname++; - break; - case CORE_OBJECT_DNS_RCODE_BADALG: - req->dnssim->stats_sum->rcode_badalg++; - req->stats->rcode_badalg++; - break; - case CORE_OBJECT_DNS_RCODE_BADTRUNC: - req->dnssim->stats_sum->rcode_badtrunc++; - req->stats->rcode_badtrunc++; - break; - case CORE_OBJECT_DNS_RCODE_BADCOOKIE: - req->dnssim->stats_sum->rcode_badcookie++; - req->stats->rcode_badcookie++; - break; - default: - req->dnssim->stats_sum->rcode_other++; - req->stats->rcode_other++; - } - - _close_request(req); -} - -void _output_dnssim_on_uv_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) -{ - mlfatal_oom(buf->base = malloc(suggested_size)); - buf->len = suggested_size; -} diff --git a/src/output/dnssim/connection.c b/src/output/dnssim/connection.c deleted file mode 100644 index eeb1ce8..0000000 --- a/src/output/dnssim/connection.c +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright (c) 2020, CZ.NIC, z.s.p.o. - * All rights reserved. - * - * This file is part of dnsjit. - * - * dnsjit is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * dnsjit is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with dnsjit. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include "output/dnssim.h" -#include "output/dnssim/internal.h" -#include "output/dnssim/ll.h" -#include "core/assert.h" - -#include <string.h> - -static core_log_t _log = LOG_T_INIT("output.dnssim"); - -static bool _conn_is_connecting(_output_dnssim_connection_t* conn) -{ - return (conn->state >= _OUTPUT_DNSSIM_CONN_TCP_HANDSHAKE && conn->state <= _OUTPUT_DNSSIM_CONN_ACTIVE); -} - -void _output_dnssim_conn_maybe_free(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn can't be nil"); - mlassert(conn->client, "conn must belong to a client"); - if (conn->handle == NULL && conn->handshake_timer == NULL && conn->idle_timer == NULL) { - _ll_try_remove(conn->client->conn, conn); - if (conn->tls != NULL) { - free(conn->tls); - conn->tls = NULL; - } - if (conn->http2 != NULL) { - free(conn->http2); - conn->http2 = NULL; - } - free(conn); - } -} - -static void _on_handshake_timer_closed(uv_handle_t* handle) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)handle->data; - mlassert(conn, "conn is nil"); - mlassert(conn->handshake_timer, "conn must have handshake timer when closing it"); - free(conn->handshake_timer); - conn->handshake_timer = NULL; - _output_dnssim_conn_maybe_free(conn); -} - -static void _on_idle_timer_closed(uv_handle_t* handle) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)handle->data; - mlassert(conn, "conn is nil"); - mlassert(conn->idle_timer, "conn must have idle timer when closing it"); - free(conn->idle_timer); - conn->is_idle = false; - conn->idle_timer = NULL; - _output_dnssim_conn_maybe_free(conn); -} - -void _output_dnssim_conn_close(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn can't be nil"); - mlassert(conn->stats, "conn must have stats"); - mlassert(conn->client, "conn must have client"); - mlassert(conn->client->dnssim, "client must have dnssim"); - - output_dnssim_t* self = conn->client->dnssim; - - switch (conn->state) { - case _OUTPUT_DNSSIM_CONN_CLOSING: - case _OUTPUT_DNSSIM_CONN_CLOSED: - return; - case _OUTPUT_DNSSIM_CONN_TCP_HANDSHAKE: - case _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE: - conn->stats->conn_handshakes_failed++; - self->stats_sum->conn_handshakes_failed++; - break; - case _OUTPUT_DNSSIM_CONN_ACTIVE: - case _OUTPUT_DNSSIM_CONN_CONGESTED: - self->stats_current->conn_active--; - break; - case _OUTPUT_DNSSIM_CONN_INITIALIZED: - case _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED: - break; - default: - lfatal("unknown conn state: %d", conn->state); - } - if (conn->prevent_close) { - lassert(conn->state <= _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED, "conn already closing"); - conn->state = _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED; - return; - } - conn->state = _OUTPUT_DNSSIM_CONN_CLOSING; - - if (conn->handshake_timer != NULL) { - uv_timer_stop(conn->handshake_timer); - uv_close((uv_handle_t*)conn->handshake_timer, _on_handshake_timer_closed); - } - if (conn->idle_timer != NULL) { - conn->is_idle = false; - uv_timer_stop(conn->idle_timer); - uv_close((uv_handle_t*)conn->idle_timer, _on_idle_timer_closed); - } - - switch (_self->transport) { - case OUTPUT_DNSSIM_TRANSPORT_TCP: - _output_dnssim_tcp_close(conn); - break; - case OUTPUT_DNSSIM_TRANSPORT_TLS: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - _output_dnssim_tls_close(conn); -#else - lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - case OUTPUT_DNSSIM_TRANSPORT_HTTPS2: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - _output_dnssim_https2_close(conn); -#else - lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - default: - lfatal("unsupported transport"); - break; - } -} - -/* Close connection or run idle timer when there are no more outstanding queries. */ -void _output_dnssim_conn_idle(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn can't be nil"); - - if (conn->queued == NULL && conn->sent == NULL) { - if (conn->idle_timer == NULL) - _output_dnssim_conn_close(conn); - else if (!conn->is_idle) { - conn->is_idle = true; - uv_timer_again(conn->idle_timer); - } - } -} - -static void _send_pending_queries(_output_dnssim_connection_t* conn) -{ - _output_dnssim_query_tcp_t* qry; - mlassert(conn, "conn is nil"); - mlassert(conn->client, "conn->client is nil"); - qry = (_output_dnssim_query_tcp_t*)conn->client->pending; - - while (qry != NULL && conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE) { - _output_dnssim_query_tcp_t* next = (_output_dnssim_query_tcp_t*)qry->qry.next; - if (qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE) { - switch (qry->qry.transport) { - case OUTPUT_DNSSIM_TRANSPORT_TCP: - _output_dnssim_tcp_write_query(conn, qry); - break; - case OUTPUT_DNSSIM_TRANSPORT_TLS: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - _output_dnssim_tls_write_query(conn, qry); -#else - mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - case OUTPUT_DNSSIM_TRANSPORT_HTTPS2: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - _output_dnssim_https2_write_query(conn, qry); -#else - mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - default: - mlfatal("unsupported protocol"); - break; - } - } - qry = next; - } -} - -int _output_dnssim_handle_pending_queries(_output_dnssim_client_t* client) -{ - int ret = 0; - mlassert(client, "client is nil"); - - if (client->pending == NULL) - return ret; - - output_dnssim_t* self = client->dnssim; - mlassert(self, "client must belong to dnssim"); - - /* Get active connection or find out whether new connection has to be opened. */ - bool is_connecting = false; - _output_dnssim_connection_t* conn = client->conn; - while (conn != NULL) { - if (conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE) - break; - else if (_conn_is_connecting(conn)) - is_connecting = true; - conn = conn->next; - } - - if (conn != NULL) { /* Send data right away over active connection. */ - _send_pending_queries(conn); - } else if (!is_connecting) { /* No active or connecting connection -> open a new one. */ - lfatal_oom(conn = calloc(1, sizeof(_output_dnssim_connection_t))); - conn->state = _OUTPUT_DNSSIM_CONN_INITIALIZED; - conn->client = client; - conn->stats = self->stats_current; - if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_TLS) { -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - ret = _output_dnssim_tls_init(conn); - if (ret < 0) { - free(conn); - return ret; - } -#else - lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - } else if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2) { -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - ret = _output_dnssim_https2_init(conn); - if (ret < 0) { - free(conn); - return ret; - } -#else - lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - } - ret = _output_dnssim_tcp_connect(self, conn); - if (ret < 0) - return ret; - _ll_append(client->conn, conn); - } /* Otherwise, pending queries wil be sent after connected callback. */ - - return ret; -} - -void _output_dnssim_conn_activate(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn is nil"); - mlassert(conn->client, "conn must be associated with a client"); - mlassert(conn->client->dnssim, "client must be associated with dnssim"); - - uv_timer_stop(conn->handshake_timer); - - conn->state = _OUTPUT_DNSSIM_CONN_ACTIVE; - conn->client->dnssim->stats_current->conn_active++; - conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSLEN; - conn->dnsbuf_len = 2; - conn->dnsbuf_pos = 0; - conn->dnsbuf_free_after_use = false; - - _send_pending_queries(conn); - _output_dnssim_conn_idle(conn); -} - -int _process_dnsmsg(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn can't be nil"); - mlassert(conn->client, "conn must have client"); - mlassert(conn->client->dnssim, "client must have dnssim"); - - output_dnssim_t* self = conn->client->dnssim; - - core_object_payload_t payload = CORE_OBJECT_PAYLOAD_INIT(NULL); - core_object_dns_t dns_a = CORE_OBJECT_DNS_INIT(&payload); - - payload.payload = (uint8_t*)conn->dnsbuf_data; - payload.len = conn->dnsbuf_len; - - dns_a.obj_prev = (core_object_t*)&payload; - int ret = core_object_dns_parse_header(&dns_a); - if (ret != 0) { - lwarning("tcp response malformed"); - return _ERR_MALFORMED; - } - ldebug("tcp recv dnsmsg id: %04x", dns_a.id); - - _output_dnssim_query_t* qry; - - if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2) { - lassert(conn->http2, "conn must have http2 ctx"); - lassert(conn->http2->current_qry, "http2 has no current_qry"); - lassert(conn->http2->current_qry->qry.req, "current_qry has no req"); - lassert(conn->http2->current_qry->qry.req->dns_q, "req has no dns_q"); - - ret = _output_dnssim_answers_request(conn->http2->current_qry->qry.req, &dns_a); - switch (ret) { - case 0: - _output_dnssim_request_answered(conn->http2->current_qry->qry.req, &dns_a); - break; - case _ERR_MSGID: - lwarning("https2 QID mismatch: request=0x%04x, response=0x%04x", - conn->http2->current_qry->qry.req->dns_q->id, dns_a.id); - break; - case _ERR_QUESTION: - default: - lwarning("https2 response question mismatch"); - break; - } - } else { - qry = conn->sent; - while (qry != NULL) { - if (qry->req->dns_q->id == dns_a.id) { - ret = _output_dnssim_answers_request(qry->req, &dns_a); - if (ret != 0) { - lwarning("response question mismatch"); - } else { - _output_dnssim_request_answered(qry->req, &dns_a); - } - break; - } - qry = qry->next; - } - } - - return 0; -} - -static int _parse_dnsbuf_data(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn can't be nil"); - mlassert(conn->dnsbuf_pos == conn->dnsbuf_len, "attempt to parse incomplete dnsbuf_data"); - int ret = 0; - - switch (conn->read_state) { - case _OUTPUT_DNSSIM_READ_STATE_DNSLEN: { - uint16_t* p_dnslen = (uint16_t*)conn->dnsbuf_data; - conn->dnsbuf_len = ntohs(*p_dnslen); - if (conn->dnsbuf_len == 0) { - mlwarning("invalid dnslen received: 0"); - conn->dnsbuf_len = 2; - conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSLEN; - } else if (conn->dnsbuf_len < 12) { - mldebug("invalid dnslen received: %d", conn->dnsbuf_len); - ret = -1; - } else { - mldebug("dnslen: %d", conn->dnsbuf_len); - conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSMSG; - } - break; - } - case _OUTPUT_DNSSIM_READ_STATE_DNSMSG: - ret = _process_dnsmsg(conn); - if (ret) { - conn->read_state = _OUTPUT_DNSSIM_READ_STATE_INVALID; - } else { - conn->dnsbuf_len = 2; - conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSLEN; - } - break; - default: - mlfatal("tcp invalid connection read_state"); - break; - } - - conn->dnsbuf_pos = 0; - if (conn->dnsbuf_free_after_use) { - conn->dnsbuf_free_after_use = false; - free(conn->dnsbuf_data); - } - conn->dnsbuf_data = NULL; - - return ret; -} - -static unsigned int _read_dns_stream_chunk(_output_dnssim_connection_t* conn, size_t len, const char* data) -{ - mlassert(conn, "conn can't be nil"); - mlassert(data, "data can't be nil"); - mlassert(len > 0, "no data to read"); - mlassert((conn->read_state == _OUTPUT_DNSSIM_READ_STATE_DNSLEN || conn->read_state == _OUTPUT_DNSSIM_READ_STATE_DNSMSG), - "connection has invalid read_state"); - - int ret = 0; - unsigned int nread; - size_t expected = conn->dnsbuf_len - conn->dnsbuf_pos; - mlassert(expected > 0, "no data expected"); - - if (conn->dnsbuf_free_after_use == false && expected > len) { - /* Start of partial read. */ - mlassert(conn->dnsbuf_pos == 0, "conn->dnsbuf_pos must be 0 at start of partial read"); - mlassert(conn->dnsbuf_len > 0, "conn->dnsbuf_len must be set at start of partial read"); - mlfatal_oom(conn->dnsbuf_data = malloc(conn->dnsbuf_len * sizeof(char))); - conn->dnsbuf_free_after_use = true; - } - - if (conn->dnsbuf_free_after_use) { /* Partial read is in progress. */ - char* dest = conn->dnsbuf_data + conn->dnsbuf_pos; - if (expected < len) - len = expected; - memcpy(dest, data, len); - conn->dnsbuf_pos += len; - nread = len; - } else { /* Complete and clean read. */ - mlassert(expected <= len, "not enough data to perform complete read"); - conn->dnsbuf_data = (char*)data; - conn->dnsbuf_pos = conn->dnsbuf_len; - nread = expected; - } - - /* If entire dnslen/dnsmsg was read, attempt to parse it. */ - if (conn->dnsbuf_len == conn->dnsbuf_pos) { - ret = _parse_dnsbuf_data(conn); - if (ret < 0) - return ret; - } - - return nread; -} - -void _output_dnssim_read_dns_stream(_output_dnssim_connection_t* conn, size_t len, const char* data) -{ - int pos = 0; - int chunk = 0; - while (pos < len) { - chunk = _read_dns_stream_chunk(conn, len - pos, data + pos); - if (chunk < 0) { - mlwarning("lost orientation in DNS stream, closing"); - _output_dnssim_conn_close(conn); - break; - } else { - pos += chunk; - } - } - mlassert((pos == len) || (chunk < 0), "dns stream read invalid, pos != len"); -} - -void _output_dnssim_read_dnsmsg(_output_dnssim_connection_t* conn, size_t len, const char* data) -{ - mlassert(conn, "conn is nil"); - mlassert(len > 0, "len is zero"); - mlassert(data, "no data"); - mlassert(conn->dnsbuf_pos == 0, "dnsbuf not empty"); - mlassert(conn->dnsbuf_free_after_use == false, "dnsbuf read in progress"); - - /* Read dnsmsg of given length from input data. */ - conn->dnsbuf_len = len; - conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSMSG; - int nread = _read_dns_stream_chunk(conn, len, data); - - if (nread != len) { - mlwarning("failed to read received dnsmsg"); - if (conn->dnsbuf_free_after_use) - free(conn->dnsbuf_data); - } - - /* Clean state afterwards. */ - conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSLEN; - conn->dnsbuf_len = 2; - conn->dnsbuf_pos = 0; - conn->dnsbuf_free_after_use = false; -} diff --git a/src/output/dnssim/https2.c b/src/output/dnssim/https2.c deleted file mode 100644 index 72fcdaf..0000000 --- a/src/output/dnssim/https2.c +++ /dev/null @@ -1,592 +0,0 @@ -/* - * Copyright (c) 2020, CZ.NIC, z.s.p.o. - * All rights reserved. - * - * This file is part of dnsjit. - * - * dnsjit is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * dnsjit is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with dnsjit. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include "output/dnssim.h" -#include "output/dnssim/internal.h" -#include "output/dnssim/ll.h" -#include "core/assert.h" -#include "lib/base64url.h" - -#include <gnutls/gnutls.h> -#include <string.h> - -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - -#define OUTPUT_DNSSIM_MAKE_NV(NAME, VALUE, VALUELEN) \ - { \ - (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, VALUELEN, \ - NGHTTP2_NV_FLAG_NONE \ - } - -#define OUTPUT_DNSSIM_MAKE_NV2(NAME, VALUE) \ - { \ - (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ - NGHTTP2_NV_FLAG_NONE \ - } - -#define OUTPUT_DNSSIM_HTTP_GET_TEMPLATE "?dns=" -#define OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN (sizeof(OUTPUT_DNSSIM_HTTP_GET_TEMPLATE) - 1) -#define OUTPUT_DNSSIM_HTTP2_INITIAL_MAX_CONCURRENT_STREAMS 100 -#define OUTPUT_DNSSIM_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS 0xffffffffu - -static core_log_t _log = LOG_T_INIT("output.dnssim"); - -static ssize_t _http2_send(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data; - mlassert(conn, "conn can't be null"); - mlassert(conn->tls, "conn must have tls ctx"); - mlassert(conn->tls->session, "conn must have tls session"); - - mldebug("http2 (%p): sending data, len=%ld", session, length); - - ssize_t len = 0; - if ((len = gnutls_record_send(conn->tls->session, data, length)) < 0) { - mlwarning("gnutls_record_send failed: %s", gnutls_strerror(len)); - len = NGHTTP2_ERR_CALLBACK_FAILURE; - } - - return len; -} - -static ssize_t _http2_on_data_provider_read(nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) -{ - _output_dnssim_https2_data_provider_t* buffer = source->ptr; - mlassert(buffer, "no data provider"); - mlassert(buffer->len <= MAX_DNSMSG_SIZE, "invalid dnsmsg size: %zu B", buffer->len); - - ssize_t sent = (length < buffer->len) ? length : buffer->len; - mlassert(sent >= 0, "negative length of bytes to send"); - - memcpy(buf, buffer->buf, sent); - buffer->buf += sent; - buffer->len -= sent; - if (buffer->len == 0) - *data_flags |= NGHTTP2_DATA_FLAG_EOF; - - return sent; -} - -static _output_dnssim_query_tcp_t* _http2_get_stream_qry(_output_dnssim_connection_t* conn, int32_t stream_id) -{ - mlassert(conn, "conn is nil"); - mlassert(stream_id >= 0, "invalid stream_id"); - - _output_dnssim_query_tcp_t* qry = (_output_dnssim_query_tcp_t*)conn->sent; - while (qry != NULL && qry->stream_id != stream_id) { - qry = (_output_dnssim_query_tcp_t*)qry->qry.next; - } - - return qry; -} - -static int _http2_on_header(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t namelen, const uint8_t* value, size_t valuelen, uint8_t flags, void* user_data) -{ - if (frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_RESPONSE) { - if (namelen == 7 && strncmp((char*)name, ":status", 7) == 0) { - if (valuelen != 3 || (value[0] != '1' && value[0] != '2')) { - /* When reponse code isn't 1xx or 2xx, close the query. - * This will result in request timeout, which currently seems - * slightly better than mocking SERVFAIL for statistics. */ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data; - mlassert(conn, "conn is nil"); - _output_dnssim_query_tcp_t* qry = _http2_get_stream_qry(conn, frame->hd.stream_id); - - if (qry != NULL) { - _output_dnssim_close_query_https2(qry); - mlinfo("http response %s, closing query", value); - } - } - } - } - return 0; -} - -static int _http2_on_data_recv(nghttp2_session* session, uint8_t flags, int32_t stream_id, const uint8_t* data, size_t len, void* user_data) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data; - mlassert(conn, "conn is nil"); - - _output_dnssim_query_tcp_t* qry = _http2_get_stream_qry(conn, stream_id); - - mldebug("http2: data chunk recv, session=%p, len=%d", session, len); - - if (qry) { - if (qry->recv_buf_len == 0) { - if (len > MAX_DNSMSG_SIZE) { - mlwarning("http response exceeded maximum size of dns message"); - return -1; - } - mlfatal_oom(qry->recv_buf = malloc(len)); - memcpy(qry->recv_buf, data, len); - qry->recv_buf_len = len; - } else { - size_t total_len = qry->recv_buf_len + len; - if (total_len > MAX_DNSMSG_SIZE) { - mlwarning("http response exceeded maximum size of dns message"); - return -1; - } - mlfatal_oom(qry->recv_buf = realloc(qry->recv_buf, total_len)); - memcpy(qry->recv_buf + qry->recv_buf_len, data, len); - qry->recv_buf_len = total_len; - } - } else { - mldebug("no query associated with this stream id, ignoring"); - } - - return 0; -} - -static void _http2_check_max_streams(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn can't be null"); - mlassert(conn->http2, "conn must have http2 ctx"); - - switch (conn->state) { - case _OUTPUT_DNSSIM_CONN_ACTIVE: - if (conn->http2->open_streams >= conn->http2->max_concurrent_streams) { - mlinfo("http2 (%p): reached maximum number of concurrent streams (%ld)", - conn->http2->session, conn->http2->max_concurrent_streams); - conn->state = _OUTPUT_DNSSIM_CONN_CONGESTED; - } - break; - case _OUTPUT_DNSSIM_CONN_CONGESTED: - if (conn->http2->open_streams < conn->http2->max_concurrent_streams) - conn->state = _OUTPUT_DNSSIM_CONN_ACTIVE; - break; - default: - break; - } -} - -static int _http2_on_stream_close(nghttp2_session* session, int32_t stream_id, uint32_t error_code, void* user_data) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data; - mlassert(conn, "conn can't be null"); - mlassert(conn->http2, "conn must have http2 ctx"); - mlassert(conn->http2->open_streams > 0, "conn has no open streams"); - - conn->http2->open_streams--; - _http2_check_max_streams(conn); - return 0; -} - -static int _http2_on_frame_recv(nghttp2_session* session, const nghttp2_frame* frame, void* user_data) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data; - mlassert(conn, "conn can't be null"); - mlassert(conn->tls, "conn must have tls ctx"); - mlassert(conn->tls->session, "conn must have tls session"); - mlassert(conn->http2, "conn must have http2 ctx"); - - switch (frame->hd.type) { - case NGHTTP2_DATA: - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - mldebug("http2 (%p): final DATA frame recv", session); - _output_dnssim_query_tcp_t* qry = _http2_get_stream_qry(conn, frame->hd.stream_id); - - if (qry != NULL) { - conn->http2->current_qry = qry; - _output_dnssim_read_dnsmsg(conn, qry->recv_buf_len, (char*)qry->recv_buf); - } - } - break; - case NGHTTP2_SETTINGS: - if (!conn->http2->remote_settings_received) { - /* On the first SETTINGS frame, set concurrent streams to unlimited, same as nghttp2. */ - conn->http2->remote_settings_received = true; - conn->http2->max_concurrent_streams = OUTPUT_DNSSIM_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS; - _http2_check_max_streams(conn); - } - nghttp2_settings* settings = (nghttp2_settings*)frame; - int i; - for (i = 0; i < settings->niv; i++) { - switch (settings->iv[i].settings_id) { - case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: - conn->http2->max_concurrent_streams = settings->iv[i].value; - _http2_check_max_streams(conn); - break; - default: - break; - } - } - break; - default: - break; - } - return 0; -} - -int _output_dnssim_https2_init(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn is nil"); - mlassert(conn->tls == NULL, "conn already has tls context"); - mlassert(conn->http2 == NULL, "conn already has http2 context"); - mlassert(conn->client, "conn must be associated with a client"); - mlassert(conn->client->dnssim, "client must have dnssim"); - - int ret = -1; - nghttp2_session_callbacks* callbacks; - nghttp2_option* option; - output_dnssim_t* self = conn->client->dnssim; - - /* Initialize TLS session. */ - ret = _output_dnssim_tls_init(conn); - if (ret < 0) - return ret; - - /* Configure ALPN to negotiate HTTP/2. */ - const gnutls_datum_t protos[] = { - { (unsigned char*)"h2", 2 } - }; - ret = gnutls_alpn_set_protocols(conn->tls->session, protos, 1, 0); - if (ret < 0) { - lwarning("failed to set ALPN protocol: %s", gnutls_strerror(ret)); - return ret; - } - - lfatal_oom(conn->http2 = calloc(1, sizeof(_output_dnssim_http2_ctx_t))); - conn->http2->max_concurrent_streams = OUTPUT_DNSSIM_HTTP2_INITIAL_MAX_CONCURRENT_STREAMS; - - /* Set up HTTP/2 callbacks and client. */ - lassert(nghttp2_session_callbacks_new(&callbacks) == 0, "out of memory"); - nghttp2_session_callbacks_set_send_callback(callbacks, _http2_send); - nghttp2_session_callbacks_set_on_header_callback(callbacks, _http2_on_header); - nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, _http2_on_data_recv); - nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, _http2_on_frame_recv); - nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, _http2_on_stream_close); - - lassert(nghttp2_option_new(&option) == 0, "out of memory"); - nghttp2_option_set_peer_max_concurrent_streams(option, conn->http2->max_concurrent_streams); - - ret = nghttp2_session_client_new2(&conn->http2->session, callbacks, conn, option); - - nghttp2_session_callbacks_del(callbacks); - nghttp2_option_del(option); - - if (ret < 0) { - free(conn->http2); - conn->http2 = NULL; - } - - return ret; -} - -int _output_dnssim_https2_setup(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn is nil"); - mlassert(conn->tls, "conn must have tls ctx"); - mlassert(conn->tls->session, "conn must have tls session"); - mlassert(conn->http2, "conn must have http2 ctx"); - mlassert(conn->http2->session, "conn must have http2 session"); - - int ret = -1; - - /* Check "h2" protocol was negotiated with ALPN. */ - gnutls_datum_t proto; - ret = gnutls_alpn_get_selected_protocol(conn->tls->session, &proto); - if (ret < 0) { - mlwarning("http2: failed to get negotiated protocol: %s", gnutls_strerror(ret)); - return ret; - } - if (proto.size != 2 || memcmp("h2", proto.data, 2) != 0) { - mlwarning("http2: protocol is not negotiated"); - return ret; - } - - /* Submit SETTIGNS frame. */ - static const nghttp2_settings_entry iv[] = { - { NGHTTP2_SETTINGS_MAX_FRAME_SIZE, MAX_DNSMSG_SIZE }, - { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 }, /* Only we can initiate streams. */ - }; - ret = nghttp2_submit_settings(conn->http2->session, NGHTTP2_FLAG_NONE, iv, sizeof(iv) / sizeof(*iv)); - if (ret < 0) { - mlwarning("http2: failed to submit SETTINGS: %s", nghttp2_strerror(ret)); - return ret; - } - - ret = 0; - return ret; -} - -void _output_dnssim_https2_process_input_data(_output_dnssim_connection_t* conn, size_t len, const char* data) -{ - mlassert(conn, "conn is nil"); - mlassert(conn->http2, "conn must have http2 ctx"); - mlassert(conn->http2->session, "conn must have http2 session"); - - /* Process incoming frames. */ - ssize_t ret = 0; - conn->prevent_close = true; - ret = nghttp2_session_mem_recv(conn->http2->session, (uint8_t*)data, len); - conn->prevent_close = false; - if (ret < 0) { - mlwarning("failed nghttp2_session_mem_recv: %s", nghttp2_strerror(ret)); - _output_dnssim_conn_close(conn); - return; - } else if (conn->state == _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED) { - _output_dnssim_conn_close(conn); - return; - } - mlassert(ret == len, "nghttp2_session_mem_recv didn't process all data"); - - /* Send any frames the read might have triggered. */ - ret = nghttp2_session_send(conn->http2->session); - if (ret < 0) { - mlwarning("failed nghttp2_session_send: %s", nghttp2_strerror(ret)); - _output_dnssim_conn_close(conn); - return; - } -} - -int _output_dnssim_create_query_https2(output_dnssim_t* self, _output_dnssim_request_t* req) -{ - mlassert_self(); - lassert(req, "req is nil"); - lassert(req->client, "request must have a client associated with it"); - - _output_dnssim_query_tcp_t* qry; - - lfatal_oom(qry = calloc(1, sizeof(_output_dnssim_query_tcp_t))); - - qry->qry.transport = OUTPUT_DNSSIM_TRANSPORT_HTTPS2; - qry->qry.req = req; - qry->qry.state = _OUTPUT_DNSSIM_QUERY_PENDING_WRITE; - qry->stream_id = -1; - req->qry = &qry->qry; // TODO change when adding support for multiple Qs for req - _ll_append(req->client->pending, &qry->qry); - - return _output_dnssim_handle_pending_queries(req->client); -} - -void _output_dnssim_close_query_https2(_output_dnssim_query_tcp_t* qry) -{ - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.req, "query must be part of a request"); - _output_dnssim_request_t* req = qry->qry.req; - mlassert(req->client, "request must belong to a client"); - - _ll_try_remove(req->client->pending, &qry->qry); - if (qry->conn) { - _output_dnssim_connection_t* conn = qry->conn; - _ll_try_remove(conn->sent, &qry->qry); - qry->conn = NULL; - _output_dnssim_conn_idle(conn); - } - - if (qry->recv_buf != NULL) - free(qry->recv_buf); - - _ll_remove(req->qry, &qry->qry); - free(qry); -} - -void _output_dnssim_https2_close(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn can't be nil"); - mlassert(conn->http2, "conn must have http2 ctx"); - - nghttp2_session_del(conn->http2->session); - _output_dnssim_tls_close(conn); -} - -static int _http2_send_query_get(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry) -{ - mlassert(conn, "conn can't be null"); - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.req, "req can't be null"); - mlassert(qry->qry.req->payload, "payload can't be null"); - mlassert(qry->qry.req->payload->len <= MAX_DNSMSG_SIZE, "payload too big"); - mlassert(conn->client, "conn must be associated with client"); - mlassert(conn->client->dnssim, "client must have dnssim"); - - output_dnssim_t* self = conn->client->dnssim; - core_object_payload_t* content = qry->qry.req->payload; - - const size_t uri_path_len = strlen(_self->h2_uri_path); - const size_t path_len = uri_path_len - + OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN - + (content->len * 4) / 3 + 3 /* upper limit of base64 encoding */ - + 1; /* terminating null byte */ - if (path_len >= _MAX_URI_LEN) { - self->discarded++; - linfo("http2: uri path with query too long, query discarded"); - return 0; - } - char path[path_len]; - memcpy(path, _self->h2_uri_path, uri_path_len); - memcpy(&path[uri_path_len], OUTPUT_DNSSIM_HTTP_GET_TEMPLATE, OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN); - - int32_t ret = base64url_encode(content->payload, content->len, - (uint8_t*)&path[uri_path_len + OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN], - sizeof(path) - uri_path_len - OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN - 1); - if (ret < 0) { - self->discarded++; - linfo("http2: base64url encode of query failed, query discarded"); - return 0; - } - - nghttp2_nv hdrs[] = { - OUTPUT_DNSSIM_MAKE_NV2(":method", "GET"), - OUTPUT_DNSSIM_MAKE_NV2(":scheme", "https"), - OUTPUT_DNSSIM_MAKE_NV(":authority", _self->h2_uri_authority, strlen(_self->h2_uri_authority)), - OUTPUT_DNSSIM_MAKE_NV(":path", path, uri_path_len + sizeof(OUTPUT_DNSSIM_HTTP_GET_TEMPLATE) - 1 + ret), - OUTPUT_DNSSIM_MAKE_NV2("accept", "application/dns-message"), - }; - - qry->stream_id = nghttp2_submit_request(conn->http2->session, NULL, hdrs, sizeof(hdrs) / sizeof(nghttp2_nv), NULL, NULL); - - if (qry->stream_id < 0) { - mldebug("http2 (%p): failed to submit request: %s", conn->http2->session, nghttp2_strerror(qry->stream_id)); - return -1; - } - mldebug("http2 (%p): GET %s", conn->http2->session, path); - conn->http2->open_streams++; - _http2_check_max_streams(conn); - - ret = nghttp2_session_send(conn->http2->session); - if (ret < 0) { - mldebug("http2 (%p): failed session send: %s", conn->http2->session, nghttp2_strerror(ret)); - return -1; - } - - return 0; -} - -static int _http2_send_query_post(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry) -{ - mlassert(conn, "conn can't be null"); - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.req, "req can't be null"); - mlassert(qry->qry.req->payload, "payload can't be null"); - mlassert(qry->qry.req->payload->len <= MAX_DNSMSG_SIZE, "payload too big"); - mlassert(conn->client, "conn must be associated with client"); - mlassert(conn->client->dnssim, "client must have dnssim"); - - output_dnssim_t* self = conn->client->dnssim; - - core_object_payload_t* content = qry->qry.req->payload; - - int window_size = nghttp2_session_get_remote_window_size(conn->http2->session); - if (content->len > window_size) { - mldebug("http2 (%p): insufficient remote window size, deferring", conn->http2->session); - return 0; - } - - char content_length[6]; /* max dnslen "65535" */ - int content_length_len = snprintf(content_length, 6, "%zd", content->len); - - nghttp2_nv hdrs[] = { - OUTPUT_DNSSIM_MAKE_NV2(":method", "POST"), - OUTPUT_DNSSIM_MAKE_NV2(":scheme", "https"), - OUTPUT_DNSSIM_MAKE_NV(":authority", _self->h2_uri_authority, strlen(_self->h2_uri_authority)), - OUTPUT_DNSSIM_MAKE_NV(":path", _self->h2_uri_path, strlen(_self->h2_uri_path)), - OUTPUT_DNSSIM_MAKE_NV2("accept", "application/dns-message"), - OUTPUT_DNSSIM_MAKE_NV2("content-type", "application/dns-message"), - OUTPUT_DNSSIM_MAKE_NV("content-length", content_length, content_length_len) - }; - - _output_dnssim_https2_data_provider_t data = { - .buf = content->payload, - .len = content->len - }; - - nghttp2_data_provider data_provider = { - .source.ptr = &data, - .read_callback = _http2_on_data_provider_read - }; - - qry->stream_id = nghttp2_submit_request(conn->http2->session, NULL, hdrs, sizeof(hdrs) / sizeof(nghttp2_nv), &data_provider, NULL); - - if (qry->stream_id < 0) { - mldebug("http2 (%p): failed to submit request: %s", conn->http2->session, nghttp2_strerror(qry->stream_id)); - return -1; - } - mldebug("http2 (%p): POST payload len=%ld", conn->http2->session, content->len); - conn->http2->open_streams++; - _http2_check_max_streams(conn); - - window_size = nghttp2_session_get_stream_remote_window_size(conn->http2->session, qry->stream_id); - mlassert(content->len <= window_size, - "unsupported: http2 stream window size (%ld B) is smaller than dns payload (%ld B)", - window_size, content->len); - - int ret = nghttp2_session_send(conn->http2->session); - if (ret < 0) { - mldebug("http2 (%p): failed session send: %s", conn->http2->session, nghttp2_strerror(ret)); - return -1; - } - - return 0; -} - -void _output_dnssim_https2_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry) -{ - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE, "qry must be pending write"); - mlassert(conn, "conn can't be null"); - mlassert(conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE, "connection state != ACTIVE"); - mlassert(conn->http2, "conn must have http2 ctx"); - mlassert(conn->http2->session, "conn must have http2 session"); - mlassert(conn->client, "conn must be associated with client"); - mlassert(conn->client->pending, "conn has no pending queries"); - mlassert(conn->client->dnssim, "client must have dnssim"); - - int ret = 0; - output_dnssim_t* self = conn->client->dnssim; - - if (!nghttp2_session_check_request_allowed(conn->http2->session)) { - mldebug("http2 (%p): request not allowed", conn->http2->session); - _output_dnssim_conn_close(conn); - return; - } - - switch (_self->h2_method) { - case OUTPUT_DNSSIM_H2_POST: - ret = _http2_send_query_post(conn, qry); - break; - case OUTPUT_DNSSIM_H2_GET: - ret = _http2_send_query_get(conn, qry); - break; - default: - lfatal("http2: unsupported method"); - } - - if (ret < 0) { - _output_dnssim_conn_close(conn); - return; - } - - qry->conn = conn; - _ll_remove(conn->client->pending, &qry->qry); - _ll_append(conn->sent, &qry->qry); - - /* Stop idle timer, since there are queries to answer now. */ - if (conn->idle_timer != NULL) { - conn->is_idle = false; - uv_timer_stop(conn->idle_timer); - } - - qry->qry.state = _OUTPUT_DNSSIM_QUERY_SENT; -} - -#endif diff --git a/src/output/dnssim/internal.h b/src/output/dnssim/internal.h deleted file mode 100644 index b9feddf..0000000 --- a/src/output/dnssim/internal.h +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o. - * All rights reserved. - * - * This file is part of dnsjit. - * - * dnsjit is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * dnsjit is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with dnsjit. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef __dnsjit_output_dnssim_internal_h -#define __dnsjit_output_dnssim_internal_h - -#include <gnutls/gnutls.h> -#include <nghttp2/nghttp2.h> -#include <uv.h> -#include "core/object/dns.h" -#include "core/object/payload.h" - -#define DNSSIM_MIN_GNUTLS_VERSION 0x030603 -#define DNSSIM_MIN_GNUTLS_ERRORMSG "dnssim tls/https2 transport requires GnuTLS >= 3.6.3" - -#define _self ((_output_dnssim_t*)self) -#define _ERR_MALFORMED -2 -#define _ERR_MSGID -3 -#define _ERR_TC -4 -#define _ERR_QUESTION -5 - -#define _MAX_URI_LEN 65536 -#define MAX_DNSMSG_SIZE 65535 -#define WIRE_BUF_SIZE (MAX_DNSMSG_SIZE + 2 + 16384) /** max tcplen + 2b tcplen + 16kb tls record */ - -typedef struct _output_dnssim_request _output_dnssim_request_t; -typedef struct _output_dnssim_connection _output_dnssim_connection_t; -typedef struct _output_dnssim_client _output_dnssim_client_t; - -/* - * Query-related structures. - */ - -typedef struct _output_dnssim_query _output_dnssim_query_t; -struct _output_dnssim_query { - /* - * Next query in the list. - * - * Currently, next is used for TCP clients/connection, which makes it - * impossible to use for tracking multiple queries of a single request. - * - * TODO: refactor the linked lists to allow query to be part of multiple lists - */ - _output_dnssim_query_t* next; - - output_dnssim_transport_t transport; - _output_dnssim_request_t* req; - - /* Query state, currently used only for TCP. */ - enum { - _OUTPUT_DNSSIM_QUERY_PENDING_WRITE, - _OUTPUT_DNSSIM_QUERY_PENDING_WRITE_CB, - _OUTPUT_DNSSIM_QUERY_PENDING_CLOSE, - _OUTPUT_DNSSIM_QUERY_WRITE_FAILED, - _OUTPUT_DNSSIM_QUERY_SENT, - _OUTPUT_DNSSIM_QUERY_ORPHANED - } state; -}; - -typedef struct _output_dnssim_query_udp _output_dnssim_query_udp_t; -struct _output_dnssim_query_udp { - _output_dnssim_query_t qry; - - uv_udp_t* handle; - uv_buf_t buf; -}; - -typedef struct _output_dnssim_query_tcp _output_dnssim_query_tcp_t; -struct _output_dnssim_query_tcp { - _output_dnssim_query_t qry; - - /* Connection this query is assigned to. */ - _output_dnssim_connection_t* conn; - - uv_write_t write_req; - - /* Send buffers for libuv; 0 is for dnslen, 1 is for dnsmsg. */ - uv_buf_t bufs[2]; - - /* HTTP/2 stream id that was used to send this query. */ - int32_t stream_id; - - /* HTTP/2 expected content length. */ - int32_t content_len; - - /* Receive buffer (currently used only by HTTP/2). */ - uint8_t* recv_buf; - ssize_t recv_buf_len; -}; - -struct _output_dnssim_request { - /* List of queries associated with this request. */ - _output_dnssim_query_t* qry; - - /* Client this request belongs to. */ - _output_dnssim_client_t* client; - - /* The DNS question to be resolved. */ - core_object_payload_t* payload; - core_object_dns_t* dns_q; - const uint8_t* question; - ssize_t question_len; - - /* Timestamps for latency calculation. */ - uint64_t created_at; - uint64_t ended_at; - - /* Timer for tracking timeout of the request. */ - uv_timer_t* timer; - - /* The output component of this request. */ - output_dnssim_t* dnssim; - - /* State of the request. */ - enum { - _OUTPUT_DNSSIM_REQ_ONGOING, - _OUTPUT_DNSSIM_REQ_CLOSING - } state; - - /* Statistics interval in which this request is tracked. */ - output_dnssim_stats_t* stats; -}; - -/* - * Connection-related structures. - */ - -/* Read-state of connection's data stream. */ -typedef enum _output_dnssim_read_state { - _OUTPUT_DNSSIM_READ_STATE_CLEAN, - _OUTPUT_DNSSIM_READ_STATE_DNSLEN, /* Expecting bytes of dnslen. */ - _OUTPUT_DNSSIM_READ_STATE_DNSMSG, /* Expecting bytes of dnsmsg. */ - _OUTPUT_DNSSIM_READ_STATE_INVALID -} _output_dnssim_read_state_t; - -/* TLS-related data for a single connection. */ -typedef struct _output_dnssim_tls_ctx { - gnutls_session_t session; - uint8_t* buf; - ssize_t buf_len; - ssize_t buf_pos; - size_t write_queue_size; -} _output_dnssim_tls_ctx_t; - -/* HTTP2 context for a single connection. */ -typedef struct _output_dnssim_http2_ctx { - nghttp2_session* session; - - /* Query to which the dnsbuf currently being processed belongs to. */ - _output_dnssim_query_tcp_t* current_qry; - - /* Maximum number of concurrent and currently open streams. */ - uint32_t max_concurrent_streams; - uint32_t open_streams; - - /* Flag indicating whether we received the peer's initial SETTINGS frame. */ - bool remote_settings_received; -} _output_dnssim_http2_ctx_t; - -struct _output_dnssim_connection { - _output_dnssim_connection_t* next; - - uv_tcp_t* handle; - - /* Timeout timer for establishing the connection. */ - uv_timer_t* handshake_timer; - - /* Idle timer for connection reuse. rfc7766#section-6.2.3 */ - uv_timer_t* idle_timer; - bool is_idle; - - /* List of queries that have been queued (pending write callback). */ - _output_dnssim_query_t* queued; - - /* List of queries that have been sent over this connection. */ - _output_dnssim_query_t* sent; - - /* Client this connection belongs to. */ - _output_dnssim_client_t* client; - - /* State of the connection. - * Numeric ordering of constants is significant and follows the typical connection lifecycle. - * Ensure new states are added to a proper place. */ - enum { - _OUTPUT_DNSSIM_CONN_INITIALIZED = 0, - _OUTPUT_DNSSIM_CONN_TCP_HANDSHAKE = 10, - _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE = 20, - _OUTPUT_DNSSIM_CONN_ACTIVE = 30, - _OUTPUT_DNSSIM_CONN_CONGESTED = 35, - _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED = 38, - _OUTPUT_DNSSIM_CONN_CLOSING = 40, - _OUTPUT_DNSSIM_CONN_CLOSED = 50 - } state; - - /* State of the data stream read. */ - _output_dnssim_read_state_t read_state; - - /* Total length of the expected dns data (either 2 for dnslen, or dnslen itself). */ - size_t dnsbuf_len; - - /* Current position in the receive dns buffer. */ - size_t dnsbuf_pos; - - /* Receive buffer used for incomplete messages or dnslen. */ - char* dnsbuf_data; - bool dnsbuf_free_after_use; - - /* Statistics interval in which the handshake is tracked. */ - output_dnssim_stats_t* stats; - - /* TLS-related data. */ - _output_dnssim_tls_ctx_t* tls; - - /* HTTP/2-related data. */ - _output_dnssim_http2_ctx_t* http2; - - /* Prevents immediate closure of connection. Instead, connection is moved - * to CLOSE_REQUESTED state and setter of this flag is responsible for - * closing the connection when clearing this flag. */ - bool prevent_close; -}; - -/* - * Client structure. - */ - -struct _output_dnssim_client { - /* Dnssim component this client belongs to. */ - output_dnssim_t* dnssim; - - /* List of connections. - * Multiple connections may be used (e.g. some are already closed for writing). - */ - _output_dnssim_connection_t* conn; - - /* List of queries that are pending to be sent over any available connection. */ - _output_dnssim_query_t* pending; - - /* TLS-ticket for session resumption. */ - gnutls_datum_t tls_ticket; -}; - -/* - * DnsSim-related structures. - */ - -typedef struct _output_dnssim_source _output_dnssim_source_t; -struct _output_dnssim_source { - _output_dnssim_source_t* next; - struct sockaddr_storage addr; -}; - -typedef struct _output_dnssim _output_dnssim_t; -struct _output_dnssim { - output_dnssim_t pub; - - uv_loop_t loop; - uv_timer_t stats_timer; - - struct sockaddr_storage target; - _output_dnssim_source_t* source; - output_dnssim_transport_t transport; - - char h2_uri_authority[_MAX_URI_LEN]; - char h2_uri_path[_MAX_URI_LEN]; - bool h2_zero_out_msgid; - output_dnssim_h2_method_t h2_method; - - /* Array of clients, mapped by client ID (ranges from 0 to max_clients). */ - _output_dnssim_client_t* client_arr; - - gnutls_priority_t* tls_priority; - gnutls_certificate_credentials_t tls_cred; - char wire_buf[WIRE_BUF_SIZE]; /* thread-local buffer for processing tls input */ -}; - -/* Provides data for HTTP/2 data frames. */ -typedef struct { - const uint8_t* buf; - size_t len; -} _output_dnssim_https2_data_provider_t; - -/* - * Forward function declarations. - */ - -int _output_dnssim_bind_before_connect(output_dnssim_t* self, uv_handle_t* handle); -int _output_dnssim_create_query_udp(output_dnssim_t* self, _output_dnssim_request_t* req); -int _output_dnssim_create_query_tcp(output_dnssim_t* self, _output_dnssim_request_t* req); -void _output_dnssim_close_query_udp(_output_dnssim_query_udp_t* qry); -void _output_dnssim_close_query_tcp(_output_dnssim_query_tcp_t* qry); -int _output_dnssim_answers_request(_output_dnssim_request_t* req, core_object_dns_t* response); -void _output_dnssim_request_answered(_output_dnssim_request_t* req, core_object_dns_t* msg); -void _output_dnssim_maybe_free_request(_output_dnssim_request_t* req); -void _output_dnssim_on_uv_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf); -void _output_dnssim_create_request(output_dnssim_t* self, _output_dnssim_client_t* client, core_object_payload_t* payload); -int _output_dnssim_handle_pending_queries(_output_dnssim_client_t* client); -int _output_dnssim_tcp_connect(output_dnssim_t* self, _output_dnssim_connection_t* conn); -void _output_dnssim_tcp_close(_output_dnssim_connection_t* conn); -void _output_dnssim_tcp_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry); -void _output_dnssim_conn_close(_output_dnssim_connection_t* conn); -void _output_dnssim_conn_idle(_output_dnssim_connection_t* conn); -int _output_dnssim_handle_pending_queries(_output_dnssim_client_t* client); -void _output_dnssim_conn_activate(_output_dnssim_connection_t* conn); -void _output_dnssim_conn_maybe_free(_output_dnssim_connection_t* conn); -void _output_dnssim_read_dns_stream(_output_dnssim_connection_t* conn, size_t len, const char* data); -void _output_dnssim_read_dnsmsg(_output_dnssim_connection_t* conn, size_t len, const char* data); - -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION -int _output_dnssim_create_query_tls(output_dnssim_t* self, _output_dnssim_request_t* req); -void _output_dnssim_close_query_tls(_output_dnssim_query_tcp_t* qry); -int _output_dnssim_tls_init(_output_dnssim_connection_t* conn); -void _output_dnssim_tls_process_input_data(_output_dnssim_connection_t* conn); -void _output_dnssim_tls_close(_output_dnssim_connection_t* conn); -void _output_dnssim_tls_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry); - -int _output_dnssim_create_query_https2(output_dnssim_t* self, _output_dnssim_request_t* req); -void _output_dnssim_close_query_https2(_output_dnssim_query_tcp_t* qry); -int _output_dnssim_https2_init(_output_dnssim_connection_t* conn); -int _output_dnssim_https2_setup(_output_dnssim_connection_t* conn); -void _output_dnssim_https2_process_input_data(_output_dnssim_connection_t* conn, size_t len, const char* data); -void _output_dnssim_https2_close(_output_dnssim_connection_t* conn); -void _output_dnssim_https2_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry); -#endif - -#endif diff --git a/src/output/dnssim/ll.h b/src/output/dnssim/ll.h deleted file mode 100644 index 8e0b07a..0000000 --- a/src/output/dnssim/ll.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o. - * All rights reserved. - * - * This file is part of dnsjit. - * - * dnsjit is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * dnsjit is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with dnsjit. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef __dnsjit_output_dnssim_ll_h -#define __dnsjit_output_dnssim_ll_h - -#include "core/assert.h" - -/* Utility macros for linked list structures. - * - * - "list" is the pointer to the first node of the linked list - * - "list" can be NULL if there are no nodes - * - every node has "next", which points to the next node (can be NULL) - */ - -/* Append a node to the list. - * - * Only a single node can be appended - node->next must be NULL. - */ -#define _ll_append(list, node) \ - { \ - glassert((node)->next == NULL, "node->next must be null when appending"); \ - if ((list) == NULL) \ - (list) = (node); \ - else if ((node) != NULL) { \ - typeof(list) _current = (list); \ - while (_current->next != NULL) \ - _current = _current->next; \ - _current->next = node; \ - } \ - } - -/* Remove a node from the list. - * - * In strict mode, the node must be present in the list. - */ -#define _ll_remove_template(list, node, strict) \ - { \ - if (strict) \ - glassert((list), "list can't be null when removing nodes"); \ - if ((list) != NULL && (node) != NULL) { \ - if ((list) == (node)) { \ - (list) = (node)->next; \ - (node)->next = NULL; \ - } else { \ - typeof(list) _current = (list); \ - while (_current != NULL && _current->next != (node)) { \ - if (strict) \ - glassert((_current->next), "list doesn't contain the node to be removed"); \ - _current = _current->next; \ - } \ - if (_current != NULL) { \ - _current->next = (node)->next; \ - (node)->next = NULL; \ - } \ - } \ - } \ - } - -/* Remove a node from the list. */ -#define _ll_remove(list, node) _ll_remove_template((list), (node), true) - -/* Remove a node from the list if it's present. */ -#define _ll_try_remove(list, node) _ll_remove_template((list), (node), false) - -#endif diff --git a/src/output/dnssim/tcp.c b/src/output/dnssim/tcp.c deleted file mode 100644 index 1f2b619..0000000 --- a/src/output/dnssim/tcp.c +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o. - * All rights reserved. - * - * This file is part of dnsjit. - * - * dnsjit is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * dnsjit is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with dnsjit. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include "output/dnssim.h" -#include "output/dnssim/internal.h" -#include "output/dnssim/ll.h" -#include "core/assert.h" - -#include <string.h> - -static core_log_t _log = LOG_T_INIT("output.dnssim"); - -static void _move_queries_to_pending(_output_dnssim_query_tcp_t* qry) -{ - _output_dnssim_query_tcp_t* qry_tmp; - while (qry != NULL) { - mlassert(qry->conn, "query must be associated with conn"); - mlassert(qry->conn->state == _OUTPUT_DNSSIM_CONN_CLOSED, "conn must be closed"); - mlassert(qry->conn->client, "conn must be associated with client"); - qry_tmp = (_output_dnssim_query_tcp_t*)qry->qry.next; - qry->qry.next = NULL; - _ll_append(qry->conn->client->pending, &qry->qry); - qry->conn = NULL; - qry->qry.state = _OUTPUT_DNSSIM_QUERY_ORPHANED; - qry->stream_id = -1; - qry->recv_buf_len = 0; - if (qry->recv_buf != NULL) { - free(qry->recv_buf); - qry->recv_buf = NULL; - } - qry = qry_tmp; - } -} - -static void _on_tcp_closed(uv_handle_t* handle) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)handle->data; - mlassert(conn, "conn is nil"); - conn->state = _OUTPUT_DNSSIM_CONN_CLOSED; - - /* Orphan any queries that are still unresolved. */ - _move_queries_to_pending((_output_dnssim_query_tcp_t*)conn->queued); - conn->queued = NULL; - _move_queries_to_pending((_output_dnssim_query_tcp_t*)conn->sent); - conn->sent = NULL; - - /* TODO Improve client re-connect behavior in case the connection fails to - * establish. Currently, queries are orphaned and attempted to be re-sent - * along with the next query that triggers a new connection. - * - * Attempting to establish new connection immediately leads to performance - * issues if the number of these attempts doesn't have upper limit. */ - ///* Ensure orhpaned queries are re-sent over a different connection. */ - //if (_output_dnssim_handle_pending_queries(conn->client) != 0) - // mlinfo("tcp: orphaned queries failed to be re-sent"); - - mlassert(conn->handle, "conn must have tcp handle when closing it"); - free(conn->handle); - conn->handle = NULL; - _output_dnssim_conn_maybe_free(conn); -} - -static void _on_tcp_query_written(uv_write_t* wr_req, int status) -{ - _output_dnssim_query_tcp_t* qry = (_output_dnssim_query_tcp_t*)wr_req->data; - mlassert(qry, "qry/wr_req->data is nil"); - mlassert(qry->conn, "query must be associated with connection"); - _output_dnssim_connection_t* conn = qry->conn; - - free(((_output_dnssim_query_tcp_t*)qry)->bufs[0].base); - - if (qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_CLOSE) { - qry->qry.state = status < 0 ? _OUTPUT_DNSSIM_QUERY_WRITE_FAILED : _OUTPUT_DNSSIM_QUERY_SENT; - _output_dnssim_request_t* req = qry->qry.req; - _output_dnssim_close_query_tcp(qry); - _output_dnssim_maybe_free_request(req); - qry = NULL; - } - - if (status < 0) { - if (status != UV_ECANCELED) - mlinfo("tcp write failed: %s", uv_strerror(status)); - if (qry != NULL) - qry->qry.state = _OUTPUT_DNSSIM_QUERY_WRITE_FAILED; - _output_dnssim_conn_close(conn); - return; - } - - if (qry == NULL) - return; - - /* Mark query as sent and assign it to connection. */ - mlassert(qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE_CB, "invalid query state"); - qry->qry.state = _OUTPUT_DNSSIM_QUERY_SENT; - if (qry->conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE) { - mlassert(qry->conn->queued, "conn has no queued queries"); - _ll_remove(qry->conn->queued, &qry->qry); - _ll_append(qry->conn->sent, &qry->qry); - } -} - -void _output_dnssim_tcp_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry) -{ - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE, "qry must be pending write"); - mlassert(qry->qry.req, "req can't be null"); - mlassert(qry->qry.req->dns_q, "dns_q can't be null"); - mlassert(qry->qry.req->dns_q->obj_prev, "payload can't be null"); - mlassert(conn, "conn can't be null"); - mlassert(conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE, "connection state != ACTIVE"); - mlassert(conn->client, "conn must be associated with client"); - mlassert(conn->client->pending, "conn has no pending queries"); - - mldebug("tcp write dnsmsg id: %04x", qry->qry.req->dns_q->id); - - core_object_payload_t* payload = (core_object_payload_t*)qry->qry.req->dns_q->obj_prev; - uint16_t* len; - mlfatal_oom(len = malloc(sizeof(uint16_t))); - *len = htons(payload->len); - qry->bufs[0] = uv_buf_init((char*)len, 2); - qry->bufs[1] = uv_buf_init((char*)payload->payload, payload->len); - - qry->conn = conn; - _ll_remove(conn->client->pending, &qry->qry); - _ll_append(conn->queued, &qry->qry); - - /* Stop idle timer, since there are queries to answer now. */ - if (conn->idle_timer != NULL) { - conn->is_idle = false; - uv_timer_stop(conn->idle_timer); - } - - qry->write_req.data = (void*)qry; - uv_write(&qry->write_req, (uv_stream_t*)conn->handle, qry->bufs, 2, _on_tcp_query_written); - qry->qry.state = _OUTPUT_DNSSIM_QUERY_PENDING_WRITE_CB; -} - -static void _on_tcp_read(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)handle->data; - output_dnssim_t* self = conn->client->dnssim; - - if (nread > 0) { - mldebug("tcp nread: %d", nread); - switch (_self->transport) { - case OUTPUT_DNSSIM_TRANSPORT_TCP: - _output_dnssim_read_dns_stream(conn, nread, buf->base); - break; - case OUTPUT_DNSSIM_TRANSPORT_TLS: - case OUTPUT_DNSSIM_TRANSPORT_HTTPS2: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - mlassert(conn->tls, "con must have tls ctx"); - conn->tls->buf = (uint8_t*)buf->base; - conn->tls->buf_pos = 0; - conn->tls->buf_len = nread; - _output_dnssim_tls_process_input_data(conn); -#else - mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - default: - mlfatal("unsupported transport"); - break; - } - } else if (nread < 0) { - if (nread != UV_EOF) - mlinfo("tcp conn unexpected close: %s", uv_strerror(nread)); - _output_dnssim_conn_close(conn); - } - - if (buf->base != NULL) - free(buf->base); -} - -static void _on_tcp_connected(uv_connect_t* conn_req, int status) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)conn_req->handle->data; - mlassert(conn, "conn is nil"); - - free(conn_req); - - if (status < 0) { - mldebug("tcp connect failed: %s", uv_strerror(status)); - _output_dnssim_conn_close(conn); - return; - } - - mlassert(conn->state == _OUTPUT_DNSSIM_CONN_TCP_HANDSHAKE, "connection state != TCP_HANDSHAKE"); - int ret = uv_read_start((uv_stream_t*)conn->handle, _output_dnssim_on_uv_alloc, _on_tcp_read); - if (ret < 0) { - mlwarning("tcp uv_read_start() failed: %s", uv_strerror(ret)); - _output_dnssim_conn_close(conn); - return; - } - - mldebug("tcp connected"); - mlassert(conn->client, "conn must be associated with a client"); - mlassert(conn->client->dnssim, "client must be associated with dnssim"); - output_dnssim_t* self = conn->client->dnssim; - switch (_self->transport) { - case OUTPUT_DNSSIM_TRANSPORT_TCP: - _output_dnssim_conn_activate(conn); - break; - case OUTPUT_DNSSIM_TRANSPORT_TLS: - case OUTPUT_DNSSIM_TRANSPORT_HTTPS2: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - mldebug("init tls handshake"); - _output_dnssim_tls_process_input_data(conn); /* Initiate TLS handshake. */ -#else - mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - default: - lfatal("unsupported transport protocol"); - break; - } -} - -static void _on_connection_timeout(uv_timer_t* handle) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)handle->data; - _output_dnssim_conn_close(conn); -} - -int _output_dnssim_tcp_connect(output_dnssim_t* self, _output_dnssim_connection_t* conn) -{ - mlassert_self(); - lassert(conn, "connection can't be null"); - lassert(conn->handle == NULL, "connection already has a handle"); - lassert(conn->handshake_timer == NULL, "connection already has a handshake timer"); - lassert(conn->idle_timer == NULL, "connection already has idle timer"); - lassert(conn->state == _OUTPUT_DNSSIM_CONN_INITIALIZED, "connection state != INITIALIZED"); - - lfatal_oom(conn->handle = malloc(sizeof(uv_tcp_t))); - conn->handle->data = (void*)conn; - int ret = uv_tcp_init(&_self->loop, conn->handle); - if (ret < 0) { - lwarning("failed to init uv_tcp_t"); - goto failure; - } - - ret = _output_dnssim_bind_before_connect(self, (uv_handle_t*)conn->handle); - if (ret < 0) - goto failure; - - /* Set connection parameters. */ - ret = uv_tcp_nodelay(conn->handle, 1); - if (ret < 0) - lwarning("tcp: failed to set TCP_NODELAY: %s", uv_strerror(ret)); - - /* Set connection handshake timeout. */ - lfatal_oom(conn->handshake_timer = malloc(sizeof(uv_timer_t))); - uv_timer_init(&_self->loop, conn->handshake_timer); - conn->handshake_timer->data = (void*)conn; - uv_timer_start(conn->handshake_timer, _on_connection_timeout, self->handshake_timeout_ms, 0); - - /* Set idle connection timer. */ - if (self->idle_timeout_ms > 0) { - lfatal_oom(conn->idle_timer = malloc(sizeof(uv_timer_t))); - uv_timer_init(&_self->loop, conn->idle_timer); - conn->idle_timer->data = (void*)conn; - - /* Start and stop the timer to set the repeat value without running the timer. */ - uv_timer_start(conn->idle_timer, _on_connection_timeout, self->idle_timeout_ms, self->idle_timeout_ms); - uv_timer_stop(conn->idle_timer); - } - - mldebug("tcp connecting"); - uv_connect_t* conn_req; - lfatal_oom(conn_req = malloc(sizeof(uv_connect_t))); - ret = uv_tcp_connect(conn_req, conn->handle, (struct sockaddr*)&_self->target, _on_tcp_connected); - if (ret < 0) - goto failure; - - conn->stats->conn_handshakes++; - conn->client->dnssim->stats_sum->conn_handshakes++; - conn->state = _OUTPUT_DNSSIM_CONN_TCP_HANDSHAKE; - return 0; -failure: - _output_dnssim_conn_close(conn); - return ret; -} - -void _output_dnssim_tcp_close(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn can't be nil"); - - if (conn->handle != NULL) { - uv_read_stop((uv_stream_t*)conn->handle); - uv_close((uv_handle_t*)conn->handle, _on_tcp_closed); - } -} - -int _output_dnssim_create_query_tcp(output_dnssim_t* self, _output_dnssim_request_t* req) -{ - mlassert_self(); - lassert(req, "req is nil"); - lassert(req->client, "request must have a client associated with it"); - - _output_dnssim_query_tcp_t* qry; - - lfatal_oom(qry = calloc(1, sizeof(_output_dnssim_query_tcp_t))); - - qry->qry.transport = OUTPUT_DNSSIM_TRANSPORT_TCP; - qry->qry.req = req; - qry->qry.state = _OUTPUT_DNSSIM_QUERY_PENDING_WRITE; - req->qry = &qry->qry; // TODO change when adding support for multiple Qs for req - _ll_append(req->client->pending, &qry->qry); - - return _output_dnssim_handle_pending_queries(req->client); -} - -void _output_dnssim_close_query_tcp(_output_dnssim_query_tcp_t* qry) -{ - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.req, "query must be part of a request"); - _output_dnssim_request_t* req = qry->qry.req; - mlassert(req->client, "request must belong to a client"); - - if ((qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE_CB || qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_CLOSE)) { - /* Query can't be freed until uv callback is called. */ - qry->qry.state = _OUTPUT_DNSSIM_QUERY_PENDING_CLOSE; - return; - } - - _ll_try_remove(req->client->pending, &qry->qry); - if (qry->conn) { - _output_dnssim_connection_t* conn = qry->conn; - _ll_try_remove(conn->queued, &qry->qry); /* edge-case of cancelled queries */ - _ll_try_remove(conn->sent, &qry->qry); - qry->conn = NULL; - _output_dnssim_conn_idle(conn); - } - - _ll_remove(req->qry, &qry->qry); - free(qry); -} diff --git a/src/output/dnssim/tls.c b/src/output/dnssim/tls.c deleted file mode 100644 index e87ca47..0000000 --- a/src/output/dnssim/tls.c +++ /dev/null @@ -1,475 +0,0 @@ -/* - * Copyright (c) 2020, CZ.NIC, z.s.p.o. - * All rights reserved. - * - * This file is part of dnsjit. - * - * dnsjit is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * dnsjit is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with dnsjit. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include "output/dnssim.h" -#include "output/dnssim/internal.h" -#include "output/dnssim/ll.h" -#include "core/assert.h" - -#include <gnutls/gnutls.h> -#include <string.h> - -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - -#ifndef MIN -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) /** Minimum of two numbers **/ -#endif - -static core_log_t _log = LOG_T_INIT("output.dnssim"); - -struct async_write_ctx { - uv_write_t write_req; - _output_dnssim_connection_t* conn; - char buf[]; -}; - -static int _tls_handshake(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn is nil"); - mlassert(conn->tls, "conn must have tls context"); - mlassert(conn->client, "conn must belong to a client"); - mlassert(conn->state <= _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE, "conn in invalid state"); - - /* Set TLS session resumption ticket if available. */ - if (conn->state < _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE && conn->client->tls_ticket.size != 0) { - gnutls_datum_t* ticket = &conn->client->tls_ticket; - gnutls_session_set_data(conn->tls->session, ticket->data, ticket->size); - } - conn->state = _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE; - - return gnutls_handshake(conn->tls->session); -} - -void _output_dnssim_tls_process_input_data(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn is nil"); - mlassert(conn->client, "conn must have client"); - mlassert(conn->client->dnssim, "client must have dnssim"); - mlassert(conn->tls, "conn must have tls ctx"); - - if (conn->state >= _OUTPUT_DNSSIM_CONN_CLOSING) - return; - - output_dnssim_t* self = conn->client->dnssim; - - /* Ensure TLS handshake is performed before receiving data. - * See https://www.gnutls.org/manual/html_node/TLS-handshake.html */ - while (conn->state <= _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE) { - int err = _tls_handshake(conn); - mldebug("tls handshake returned: %s", gnutls_strerror(err)); - if (err == GNUTLS_E_SUCCESS) { - if (gnutls_session_is_resumed(conn->tls->session)) - conn->stats->conn_resumed++; - if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2) { - if (_output_dnssim_https2_setup(conn) < 0) { - _output_dnssim_conn_close(conn); - return; - } - } - _output_dnssim_conn_activate(conn); - break; - } else if (err == GNUTLS_E_AGAIN) { - return; /* Wait for more data */ - } else if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) { - gnutls_alert_description_t alert = gnutls_alert_get(conn->tls->session); - mlwarning("gnutls_handshake failed: %s", gnutls_alert_get_name(alert)); - _output_dnssim_conn_close(conn); - return; - } else if (gnutls_error_is_fatal(err)) { - mlwarning("gnutls_handshake failed: %s", gnutls_strerror_name(err)); - _output_dnssim_conn_close(conn); - return; - } - } - - /* See https://gnutls.org/manual/html_node/Data-transfer-and-termination.html#Data-transfer-and-termination */ - while (true) { - /* Connection might have been closed due to an error, don't try to use it. */ - if (conn->state < _OUTPUT_DNSSIM_CONN_ACTIVE || conn->state >= _OUTPUT_DNSSIM_CONN_CLOSING) - return; - - ssize_t count = gnutls_record_recv(conn->tls->session, _self->wire_buf, WIRE_BUF_SIZE); - if (count > 0) { - switch (_self->transport) { - case OUTPUT_DNSSIM_TRANSPORT_TLS: - _output_dnssim_read_dns_stream(conn, count, _self->wire_buf); - break; - case OUTPUT_DNSSIM_TRANSPORT_HTTPS2: - _output_dnssim_https2_process_input_data(conn, count, _self->wire_buf); - break; - default: - lfatal("unsupported transport layer"); - break; - } - } else if (count == GNUTLS_E_AGAIN) { - if (conn->tls->buf_pos == conn->tls->buf_len) { - /* See https://www.gnutls.org/manual/html_node/Asynchronous-operation.html */ - break; /* No more data available in this libuv buffer */ - } - continue; - } else if (count == GNUTLS_E_INTERRUPTED) { - continue; - } else if (count == GNUTLS_E_REHANDSHAKE) { - continue; /* Ignore rehandshake request. */ - } else if (count < 0) { - mlwarning("gnutls_record_recv failed: %s", gnutls_strerror_name(count)); - _output_dnssim_conn_close(conn); - return; - } else if (count == 0) { - break; - } - } - mlassert(conn->tls->buf_len == conn->tls->buf_pos, "tls didn't read the entire buffer"); -} - -static ssize_t _tls_pull(gnutls_transport_ptr_t ptr, void* buf, size_t len) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)ptr; - mlassert(conn != NULL, "conn is null"); - mlassert(conn->tls != NULL, "conn must have tls ctx"); - - ssize_t avail = conn->tls->buf_len - conn->tls->buf_pos; - if (avail <= 0) { - mldebug("tls pull: no more data"); - errno = EAGAIN; - return -1; - } - - ssize_t transfer = MIN(avail, len); - memcpy(buf, conn->tls->buf + conn->tls->buf_pos, transfer); - conn->tls->buf_pos += transfer; - return transfer; -} - -static void _tls_on_write_complete(uv_write_t* req, int status) -{ - mlassert(req->data != NULL, "uv_write req has no data pointer"); - struct async_write_ctx* async_ctx = (struct async_write_ctx*)req->data; - _output_dnssim_connection_t* conn = async_ctx->conn; - mlassert(conn, "conn is nil"); - mlassert(conn->tls, "conn must have tls ctx"); - mlassert(conn->tls->write_queue_size > 0, "invalid write_queue_size: %d", conn->tls->write_queue_size); - conn->tls->write_queue_size -= 1; - free(req->data); - - if (status < 0) - _output_dnssim_conn_close(conn); -} - -static ssize_t _tls_vec_push(gnutls_transport_ptr_t ptr, const giovec_t* iov, int iovcnt) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)ptr; - mlassert(conn != NULL, "conn is null"); - mlassert(conn->tls != NULL, "conn must have tls ctx"); - - if (iovcnt == 0) - return 0; - - /* - * This is a little bit complicated. There are two different writes: - * 1. Immediate, these don't need to own the buffered data and return immediately - * 2. Asynchronous, these need to own the buffers until the write completes - * In order to avoid copying the buffer, an immediate write is tried first if possible. - * If it isn't possible to write the data without queueing, an asynchronous write - * is created (with copied buffered data). - */ - - size_t total_len = 0; - uv_buf_t uv_buf[iovcnt]; - int i; - for (i = 0; i < iovcnt; ++i) { - uv_buf[i].base = iov[i].iov_base; - uv_buf[i].len = iov[i].iov_len; - total_len += iov[i].iov_len; - } - - /* Try to perform the immediate write first to avoid copy */ - int ret = 0; - if (conn->tls->write_queue_size == 0) { - ret = uv_try_write((uv_stream_t*)conn->handle, uv_buf, iovcnt); - /* from libuv documentation - - uv_try_write will return either: - > 0: number of bytes written (can be less than the supplied buffer size). - < 0: negative error code (UV_EAGAIN is returned if no data can be sent immediately). - */ - if (ret == total_len) { - /* All the data were buffered by libuv. - * Return. */ - return ret; - } - - if (ret < 0 && ret != UV_EAGAIN) { - /* uv_try_write() has returned error code other then UV_EAGAIN. - * Return. */ - errno = EIO; - return -1; - } - /* Since we are here expression below is true - * (ret != total_len) && (ret >= 0 || ret == UV_EAGAIN) - * or the same - * (ret != total_len && ret >= 0) || (ret != total_len && ret == UV_EAGAIN) - * i.e. either occurs partial write or UV_EAGAIN. - * Proceed and copy data amount to owned memory and perform async write. - */ - if (ret == UV_EAGAIN) { - /* No data were buffered, so we must buffer all the data. */ - ret = 0; - } - } - - /* Fallback when the queue is full, and it's not possible to do an immediate write */ - char* p = malloc(sizeof(struct async_write_ctx) + total_len - ret); - if (p != NULL) { - struct async_write_ctx* async_ctx = (struct async_write_ctx*)p; - async_ctx->conn = conn; - char* buf = async_ctx->buf; - /* Skip data written in the partial write */ - size_t to_skip = ret; - /* Copy the buffer into owned memory */ - size_t off = 0; - int i; - for (i = 0; i < iovcnt; ++i) { - if (to_skip > 0) { - /* Ignore current buffer if it's all skipped */ - if (to_skip >= uv_buf[i].len) { - to_skip -= uv_buf[i].len; - continue; - } - /* Skip only part of the buffer */ - uv_buf[i].base += to_skip; - uv_buf[i].len -= to_skip; - to_skip = 0; - } - memcpy(buf + off, uv_buf[i].base, uv_buf[i].len); - off += uv_buf[i].len; - } - uv_buf[0].base = buf; - uv_buf[0].len = off; - - /* Create an asynchronous write request */ - uv_write_t* write_req = &async_ctx->write_req; - memset(write_req, 0, sizeof(uv_write_t)); - write_req->data = p; - - /* Perform an asynchronous write with a callback */ - if (uv_write(write_req, (uv_stream_t*)conn->handle, uv_buf, 1, _tls_on_write_complete) == 0) { - ret = total_len; - conn->tls->write_queue_size += 1; - } else { - free(p); - errno = EIO; - ret = -1; - } - } else { - errno = ENOMEM; - ret = -1; - } - - return ret; -} - -int _tls_pull_timeout(gnutls_transport_ptr_t ptr, unsigned int ms) -{ - _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)ptr; - mlassert(conn != NULL, "conn is null"); - mlassert(conn->tls != NULL, "conn must have tls ctx"); - - ssize_t avail = conn->tls->buf_len - conn->tls->buf_pos; - if (avail <= 0) { - errno = EAGAIN; - return -1; - } - return avail; -} - -int _output_dnssim_tls_init(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn is nil"); - mlassert(conn->tls == NULL, "conn already has tls context"); - - int ret; - mlfatal_oom(conn->tls = malloc(sizeof(_output_dnssim_tls_ctx_t))); - conn->tls->buf = NULL; - conn->tls->buf_len = 0; - conn->tls->buf_pos = 0; - conn->tls->write_queue_size = 0; - - ret = gnutls_init(&conn->tls->session, GNUTLS_CLIENT | GNUTLS_NONBLOCK); - if (ret < 0) { - mldebug("failed gnutls_init() (%s)", gnutls_strerror(ret)); - free(conn->tls); - conn->tls = 0; - return ret; - } - - output_dnssim_t* self = conn->client->dnssim; - if (_self->tls_priority == NULL) { - ret = gnutls_set_default_priority(conn->tls->session); - if (ret < 0) { - mldebug("failed gnutls_set_default_priority() (%s)", gnutls_strerror(ret)); - gnutls_deinit(conn->tls->session); - free(conn->tls); - conn->tls = 0; - return ret; - } - } else { - ret = gnutls_priority_set(conn->tls->session, *_self->tls_priority); - if (ret < 0) { - mldebug("failed gnutls_priority_set() (%s)", gnutls_strerror(ret)); - gnutls_deinit(conn->tls->session); - free(conn->tls); - conn->tls = 0; - return ret; - } - } - - ret = gnutls_credentials_set(conn->tls->session, GNUTLS_CRD_CERTIFICATE, _self->tls_cred); - if (ret < 0) { - mldebug("failed gnutls_credentials_set() (%s)", gnutls_strerror(ret)); - gnutls_deinit(conn->tls->session); - free(conn->tls); - conn->tls = 0; - return ret; - } - - gnutls_transport_set_pull_function(conn->tls->session, _tls_pull); - gnutls_transport_set_pull_timeout_function(conn->tls->session, _tls_pull_timeout); - gnutls_transport_set_vec_push_function(conn->tls->session, _tls_vec_push); - gnutls_transport_set_ptr(conn->tls->session, conn); - - return 0; -} - -int _output_dnssim_create_query_tls(output_dnssim_t* self, _output_dnssim_request_t* req) -{ - mlassert_self(); - lassert(req, "req is nil"); - lassert(req->client, "request must have a client associated with it"); - - _output_dnssim_query_tcp_t* qry; - - lfatal_oom(qry = calloc(1, sizeof(_output_dnssim_query_tcp_t))); - - qry->qry.transport = OUTPUT_DNSSIM_TRANSPORT_TLS; - qry->qry.req = req; - qry->qry.state = _OUTPUT_DNSSIM_QUERY_PENDING_WRITE; - req->qry = &qry->qry; // TODO change when adding support for multiple Qs for req - _ll_append(req->client->pending, &qry->qry); - - return _output_dnssim_handle_pending_queries(req->client); -} - -void _output_dnssim_close_query_tls(_output_dnssim_query_tcp_t* qry) -{ - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.req, "query must be part of a request"); - _output_dnssim_request_t* req = qry->qry.req; - mlassert(req->client, "request must belong to a client"); - - _ll_try_remove(req->client->pending, &qry->qry); - if (qry->conn) { - _output_dnssim_connection_t* conn = qry->conn; - _ll_try_remove(conn->sent, &qry->qry); - qry->conn = NULL; - _output_dnssim_conn_idle(conn); - } - - _ll_remove(req->qry, &qry->qry); - free(qry); -} - -void _output_dnssim_tls_close(_output_dnssim_connection_t* conn) -{ - mlassert(conn, "conn can't be nil"); - mlassert(conn->tls, "conn must have tls ctx"); - mlassert(conn->client, "conn must belong to a client"); - - /* Try and get a TLS session ticket for potential resumption. */ - int ret; - if (gnutls_session_get_flags(conn->tls->session) & GNUTLS_SFLAGS_SESSION_TICKET) { - if (conn->client->tls_ticket.size != 0) { - gnutls_free(conn->client->tls_ticket.data); - } - ret = gnutls_session_get_data2(conn->tls->session, &conn->client->tls_ticket); - if (ret < 0) { - mldebug("gnutls_session_get_data2 failed: %s", gnutls_strerror(ret)); - conn->client->tls_ticket.size = 0; - } - } - - gnutls_deinit(conn->tls->session); - _output_dnssim_tcp_close(conn); -} - -void _output_dnssim_tls_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry) -{ - mlassert(qry, "qry can't be null"); - mlassert(qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE, "qry must be pending write"); - mlassert(qry->qry.req, "req can't be null"); - mlassert(qry->qry.req->dns_q, "dns_q can't be null"); - mlassert(qry->qry.req->dns_q->obj_prev, "payload can't be null"); - mlassert(conn, "conn can't be null"); - mlassert(conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE, "connection state != ACTIVE"); - mlassert(conn->tls, "conn must have tls ctx"); - mlassert(conn->client, "conn must be associated with client"); - mlassert(conn->client->pending, "conn has no pending queries"); - - core_object_payload_t* payload = (core_object_payload_t*)qry->qry.req->dns_q->obj_prev; - uint16_t len = htons(payload->len); - - gnutls_record_cork(conn->tls->session); - ssize_t count = 0; - if ((count = gnutls_record_send(conn->tls->session, &len, sizeof(len)) < 0) || (count = gnutls_record_send(conn->tls->session, payload->payload, payload->len) < 0)) { - mlwarning("gnutls_record_send failed: %s", gnutls_strerror_name(count)); - _output_dnssim_conn_close(conn); - return; - } - - const ssize_t submitted = sizeof(len) + payload->len; - - int ret = gnutls_record_uncork(conn->tls->session, GNUTLS_RECORD_WAIT); - if (gnutls_error_is_fatal(ret)) { - mlinfo("gnutls_record_uncorck failed: %s", gnutls_strerror_name(ret)); - _output_dnssim_conn_close(conn); - return; - } - - if (ret != submitted) { - mlwarning("gnutls_record_uncork didn't send all data"); - _output_dnssim_conn_close(conn); - return; - } - - qry->conn = conn; - _ll_remove(conn->client->pending, &qry->qry); - _ll_append(conn->sent, &qry->qry); - - /* Stop idle timer, since there are queries to answer now. */ - if (conn->idle_timer != NULL) { - conn->is_idle = false; - uv_timer_stop(conn->idle_timer); - } - - qry->qry.state = _OUTPUT_DNSSIM_QUERY_SENT; -} - -#endif diff --git a/src/output/dnssim/udp.c b/src/output/dnssim/udp.c deleted file mode 100644 index 74f8569..0000000 --- a/src/output/dnssim/udp.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o. - * All rights reserved. - * - * This file is part of dnsjit. - * - * dnsjit is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * dnsjit is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with dnsjit. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include "output/dnssim.h" -#include "output/dnssim/internal.h" -#include "output/dnssim/ll.h" -#include "core/assert.h" - -static core_log_t _log = LOG_T_INIT("output.dnssim"); - -static int _process_udp_response(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf) -{ - _output_dnssim_query_udp_t* qry = (_output_dnssim_query_udp_t*)handle->data; - _output_dnssim_request_t* req; - core_object_payload_t payload = CORE_OBJECT_PAYLOAD_INIT(NULL); - core_object_dns_t dns_a = CORE_OBJECT_DNS_INIT(&payload); - mlassert(qry, "qry is nil"); - mlassert(qry->qry.req, "query must be part of a request"); - req = qry->qry.req; - - payload.payload = (uint8_t*)buf->base; - payload.len = nread; - - dns_a.obj_prev = (core_object_t*)&payload; - int ret = core_object_dns_parse_header(&dns_a); - if (ret != 0) { - mldebug("udp response malformed"); - return _ERR_MALFORMED; - } - if (dns_a.id != req->dns_q->id) { - mldebug("udp response msgid mismatch %x(q) != %x(a)", req->dns_q->id, dns_a.id); - return _ERR_MSGID; - } - if (dns_a.tc == 1) { - mldebug("udp response has TC=1"); - return _ERR_TC; - } - ret = _output_dnssim_answers_request(req, &dns_a); - if (ret != 0) { - mlwarning("udp reponse question mismatch"); - return _ERR_QUESTION; - } - - _output_dnssim_request_answered(req, &dns_a); - return 0; -} - -static void _on_udp_query_recv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) -{ - if (nread > 0) { - mldebug("udp recv: %d", nread); - - // TODO handle TC=1 - _process_udp_response(handle, nread, buf); - } - - if (buf->base != NULL) { - free(buf->base); - } -} - -static void _on_query_udp_closed(uv_handle_t* handle) -{ - _output_dnssim_query_udp_t* qry = (_output_dnssim_query_udp_t*)handle->data; - _output_dnssim_request_t* req; - mlassert(qry, "qry is nil"); - mlassert(qry->qry.req, "query must be part of a request"); - req = qry->qry.req; - - free(qry->handle); - - _ll_remove(req->qry, &qry->qry); - free(qry); - - if (req->qry == NULL) - _output_dnssim_maybe_free_request(req); -} - -void _output_dnssim_close_query_udp(_output_dnssim_query_udp_t* qry) -{ - int ret; - mlassert(qry, "qry is nil"); - - ret = uv_udp_recv_stop(qry->handle); - if (ret < 0) { - mldebug("failed uv_udp_recv_stop(): %s", uv_strerror(ret)); - } - - uv_close((uv_handle_t*)qry->handle, _on_query_udp_closed); -} - -int _output_dnssim_create_query_udp(output_dnssim_t* self, _output_dnssim_request_t* req) -{ - int ret; - _output_dnssim_query_udp_t* qry; - core_object_payload_t* payload; - mlassert_self(); - lassert(req, "req is nil"); - payload = (core_object_payload_t*)req->dns_q->obj_prev; - - lfatal_oom(qry = calloc(1, sizeof(_output_dnssim_query_udp_t))); - lfatal_oom(qry->handle = malloc(sizeof(uv_udp_t))); - - qry->qry.transport = OUTPUT_DNSSIM_TRANSPORT_UDP; - qry->qry.req = req; - qry->buf = uv_buf_init((char*)payload->payload, payload->len); - qry->handle->data = (void*)qry; - ret = uv_udp_init(&_self->loop, qry->handle); - if (ret < 0) { - lwarning("failed to init uv_udp_t"); - goto failure; - } - _ll_append(req->qry, &qry->qry); - - ret = _output_dnssim_bind_before_connect(self, (uv_handle_t*)qry->handle); - if (ret < 0) - return ret; - - ret = uv_udp_try_send(qry->handle, &qry->buf, 1, (struct sockaddr*)&_self->target); - if (ret < 0) { - lwarning("failed to send udp packet: %s", uv_strerror(ret)); - return ret; - } - - // listen for reply - ret = uv_udp_recv_start(qry->handle, _output_dnssim_on_uv_alloc, _on_udp_query_recv); - if (ret < 0) { - lwarning("failed uv_udp_recv_start(): %s", uv_strerror(ret)); - return ret; - } - - return 0; -failure: - free(qry->handle); - free(qry); - return ret; -} |