From 4754ed45b607e82450a5e31fea1da3ba61433b04 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Mar 2021 08:54:12 +0100 Subject: Adding upstream version 1.1.0+debian. Signed-off-by: Daniel Baumann --- src/output/dnssim/https2.c | 592 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 592 insertions(+) create mode 100644 src/output/dnssim/https2.c (limited to 'src/output/dnssim/https2.c') diff --git a/src/output/dnssim/https2.c b/src/output/dnssim/https2.c new file mode 100644 index 0000000..72fcdaf --- /dev/null +++ b/src/output/dnssim/https2.c @@ -0,0 +1,592 @@ +/* + * 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 . + */ + +#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 +#include + +#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 -- cgit v1.2.3