diff options
Diffstat (limited to '')
-rw-r--r-- | include/dnsjit/filter/copy.h (renamed from src/output/dnssim.h) | 13 | ||||
-rw-r--r-- | src/output/dnssim.c | 502 | ||||
-rw-r--r-- | src/output/dnssim.hh | 123 | ||||
-rw-r--r-- | src/output/dnssim.lua | 433 | ||||
-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 |
13 files changed, 6 insertions, 3941 deletions
diff --git a/src/output/dnssim.h b/include/dnsjit/filter/copy.h index f843000..d65a552 100644 --- a/src/output/dnssim.h +++ b/include/dnsjit/filter/copy.h @@ -18,14 +18,13 @@ * along with dnsjit. If not, see <http://www.gnu.org/licenses/>. */ -#include "core/log.h" -#include "core/receiver.h" +#include <dnsjit/core/log.h> +#include <dnsjit/core/object.h> +#include <dnsjit/core/receiver.h> -#ifndef __dnsjit_output_dnssim_h -#define __dnsjit_output_dnssim_h +#ifndef __dnsjit_filter_copy_h +#define __dnsjit_filter_copy_h -#include <stdbool.h> - -#include "output/dnssim.hh" +#include <dnsjit/filter/copy.hh> #endif diff --git a/src/output/dnssim.c b/src/output/dnssim.c deleted file mode 100644 index acd0a05..0000000 --- a/src/output/dnssim.c +++ /dev/null @@ -1,502 +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 "core/object/ip.h" -#include "core/object/ip6.h" - -#include <gnutls/gnutls.h> -#include <string.h> - -static core_log_t _log = LOG_T_INIT("output.dnssim"); -static output_dnssim_t _defaults = { LOG_T_INIT_OBJ("output.dnssim") }; - -static uint64_t _now_ms() -{ -#if HAVE_CLOCK_NANOSLEEP - struct timespec ts; - uint64_t now_ms; - if (clock_gettime(CLOCK_REALTIME, &ts)) { - mlfatal("clock_gettime()"); - } - now_ms = ts.tv_sec * 1000; - now_ms += ts.tv_nsec / 1000000; - return now_ms; -#else - mlfatal("clock_gettime() not available"); - return 0; -#endif -} - -core_log_t* output_dnssim_log() -{ - return &_log; -} - -output_dnssim_t* output_dnssim_new(size_t max_clients) -{ - output_dnssim_t* self; - int ret, i; - - mlfatal_oom(self = calloc(1, sizeof(_output_dnssim_t))); - *self = _defaults; - self->handshake_timeout_ms = 5000; - self->idle_timeout_ms = 10000; - output_dnssim_timeout_ms(self, 2000); - - _self->source = NULL; - _self->transport = OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY; - _self->h2_zero_out_msgid = false; - - self->max_clients = max_clients; - lfatal_oom(_self->client_arr = calloc(max_clients, sizeof(_output_dnssim_client_t))); - - for (i = 0; i < max_clients; ++i) { - _self->client_arr[i].dnssim = self; - } - - ret = gnutls_certificate_allocate_credentials(&_self->tls_cred); - if (ret < 0) - lfatal("failed to allocated TLS credentials (%s)", gnutls_strerror(ret)); - - ret = uv_loop_init(&_self->loop); - if (ret < 0) - lfatal("failed to initialize uv_loop (%s)", uv_strerror(ret)); - ldebug("initialized uv_loop"); - - return self; -} - -void output_dnssim_free(output_dnssim_t* self) -{ - mlassert_self(); - int ret, i; - _output_dnssim_source_t* source; - _output_dnssim_source_t* first = _self->source; - output_dnssim_stats_t* stats_prev; - - free(self->stats_sum->latency); - free(self->stats_sum); - do { - stats_prev = self->stats_current->prev; - free(self->stats_current->latency); - free(self->stats_current); - self->stats_current = stats_prev; - } while (self->stats_current != NULL); - - if (_self->source != NULL) { - // free cilcular linked list - do { - source = _self->source->next; - free(_self->source); - _self->source = source; - } while (_self->source != first); - } - - for (i = 0; i < self->max_clients; ++i) { - if (_self->client_arr[i].tls_ticket.size != 0) { - gnutls_free(_self->client_arr[i].tls_ticket.data); - } - } - free(_self->client_arr); - - ret = uv_loop_close(&_self->loop); - if (ret < 0) { - lcritical("failed to close uv_loop (%s)", uv_strerror(ret)); - } else { - ldebug("closed uv_loop"); - } - - gnutls_certificate_free_credentials(_self->tls_cred); - if (_self->tls_priority != NULL) { - gnutls_priority_deinit(*_self->tls_priority); - free(_self->tls_priority); - } - - free(self); -} - -void output_dnssim_log_name(output_dnssim_t* self, const char* name) -{ - mlassert_self(); - lassert(name, "name is nil"); - - strncpy(self->_log.name, name, sizeof(self->_log.name) - 1); - self->_log.name[sizeof(self->_log.name) - 1] = 0; - self->_log.is_obj = false; -} - -static uint32_t _extract_client(const core_object_t* obj) -{ - uint32_t client; - uint8_t* ip; - - switch (obj->obj_type) { - case CORE_OBJECT_IP: - ip = ((core_object_ip_t*)obj)->dst; - break; - case CORE_OBJECT_IP6: - ip = ((core_object_ip6_t*)obj)->dst; - break; - default: - return -1; - } - - memcpy(&client, ip, sizeof(client)); - return client; -} - -static void _receive(output_dnssim_t* self, const core_object_t* obj) -{ - mlassert_self(); - core_object_t* current = (core_object_t*)obj; - core_object_payload_t* payload; - uint32_t client; - - self->processed++; - - /* get payload from packet */ - for (;;) { - if (current->obj_type == CORE_OBJECT_PAYLOAD) { - payload = (core_object_payload_t*)current; - break; - } - if (current->obj_prev == NULL) { - self->discarded++; - lwarning("packet discarded (missing payload object)"); - return; - } - current = (core_object_t*)current->obj_prev; - } - - /* extract client information from IP/IP6 layer */ - for (;;) { - if (current->obj_type == CORE_OBJECT_IP || current->obj_type == CORE_OBJECT_IP6) { - client = _extract_client(current); - break; - } - if (current->obj_prev == NULL) { - self->discarded++; - lwarning("packet discarded (missing ip/ip6 object)"); - return; - } - current = (core_object_t*)current->obj_prev; - } - - if (self->free_after_use) { - /* free all objects except payload */ - current = (core_object_t*)obj; - core_object_t* parent = current; - while (current != NULL) { - parent = current; - current = (core_object_t*)current->obj_prev; - if (parent->obj_type != CORE_OBJECT_PAYLOAD) { - core_object_free(parent); - } - } - } - - if (_self->h2_zero_out_msgid) { - lassert(_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2, "must use HTTP/2 to zero-out msgid"); - if (payload->len < 2) { - self->discarded++; - lwarning("packet discarded (payload len < 2)"); - return; - } - uint8_t* data = (uint8_t*)payload->payload; - data[0] = 0x00; - data[1] = 0x00; - } - - if (client >= self->max_clients) { - self->discarded++; - lwarning("packet discarded (client exceeded max_clients)"); - return; - } - - ldebug("client(c): %d", client); - _output_dnssim_create_request(self, &_self->client_arr[client], payload); -} - -core_receiver_t output_dnssim_receiver() -{ - return (core_receiver_t)_receive; -} - -void output_dnssim_set_transport(output_dnssim_t* self, output_dnssim_transport_t tr) -{ - mlassert_self(); - - switch (tr) { - case OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY: - lnotice("transport set to UDP (no TCP fallback)"); - break; - case OUTPUT_DNSSIM_TRANSPORT_TCP: - lnotice("transport set to TCP"); - break; - case OUTPUT_DNSSIM_TRANSPORT_TLS: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - lnotice("transport set to TLS"); -#else - lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - case OUTPUT_DNSSIM_TRANSPORT_HTTPS2: -#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION - lnotice("transport set to HTTP/2 over TLS"); - if (&_self->h2_uri_authority[0]) - lnotice("set uri authority to: %s", _self->h2_uri_authority); -#else - lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG); -#endif - break; - case OUTPUT_DNSSIM_TRANSPORT_UDP: - lfatal("UDP transport with TCP fallback is not supported yet."); - break; - default: - lfatal("unknown or unsupported transport"); - break; - } - - _self->transport = tr; -} - -int output_dnssim_target(output_dnssim_t* self, const char* ip, uint16_t port) -{ - int ret; - mlassert_self(); - lassert(ip, "ip is nil"); - lassert(port, "port is nil"); - - ret = uv_ip6_addr(ip, port, (struct sockaddr_in6*)&_self->target); - if (ret != 0) { - ret = uv_ip4_addr(ip, port, (struct sockaddr_in*)&_self->target); - if (ret != 0) { - lfatal("failed to parse IPv4 or IPv6 from \"%s\"", ip); - } else { - ret = snprintf(_self->h2_uri_authority, _MAX_URI_LEN, "%s:%d", ip, port); - } - } else { - ret = snprintf(_self->h2_uri_authority, _MAX_URI_LEN, "[%s]:%d", ip, port); - } - - if (ret > 0) { - if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2) - lnotice("set uri authority to: %s", _self->h2_uri_authority); - } else { - _self->h2_uri_authority[0] = '\0'; - if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2) - lfatal("failed to set authority"); - } - - lnotice("set target to %s port %d", ip, port); - return 0; -} - -int output_dnssim_bind(output_dnssim_t* self, const char* ip) -{ - int ret; - mlassert_self(); - lassert(ip, "ip is nil"); - - _output_dnssim_source_t* source; - lfatal_oom(source = malloc(sizeof(_output_dnssim_source_t))); - - ret = uv_ip6_addr(ip, 0, (struct sockaddr_in6*)&source->addr); - if (ret != 0) { - ret = uv_ip4_addr(ip, 0, (struct sockaddr_in*)&source->addr); - if (ret != 0) { - lfatal("failed to parse IPv4 or IPv6 from \"%s\"", ip); - } - } - - if (_self->source == NULL) { - source->next = source; - _self->source = source; - } else { - source->next = _self->source->next; - _self->source->next = source; - } - - lnotice("bind to source address %s", ip); - return 0; -} - -int output_dnssim_tls_priority(output_dnssim_t* self, const char* priority) -{ - mlassert_self(); - lassert(priority, "priority is nil"); - - if (_self->tls_priority != NULL) { - gnutls_priority_deinit(*_self->tls_priority); - free(_self->tls_priority); - } - lfatal_oom(_self->tls_priority = malloc(sizeof(gnutls_priority_t))); - - int ret = gnutls_priority_init(_self->tls_priority, priority, NULL); - if (ret < 0) { - lfatal("failed to initialize TLS priority cache: %s", gnutls_strerror(ret)); - } else { - lnotice("GnuTLS priority set: %s", priority); - } - - return 0; -} - -int output_dnssim_run_nowait(output_dnssim_t* self) -{ - mlassert_self(); - - return uv_run(&_self->loop, UV_RUN_NOWAIT); -} - -void output_dnssim_timeout_ms(output_dnssim_t* self, uint64_t timeout_ms) -{ - mlassert_self(); - lassert(timeout_ms > 0, "timeout must be greater than 0"); - - if (self->stats_sum != NULL) { - free(self->stats_sum->latency); - free(self->stats_sum); - self->stats_sum = 0; - } - if (self->stats_current != NULL) { - output_dnssim_stats_t* stats_prev; - do { - stats_prev = self->stats_current->prev; - free(self->stats_current->latency); - free(self->stats_current); - self->stats_current = stats_prev; - } while (self->stats_current != NULL); - } - - self->timeout_ms = timeout_ms; - - lfatal_oom(self->stats_sum = calloc(1, sizeof(output_dnssim_stats_t))); - lfatal_oom(self->stats_sum->latency = calloc(self->timeout_ms + 1, sizeof(uint64_t))); - - lfatal_oom(self->stats_current = calloc(1, sizeof(output_dnssim_stats_t))); - lfatal_oom(self->stats_current->latency = calloc(self->timeout_ms + 1, sizeof(uint64_t))); - - self->stats_first = self->stats_current; -} - -void output_dnssim_h2_uri_path(output_dnssim_t* self, const char* uri_path) -{ - mlassert_self(); - lassert(uri_path, "uri_path is nil"); - lassert(strlen(uri_path) < _MAX_URI_LEN, "uri_path too long"); - - strncpy(_self->h2_uri_path, uri_path, _MAX_URI_LEN - 1); - _self->h2_uri_path[_MAX_URI_LEN - 1] = 0; - lnotice("http2: set uri path to: %s", _self->h2_uri_path); -} - -void output_dnssim_h2_method(output_dnssim_t* self, const char* method) -{ - mlassert_self(); - lassert(method, "method is nil"); - - if (strcmp("GET", method) == 0) { - _self->h2_method = OUTPUT_DNSSIM_H2_GET; - } else if (strcmp("POST", method) == 0) { - _self->h2_method = OUTPUT_DNSSIM_H2_POST; - } else { - lfatal("http2: unsupported method: \"%s\"", method); - } - - lnotice("http2: set method to %s", method); -} - -void output_dnssim_h2_zero_out_msgid(output_dnssim_t* self, bool zero_out_msgid) -{ - mlassert_self(); - - if (zero_out_msgid) { - lassert(_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2, "transport must be set to HTTP/2 to set zero_out_msgid"); - _self->h2_zero_out_msgid = zero_out_msgid; - } -} - -static void _on_stats_timer_tick(uv_timer_t* handle) -{ - uint64_t now_ms = _now_ms(); - output_dnssim_t* self; - mlassert(handle, "handle is nil"); - self = (output_dnssim_t*)handle->data; - mlassert_self(); - lassert(self->stats_sum, "stats_sum is nil"); - lassert(self->stats_current, "stats_current is nil"); - - lnotice("total processed:%10ld; answers:%10ld; discarded:%10ld; ongoing:%10ld", - self->processed, self->stats_sum->answers, self->discarded, self->ongoing); - - output_dnssim_stats_t* stats_next; - lfatal_oom(stats_next = calloc(1, sizeof(output_dnssim_stats_t))); - lfatal_oom(stats_next->latency = calloc(self->timeout_ms + 1, sizeof(uint64_t))); - - self->stats_current->until_ms = now_ms; - stats_next->since_ms = now_ms; - stats_next->conn_active = self->stats_current->conn_active; - - stats_next->ongoing = self->ongoing; - stats_next->prev = self->stats_current; - self->stats_current->next = stats_next; - self->stats_current = stats_next; -} - -void output_dnssim_stats_collect(output_dnssim_t* self, uint64_t interval_ms) -{ - uint64_t now_ms = _now_ms(); - mlassert_self(); - lassert(self->stats_sum, "stats_sum is nil"); - lassert(self->stats_current, "stats_current is nil"); - - if (self->stats_interval_ms != 0) { - lfatal("statistics collection has already started!"); - } - self->stats_interval_ms = interval_ms; - - self->stats_sum->since_ms = now_ms; - self->stats_current->since_ms = now_ms; - - _self->stats_timer.data = (void*)self; - uv_timer_init(&_self->loop, &_self->stats_timer); - uv_timer_start(&_self->stats_timer, _on_stats_timer_tick, interval_ms, interval_ms); -} - -void output_dnssim_stats_finish(output_dnssim_t* self) -{ - uint64_t now_ms = _now_ms(); - mlassert_self(); - lassert(self->stats_sum, "stats_sum is nil"); - lassert(self->stats_current, "stats_current is nil"); - - self->stats_sum->until_ms = now_ms; - self->stats_current->until_ms = now_ms; - - uv_timer_stop(&_self->stats_timer); - uv_close((uv_handle_t*)&_self->stats_timer, NULL); -} diff --git a/src/output/dnssim.hh b/src/output/dnssim.hh deleted file mode 100644 index a17125d..0000000 --- a/src/output/dnssim.hh +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2018-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/>. - */ - -//lua:require("dnsjit.core.log") -//lua:require("dnsjit.core.receiver_h") - -typedef enum output_dnssim_transport { - OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY, - OUTPUT_DNSSIM_TRANSPORT_UDP, - OUTPUT_DNSSIM_TRANSPORT_TCP, - OUTPUT_DNSSIM_TRANSPORT_TLS, - OUTPUT_DNSSIM_TRANSPORT_HTTPS2 -} output_dnssim_transport_t; - -typedef enum output_dnssim_h2_method { - OUTPUT_DNSSIM_H2_GET, - OUTPUT_DNSSIM_H2_POST -} output_dnssim_h2_method_t; - -typedef struct output_dnssim_stats output_dnssim_stats_t; -struct output_dnssim_stats { - output_dnssim_stats_t* prev; - output_dnssim_stats_t* next; - - uint64_t* latency; - - uint64_t since_ms; - uint64_t until_ms; - - uint64_t requests; - uint64_t ongoing; - uint64_t answers; - - /* Number of connections that are open at the end of the stats interval. */ - uint64_t conn_active; - - /* Number of connection handshake attempts during the stats interval. */ - uint64_t conn_handshakes; - - /* Number of connection that have been resumed with TLS session resumption. */ - uint64_t conn_resumed; - - /* Number of timed out connection handshakes during the stats interval. */ - uint64_t conn_handshakes_failed; - - uint64_t rcode_noerror; - uint64_t rcode_formerr; - uint64_t rcode_servfail; - uint64_t rcode_nxdomain; - uint64_t rcode_notimp; - uint64_t rcode_refused; - uint64_t rcode_yxdomain; - uint64_t rcode_yxrrset; - uint64_t rcode_nxrrset; - uint64_t rcode_notauth; - uint64_t rcode_notzone; - uint64_t rcode_badvers; - uint64_t rcode_badkey; - uint64_t rcode_badtime; - uint64_t rcode_badmode; - uint64_t rcode_badname; - uint64_t rcode_badalg; - uint64_t rcode_badtrunc; - uint64_t rcode_badcookie; - uint64_t rcode_other; -}; - -typedef struct output_dnssim { - core_log_t _log; - - uint64_t processed; - uint64_t discarded; - uint64_t ongoing; - - output_dnssim_stats_t* stats_sum; - output_dnssim_stats_t* stats_current; - output_dnssim_stats_t* stats_first; - - size_t max_clients; - bool free_after_use; - - uint64_t timeout_ms; - uint64_t idle_timeout_ms; - uint64_t handshake_timeout_ms; - uint64_t stats_interval_ms; -} output_dnssim_t; - -core_log_t* output_dnssim_log(); - -output_dnssim_t* output_dnssim_new(size_t max_clients); -void output_dnssim_free(output_dnssim_t* self); - -void output_dnssim_log_name(output_dnssim_t* self, const char* name); -void output_dnssim_set_transport(output_dnssim_t* self, output_dnssim_transport_t tr); -int output_dnssim_target(output_dnssim_t* self, const char* ip, uint16_t port); -int output_dnssim_bind(output_dnssim_t* self, const char* ip); -int output_dnssim_tls_priority(output_dnssim_t* self, const char* priority); -int output_dnssim_run_nowait(output_dnssim_t* self); -void output_dnssim_timeout_ms(output_dnssim_t* self, uint64_t timeout_ms); -void output_dnssim_h2_uri_path(output_dnssim_t* self, const char* uri_path); -void output_dnssim_h2_method(output_dnssim_t* self, const char* method); -void output_dnssim_h2_zero_out_msgid(output_dnssim_t* self, bool zero_out_msgid); -void output_dnssim_stats_collect(output_dnssim_t* self, uint64_t interval_ms); -void output_dnssim_stats_finish(output_dnssim_t* self); - -core_receiver_t output_dnssim_receiver(); diff --git a/src/output/dnssim.lua b/src/output/dnssim.lua deleted file mode 100644 index 25193c4..0000000 --- a/src/output/dnssim.lua +++ /dev/null @@ -1,433 +0,0 @@ --- Copyright (c) 2018-2021, 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/>. - --- dnsjit.output.dnssim --- Simulate independent DNS clients over various transports --- output = require("dnsjit.output.dnssim").new() --- .SS Usage --- output:udp() --- output:target("::1", 53) --- recv, rctx = output:receive() --- -- pass in objects using recv(rctx, obj) --- -- repeatedly call output:run_nowait() until it returns 0 --- .SS DNS-over-TLS example configuration --- output:tls("NORMAL:-VERS-ALL:+VERS-TLS1.3") -- enforce TLS 1.3 --- .SS DNS-over-HTTPS/2 example configuration --- output:https2({ method = "POST", uri_path = "/doh" }) --- --- Output module for simulating traffic from huge number of independent, --- individual DNS clients. --- Uses libuv for asynchronous communication. --- There may only be a single DnsSim in a thread. --- Use --- .I dnsjit.core.thread --- to have multiple DnsSim instances. --- .P --- With proper use of this component, it is possible to simulate hundreds of --- thousands of clients when using a high-performance server. --- This also applies for state-full transports. --- The complete set-up is quite complex and requires other components. --- See DNS Shotgun --- .RI ( https://gitlab.nic.cz/knot/shotgun ) --- for dnsjit scripts ready for use for high-performance --- benchmarking. -module(...,package.seeall) - -require("dnsjit.output.dnssim_h") -local bit = require("bit") -local object = require("dnsjit.core.objects") -local ffi = require("ffi") -local C = ffi.C - -local DnsSim = {} - -local _DNSSIM_VERSION = 20210129 -local _DNSSIM_JSON_VERSION = 20200527 - --- Create a new DnsSim output for up to max_clients. -function DnsSim.new(max_clients) - local self = { - obj = C.output_dnssim_new(max_clients), - max_clients = max_clients, - } - ffi.gc(self.obj, C.output_dnssim_free) - return setmetatable(self, { __index = DnsSim }) -end - -local function _check_version(version, req_version) - if req_version == nil then - return version - end - local min_version = tonumber(req_version) - if min_version == nil then - C.output_dnssim_log():fatal("invalid version number: "..req_version) - return nil - end - if version >= min_version then - return version - end - return nil -end - --- Check that version of dnssim is at minimum the one passed as --- .B req_version --- and return the actual version number. --- Return nil if the condition is not met. --- --- If no --- .B req_version --- is specified no check is done and only the version number is returned. -function DnsSim.check_version(req_version) - return _check_version(_DNSSIM_VERSION, req_version) -end - --- Check that version of dnssim's JSON data format is at minimum the one passed as --- .B req_version --- and return the actual version number. --- Return nil if the condition is not met. --- --- If no --- .B req_version --- is specified no check is done and only the version number is returned. -function DnsSim.check_json_version(req_version) - return _check_version(_DNSSIM_JSON_VERSION, req_version) -end - --- Return the Log object to control logging of this instance or module. --- Optionally, set the instance's log name. --- Unique name should be used for each instance. -function DnsSim:log(name) - if self == nil then - return C.output_dnssim_log() - end - if name ~= nil then - C.output_dnssim_log_name(self.obj, name) - end - return self.obj._log -end - --- Set the target IPv4/IPv6 address where queries will be sent to. -function DnsSim:target(ip, port) - local nport = tonumber(port) - if nport == nil then - self.obj._log:fatal("invalid port: "..port) - return -1 - end - if nport <= 0 or nport > 65535 then - self.obj._log:fatal("invalid port number: "..nport) - return -1 - end - return C.output_dnssim_target(self.obj, ip, nport) -end - --- Specify source IPv4/IPv6 address for sending queries. --- Can be set multiple times. --- Addresses are selected round-robin when sending. -function DnsSim:bind(ip) - return C.output_dnssim_bind(self.obj, ip) -end - --- Set the preferred transport to UDP. --- --- When the optional argument --- .B tcp_fallback --- is set to true, individual queries are re-tried over TCP when TC bit is set in the answer. --- Defaults to --- .B false --- (aka only UDP is used). -function DnsSim:udp(tcp_fallback) - if tcp_fallback == true then - C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_UDP) - else - C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY) - end -end - --- Set the transport to TCP. -function DnsSim:tcp() - C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_TCP) -end - --- Set the transport to TLS. --- --- The optional argument --- .B tls_priority --- is a GnuTLS priority string, which can be used to select TLS versions, cipher suites etc. --- For example: --- --- .RB "- """ NORMAL:%NO_TICKETS """" --- will use defaults without TLS session resumption. --- --- .RB "- """ SECURE128:-VERS-ALL:+VERS-TLS1.3 """" --- will use only TLS 1.3 with 128-bit secure ciphers. --- --- Refer to: --- .I https://gnutls.org/manual/html_node/Priority-Strings.html -function DnsSim:tls(tls_priority) - if tls_priority ~= nil then - C.output_dnssim_tls_priority(self.obj, tls_priority) - end - C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_TLS) -end - --- Set the transport to HTTP/2 over TLS. --- --- .B http2_options --- is a lua table which supports the following keys: --- --- .B method: --- .B GET --- (default) --- or --- .B POST --- --- .B uri_path: --- where queries will be sent. --- Defaults to --- .B /dns-query --- --- .B zero_out_msgid: --- when --- .B true --- (default), query ID is always set to 0 --- --- See tls() method for --- .B tls_priority --- documentation. -function DnsSim:https2(http2_options, tls_priority) - if tls_priority ~= nil then - C.output_dnssim_tls_priority(self.obj, tls_priority) - end - - uri_path = "/dns-query" - zero_out_msgid = true - method = "GET" - - if http2_options ~= nil then - if type(http2_options) ~= "table" then - self.obj._log:fatal("http2_options must be a table") - else - if http2_options["uri_path"] ~= nil then - uri_path = http2_options["uri_path"] - end - if http2_options["zero_out_msgid"] ~= nil and http2_options["zero_out_msgid"] ~= true then - zero_out_msgid = false - end - if http2_options["method"] ~= nil then - method = http2_options["method"] - end - end - end - - C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_HTTPS2) - C.output_dnssim_h2_uri_path(self.obj, uri_path) - C.output_dnssim_h2_method(self.obj, method) - C.output_dnssim_h2_zero_out_msgid(self.obj, zero_out_msgid) -end - --- Set timeout for the individual requests in seconds (default 2s). --- --- .BR Beware : --- increasing this value while the target resolver isn't very responsive --- (cold cache, heavy load) may degrade DnsSim's performance and skew --- the results. -function DnsSim:timeout(seconds) - if seconds == nil then - seconds = 2 - end - timeout_ms = math.floor(seconds * 1000) - C.output_dnssim_timeout_ms(self.obj, timeout_ms) -end - --- Set TCP connection idle timeout for connection reuse according to RFC7766, --- Section 6.2.3 (defaults to 10s). --- When set to zero, connections are closed immediately after there are no --- more pending queries. -function DnsSim:idle_timeout(seconds) - if seconds == nil then - seconds = 10 - end - self.obj.idle_timeout_ms = math.floor(seconds * 1000) -end - --- Set TCP connection handshake timeout (defaults to 5s). --- During heavy load, the server may no longer accept new connections. --- This parameter ensures such connection attempts are aborted after the --- timeout expires. -function DnsSim:handshake_timeout(seconds) - if seconds == nil then - seconds = 5 - end - self.obj.handshake_timeout_ms = math.floor(seconds * 1000) -end - --- Run the libuv loop once without blocking when there is no I/O. --- This should be called repeatedly until 0 is returned and no more data --- is expected to be received by DnsSim. -function DnsSim:run_nowait() - return C.output_dnssim_run_nowait(self.obj) -end - --- Set this to true if DnsSim should free the memory of passed-in objects --- (useful when using --- .I dnsjit.filter.copy --- to pass objects from different thread). -function DnsSim:free_after_use(free_after_use) - self.obj.free_after_use = free_after_use -end - --- Number of input packets discarded due to various reasons. --- To investigate causes, run with increased logging level. -function DnsSim:discarded() - return tonumber(self.obj.discarded) -end - --- Number of valid requests (input packets) processed. -function DnsSim:requests() - return tonumber(self.obj.stats_sum.requests) -end - --- Number of requests that received an answer -function DnsSim:answers() - return tonumber(self.obj.stats_sum.answers) -end - --- Number of requests that received a NOERROR response -function DnsSim:noerror() - return tonumber(self.obj.stats_sum.rcode_noerror) -end - --- Configure statistics to be collected every N seconds. -function DnsSim:stats_collect(seconds) - if seconds == nil then - self.obj._log:fatal("number of seconds must be set for stats_collect()") - end - interval_ms = math.floor(seconds * 1000) - C.output_dnssim_stats_collect(self.obj, interval_ms) -end - --- Stop the collection of statistics. -function DnsSim:stats_finish() - C.output_dnssim_stats_finish(self.obj) -end - --- Export the results to a JSON file. -function DnsSim:export(filename) - local file = io.open(filename, "w") - if file == nil then - self.obj._log:fatal("export failed: no filename") - return - end - - local function write_stats(file, stats) - file:write( - "{ ", - '"since_ms":', tonumber(stats.since_ms), ',', - '"until_ms":', tonumber(stats.until_ms), ',', - '"requests":', tonumber(stats.requests), ',', - '"ongoing":', tonumber(stats.ongoing), ',', - '"answers":', tonumber(stats.answers), ',', - '"conn_active":', tonumber(stats.conn_active), ',', - '"conn_handshakes":', tonumber(stats.conn_handshakes), ',', - '"conn_resumed":', tonumber(stats.conn_resumed), ',', - '"conn_handshakes_failed":', tonumber(stats.conn_handshakes_failed), ',', - '"rcode_noerror":', tonumber(stats.rcode_noerror), ',', - '"rcode_formerr":', tonumber(stats.rcode_formerr), ',', - '"rcode_servfail":', tonumber(stats.rcode_servfail), ',', - '"rcode_nxdomain":', tonumber(stats.rcode_nxdomain), ',', - '"rcode_notimp":', tonumber(stats.rcode_notimp), ',', - '"rcode_refused":', tonumber(stats.rcode_refused), ',', - '"rcode_yxdomain":', tonumber(stats.rcode_yxdomain), ',', - '"rcode_yxrrset":', tonumber(stats.rcode_yxrrset), ',', - '"rcode_nxrrset":', tonumber(stats.rcode_nxrrset), ',', - '"rcode_notauth":', tonumber(stats.rcode_notauth), ',', - '"rcode_notzone":', tonumber(stats.rcode_notzone), ',', - '"rcode_badvers":', tonumber(stats.rcode_badvers), ',', - '"rcode_badkey":', tonumber(stats.rcode_badkey), ',', - '"rcode_badtime":', tonumber(stats.rcode_badtime), ',', - '"rcode_badmode":', tonumber(stats.rcode_badmode), ',', - '"rcode_badname":', tonumber(stats.rcode_badname), ',', - '"rcode_badalg":', tonumber(stats.rcode_badalg), ',', - '"rcode_badtrunc":', tonumber(stats.rcode_badtrunc), ',', - '"rcode_badcookie":', tonumber(stats.rcode_badcookie), ',', - '"rcode_other":', tonumber(stats.rcode_other), ',', - '"latency":[') - file:write(tonumber(stats.latency[0])) - for i=1,tonumber(self.obj.timeout_ms) do - file:write(',', tonumber(stats.latency[i])) - end - file:write("]}") - end - - file:write( - "{ ", - '"version":', _DNSSIM_JSON_VERSION, ',', - '"merged":false,', - '"stats_interval_ms":', tonumber(self.obj.stats_interval_ms), ',', - '"timeout_ms":', tonumber(self.obj.timeout_ms), ',', - '"idle_timeout_ms":', tonumber(self.obj.idle_timeout_ms), ',', - '"handshake_timeout_ms":', tonumber(self.obj.handshake_timeout_ms), ',', - '"discarded":', self:discarded(), ',', - '"stats_sum":') - write_stats(file, self.obj.stats_sum) - file:write( - ',', - '"stats_periodic":[') - - local stats = self.obj.stats_first - write_stats(file, stats) - - while (stats.next ~= nil) do - stats = stats.next - file:write(',') - write_stats(file, stats) - end - - file:write(']}') - file:close() - self.obj._log:notice("results exported to "..filename) -end - --- Return the C function and context for receiving objects. --- Only --- .I dnsjit.filter.core.object.ip --- or --- .I dnsjit.filter.core.object.ip6 --- objects are supported. --- The component expects a 32bit integer (in host order) ranging from 0 --- to max_clients written to first 4 bytes of destination IP. --- See --- .IR dnsjit.filter.ipsplit . -function DnsSim:receive() - local receive = C.output_dnssim_receiver() - return receive, self.obj -end - --- Deprecated: use udp() instead. --- --- Set the transport to UDP (without any TCP fallback). -function DnsSim:udp_only() - C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY) -end - --- dnsjit.filter.copy (3), --- dnsjit.filter.ipsplit (3), --- dnsjit.filter.core.object.ip (3), --- dnsjit.filter.core.object.ip6 (3), --- https://gitlab.nic.cz/knot/shotgun -return DnsSim 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; -} |