summaryrefslogtreecommitdiffstats
path: root/src/output/dnssim/https2.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/output/dnssim/https2.c')
-rw-r--r--src/output/dnssim/https2.c592
1 files changed, 592 insertions, 0 deletions
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 <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