1 /*
2 *
3 * Copyright (C) 2020 CZ.NIC, z.s.p.o
4 *
5 * Initial Author: Jan Hák <jan.hak@nic.cz>
6 *
7 * SPDX-License-Identifier: GPL-3.0-or-later
8 */
9
10 #include <errno.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14
15 #include "daemon/io.h"
16 #include "daemon/http.h"
17 #include "daemon/worker.h"
18 #include "daemon/session.h"
19 #include "lib/layer/iterate.h" /* kr_response_classify */
20 #include "lib/cache/util.h"
21
22 #include "contrib/cleanup.h"
23 #include "contrib/base64url.h"
24
25 #define MAKE_NV(K, KS, V, VS) \
26 { (uint8_t *)(K), (uint8_t *)(V), (KS), (VS), NGHTTP2_NV_FLAG_NONE }
27
28 #define MAKE_STATIC_NV(K, V) \
29 MAKE_NV((K), sizeof(K) - 1, (V), sizeof(V) - 1)
30
31 /* Use same maximum as for tcp_pipeline_max. */
32 #define HTTP_MAX_CONCURRENT_STREAMS UINT16_MAX
33
34 #define HTTP_MAX_HEADER_IN_SIZE 1024
35
36 #define HTTP_FRAME_HDLEN 9
37 #define HTTP_FRAME_PADLEN 1
38
39 #define MAX_DECIMAL_LENGTH(VT) ((CHAR_BIT * sizeof(VT) / 3) + 3)
40
41 struct http_data {
42 uint8_t *buf;
43 size_t len;
44 size_t pos;
45 uint32_t ttl;
46 uv_write_cb on_write;
47 uv_write_t *req;
48 };
49
50 /*
51 * Write HTTP/2 protocol data to underlying transport layer.
52 */
send_callback(nghttp2_session * h2,const uint8_t * data,size_t length,int flags,void * user_data)53 static ssize_t send_callback(nghttp2_session *h2, const uint8_t *data, size_t length,
54 int flags, void *user_data)
55 {
56 struct http_ctx *ctx = (struct http_ctx *)user_data;
57 return ctx->send_cb(data, length, ctx->session);
58 }
59
60 /*
61 * Send padding length (if greater than zero).
62 */
send_padlen(struct http_ctx * ctx,size_t padlen)63 static int send_padlen(struct http_ctx *ctx, size_t padlen)
64 {
65 int ret;
66 uint8_t buf;
67
68 if (padlen == 0)
69 return 0;
70
71 buf = (uint8_t)padlen;
72 ret = ctx->send_cb(&buf, HTTP_FRAME_PADLEN, ctx->session);
73 if (ret < 0)
74 return NGHTTP2_ERR_CALLBACK_FAILURE;
75
76 return 0;
77 }
78
79 /*
80 * Send HTTP/2 zero-byte padding.
81 *
82 * This sends only padlen-1 bytes of padding (if any), since padlen itself
83 * (already sent) is also considered padding. Refer to RFC7540, section 6.1
84 */
send_padding(struct http_ctx * ctx,uint8_t padlen)85 static int send_padding(struct http_ctx *ctx, uint8_t padlen)
86 {
87 static const uint8_t buf[UINT8_MAX];
88 int ret;
89
90 if (padlen <= 1)
91 return 0;
92
93 ret = ctx->send_cb(buf, padlen - 1, ctx->session);
94 if (ret < 0)
95 return NGHTTP2_ERR_CALLBACK_FAILURE;
96
97 return 0;
98 }
99
100 /*
101 * Write entire DATA frame to underlying transport layer.
102 *
103 * This function reads directly from data provider to avoid copying packet wire buffer.
104 */
send_data_callback(nghttp2_session * h2,nghttp2_frame * frame,const uint8_t * framehd,size_t length,nghttp2_data_source * source,void * user_data)105 static int send_data_callback(nghttp2_session *h2, nghttp2_frame *frame, const uint8_t *framehd,
106 size_t length, nghttp2_data_source *source, void *user_data)
107 {
108 struct http_data *data;
109 int ret;
110 struct http_ctx *ctx;
111
112 ctx = (struct http_ctx *)user_data;
113 data = (struct http_data*)source->ptr;
114
115 ret = ctx->send_cb(framehd, HTTP_FRAME_HDLEN, ctx->session);
116 if (ret < 0)
117 return NGHTTP2_ERR_CALLBACK_FAILURE;
118
119 ret = send_padlen(ctx, frame->data.padlen);
120 if (ret < 0)
121 return NGHTTP2_ERR_CALLBACK_FAILURE;
122
123 ret = ctx->send_cb(data->buf + data->pos, length, ctx->session);
124 if (ret < 0)
125 return NGHTTP2_ERR_CALLBACK_FAILURE;
126 data->pos += length;
127 if (kr_fails_assert(data->pos <= data->len))
128 return NGHTTP2_ERR_CALLBACK_FAILURE;
129
130 ret = send_padding(ctx, (uint8_t)frame->data.padlen);
131 if (ret < 0)
132 return NGHTTP2_ERR_CALLBACK_FAILURE;
133
134 return 0;
135 }
136
137 /*
138 * Check endpoint and uri path
139 */
check_uri(const char * uri_path)140 static int check_uri(const char* uri_path)
141 {
142 static const char key[] = "dns=";
143 static const char *delim = "&";
144 static const char *endpoints[] = {"dns-query", "doh"};
145 char *beg;
146 char *end_prev;
147 ssize_t endpoint_len;
148 ssize_t ret;
149
150 if (!uri_path)
151 return kr_error(EINVAL);
152
153 auto_free char *path = malloc(sizeof(*path) * (strlen(uri_path) + 1));
154 if (!path)
155 return kr_error(ENOMEM);
156
157 memcpy(path, uri_path, strlen(uri_path));
158 path[strlen(uri_path)] = '\0';
159
160 char *query_mark = strstr(path, "?");
161
162 /* calculating of endpoint_len - for POST or GET method */
163 endpoint_len = (query_mark) ? query_mark - path - 1 : strlen(path) - 1;
164
165 /* check endpoint */
166 ret = -1;
167 for(int i = 0; i < sizeof(endpoints)/sizeof(*endpoints); i++)
168 {
169 if (strlen(endpoints[i]) != endpoint_len)
170 continue;
171 ret = strncmp(path + 1, endpoints[i], strlen(endpoints[i]));
172 if (!ret)
173 break;
174 }
175
176 if (ret) /* no endpoint found */
177 return -1;
178
179 /* FIXME This also passes for GET when no variables are provided.
180 * Fixing it doesn't seem straightforward, since :method may not be
181 * known by the time check_uri() is called... */
182 if (endpoint_len == strlen(path) - 1) /* done for POST method */
183 return 0;
184
185 /* go over key:value pair */
186 beg = strtok(query_mark + 1, delim);
187 if (beg) {
188 while (beg != NULL) {
189 if (!strncmp(beg, key, 4)) { /* dns variable in path found */
190 break;
191 }
192 end_prev = beg + strlen(beg);
193 beg = strtok(NULL, delim);
194 if (beg-1 != end_prev) { /* detect && */
195 return -1;
196 }
197 }
198
199 if (!beg) { /* no dns variable in path */
200 return -1;
201 }
202 }
203
204 return 0;
205 }
206
207 /*
208 * Process a query from URI path if there's base64url encoded dns variable.
209 */
process_uri_path(struct http_ctx * ctx,const char * path,int32_t stream_id)210 static int process_uri_path(struct http_ctx *ctx, const char* path, int32_t stream_id)
211 {
212 if (!ctx || !path)
213 return kr_error(EINVAL);
214
215 static const char key[] = "dns=";
216 char *beg = strstr(path, key);
217 char *end;
218 size_t remaining;
219 ssize_t ret;
220 uint8_t *dest;
221
222 if (!beg) /* No dns variable in path. */
223 return -1;
224
225 beg += sizeof(key) - 1;
226 end = strchr(beg, '&');
227 if (end == NULL)
228 end = beg + strlen(beg);
229
230 ctx->buf_pos = sizeof(uint16_t); /* Reserve 2B for dnsmsg len. */
231 remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos;
232 dest = ctx->buf + ctx->buf_pos;
233
234 ret = kr_base64url_decode((uint8_t*)beg, end - beg, dest, remaining);
235 if (ret < 0) {
236 ctx->buf_pos = 0;
237 kr_log_debug(DOH, "[%p] base64url decode failed %s\n", (void *)ctx->h2, strerror(ret));
238 return ret;
239 }
240
241 ctx->buf_pos += ret;
242
243 struct http_stream stream = {
244 .id = stream_id,
245 .headers = ctx->headers
246 };
247 queue_push(ctx->streams, stream);
248 return 0;
249 }
250
refuse_stream(nghttp2_session * h2,int32_t stream_id)251 static void refuse_stream(nghttp2_session *h2, int32_t stream_id)
252 {
253 nghttp2_submit_rst_stream(
254 h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_REFUSED_STREAM);
255 }
256
http_free_headers(kr_http_header_array_t * headers)257 void http_free_headers(kr_http_header_array_t *headers)
258 {
259 if (headers == NULL)
260 return;
261
262 for (int i = 0; i < headers->len; i++) {
263 free(headers->at[i].name);
264 free(headers->at[i].value);
265 }
266 array_clear(*headers);
267 free(headers);
268 }
269 /* Return the http ctx into a pristine state in which no stream is being processed. */
http_cleanup_stream(struct http_ctx * ctx)270 static void http_cleanup_stream(struct http_ctx *ctx)
271 {
272 ctx->incomplete_stream = -1;
273 ctx->current_method = HTTP_METHOD_NONE;
274 free(ctx->uri_path);
275 ctx->uri_path = NULL;
276 http_free_headers(ctx->headers);
277 ctx->headers = NULL;
278 }
279
280 /*
281 * Save stream id from first header's frame.
282 *
283 * We don't support interweaving from different streams. To successfully parse
284 * multiple subsequent streams, each one must be fully received before processing
285 * a new stream.
286 */
begin_headers_callback(nghttp2_session * h2,const nghttp2_frame * frame,void * user_data)287 static int begin_headers_callback(nghttp2_session *h2, const nghttp2_frame *frame,
288 void *user_data)
289 {
290 struct http_ctx *ctx = (struct http_ctx *)user_data;
291 int32_t stream_id = frame->hd.stream_id;
292
293 if (frame->hd.type != NGHTTP2_HEADERS ||
294 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
295 return 0;
296 }
297
298 if (ctx->incomplete_stream != -1) {
299 kr_log_debug(DOH, "[%p] stream %d incomplete, refusing (begin_headers_callback)\n",
300 (void *)h2, ctx->incomplete_stream);
301 refuse_stream(h2, stream_id);
302 } else {
303 http_cleanup_stream(ctx); // Free any leftover data and ensure pristine state
304 ctx->incomplete_stream = stream_id;
305 ctx->headers = malloc(sizeof(kr_http_header_array_t));
306 array_init(*ctx->headers);
307 }
308 return 0;
309 }
310
311 /*
312 * Process a received header name-value pair.
313 *
314 * In DoH, GET requests contain the base64url-encoded query in dns variable present in path.
315 * This variable is parsed from :path pseudoheader.
316 */
header_callback(nghttp2_session * h2,const nghttp2_frame * frame,const uint8_t * name,size_t namelen,const uint8_t * value,size_t valuelen,uint8_t flags,void * user_data)317 static int header_callback(nghttp2_session *h2, const nghttp2_frame *frame,
318 const uint8_t *name, size_t namelen, const uint8_t *value,
319 size_t valuelen, uint8_t flags, void *user_data)
320 {
321 struct http_ctx *ctx = (struct http_ctx *)user_data;
322 int32_t stream_id = frame->hd.stream_id;
323
324 if (frame->hd.type != NGHTTP2_HEADERS)
325 return 0;
326
327 if (ctx->incomplete_stream != stream_id) {
328 kr_log_debug(DOH, "[%p] stream %d incomplete, refusing (header_callback)\n",
329 (void *)h2, ctx->incomplete_stream);
330 refuse_stream(h2, stream_id);
331 return 0;
332 }
333
334 /* Store chosen headers to pass them to kr_request. */
335 for (int i = 0; i < the_worker->doh_qry_headers.len; i++) {
336 if (!strcasecmp(the_worker->doh_qry_headers.at[i], (const char *)name)) {
337 kr_http_header_array_entry_t header;
338
339 /* Limit maximum value size to reduce attack surface. */
340 if (valuelen > HTTP_MAX_HEADER_IN_SIZE) {
341 kr_log_debug(DOH,
342 "[%p] stream %d: header too large (%zu B), refused\n",
343 (void *)h2, stream_id, valuelen);
344 refuse_stream(h2, stream_id);
345 return 0;
346 }
347
348 /* Copy the user-provided header name to keep the original case. */
349 header.name = malloc(sizeof(*header.name) * (namelen + 1));
350 memcpy(header.name, the_worker->doh_qry_headers.at[i], namelen);
351 header.name[namelen] = '\0';
352
353 header.value = malloc(sizeof(*header.value) * (valuelen + 1));
354 memcpy(header.value, value, valuelen);
355 header.value[valuelen] = '\0';
356
357 array_push(*ctx->headers, header);
358 break;
359 }
360 }
361
362 if (!strcasecmp(":path", (const char *)name)) {
363 if (check_uri((const char *)value) < 0) {
364 refuse_stream(h2, stream_id);
365 return 0;
366 }
367
368 kr_assert(ctx->uri_path == NULL);
369 ctx->uri_path = malloc(sizeof(*ctx->uri_path) * (valuelen + 1));
370 if (!ctx->uri_path)
371 return kr_error(ENOMEM);
372 memcpy(ctx->uri_path, value, valuelen);
373 ctx->uri_path[valuelen] = '\0';
374 }
375
376 if (!strcasecmp(":method", (const char *)name)) {
377 if (!strcasecmp("get", (const char *)value)) {
378 ctx->current_method = HTTP_METHOD_GET;
379 } else if (!strcasecmp("post", (const char *)value)) {
380 ctx->current_method = HTTP_METHOD_POST;
381 } else {
382 ctx->current_method = HTTP_METHOD_NONE;
383 }
384 }
385
386 return 0;
387 }
388
389 /*
390 * Process DATA chunk sent by the client (by POST method).
391 *
392 * We use a single DNS message buffer for the entire connection. Therefore, we
393 * don't support interweaving DATA chunks from different streams. To successfully
394 * parse multiple subsequent streams, each one must be fully received before
395 * processing a new stream. See https://gitlab.nic.cz/knot/knot-resolver/-/issues/619
396 */
data_chunk_recv_callback(nghttp2_session * h2,uint8_t flags,int32_t stream_id,const uint8_t * data,size_t len,void * user_data)397 static int data_chunk_recv_callback(nghttp2_session *h2, uint8_t flags, int32_t stream_id,
398 const uint8_t *data, size_t len, void *user_data)
399 {
400 struct http_ctx *ctx = (struct http_ctx *)user_data;
401 ssize_t remaining;
402 ssize_t required;
403 bool is_first = queue_len(ctx->streams) == 0 || queue_tail(ctx->streams).id != ctx->incomplete_stream;
404
405 if (ctx->incomplete_stream != stream_id) {
406 kr_log_debug(DOH, "[%p] stream %d incomplete, refusing (data_chunk_recv_callback)\n",
407 (void *)h2, ctx->incomplete_stream);
408 refuse_stream(h2, stream_id);
409 ctx->incomplete_stream = -1;
410 return 0;
411 }
412
413 remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos;
414 required = len;
415 /* First data chunk of the new stream */
416 if (is_first)
417 required += sizeof(uint16_t);
418
419 if (required > remaining) {
420 kr_log_error(DOH, "[%p] insufficient space in buffer\n", (void *)h2);
421 ctx->incomplete_stream = -1;
422 return NGHTTP2_ERR_CALLBACK_FAILURE;
423 }
424
425 if (is_first) {
426 /* FIXME: reserving the 2B length should be done elsewhere,
427 * ideally for both POST and GET at the same time. The right
428 * place would probably be after receiving HEADERS frame in
429 * on_frame_recv()
430 *
431 * queue_push() should be moved: see FIXME in
432 * submit_to_wirebuffer() */
433 ctx->buf_pos = sizeof(uint16_t); /* Reserve 2B for dnsmsg len. */
434 struct http_stream stream = {
435 .id = stream_id,
436 .headers = ctx->headers
437 };
438 queue_push(ctx->streams, stream);
439 }
440
441 memmove(ctx->buf + ctx->buf_pos, data, len);
442 ctx->buf_pos += len;
443 return 0;
444 }
445
submit_to_wirebuffer(struct http_ctx * ctx)446 static int submit_to_wirebuffer(struct http_ctx *ctx)
447 {
448 int ret = -1;
449 ssize_t len;
450
451 /* Transfer ownership to stream (waiting in wirebuffer) */
452 /* FIXME: technically, transferring memory ownership should happen
453 * along with queue_push(ctx->streams) to avoid confusion of who owns
454 * what and when. Pushing to queue should be done AFTER we successfully
455 * finish this function. On error, we'd clean up and not push anything.
456 * However, queue's content is now also used to detect first DATA frame
457 * in stream, so it needs to be refactored first.
458 *
459 * For now, we assume memory is transferred even on error and the
460 * headers themselves get cleaned up during http_free() which is
461 * triggered after the error when session is closed. */
462 ctx->headers = NULL;
463
464 len = ctx->buf_pos - sizeof(uint16_t);
465 if (len <= 0 || len > KNOT_WIRE_MAX_PKTSIZE) {
466 kr_log_debug(DOH, "[%p] invalid dnsmsg size: %zd B\n", (void *)ctx->h2, len);
467 ret = -1;
468 goto cleanup;
469 }
470
471 /* Submit data to wirebuffer. */
472 knot_wire_write_u16(ctx->buf, len);
473 ctx->submitted += ctx->buf_pos;
474 ctx->buf += ctx->buf_pos;
475 ctx->buf_pos = 0;
476 ret = 0;
477 cleanup:
478 http_cleanup_stream(ctx);
479 return ret;
480 }
481
482 /*
483 * Finalize existing buffer upon receiving the last frame in the stream.
484 *
485 * For GET, this would be HEADERS frame.
486 * For POST, it is a DATA frame.
487 *
488 * Unrelated frames (such as SETTINGS) are ignored (no data was buffered).
489 */
on_frame_recv_callback(nghttp2_session * h2,const nghttp2_frame * frame,void * user_data)490 static int on_frame_recv_callback(nghttp2_session *h2, const nghttp2_frame *frame, void *user_data)
491 {
492 struct http_ctx *ctx = (struct http_ctx *)user_data;
493 int32_t stream_id = frame->hd.stream_id;
494 if(kr_fails_assert(stream_id != -1))
495 return NGHTTP2_ERR_CALLBACK_FAILURE;
496
497 if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) && ctx->incomplete_stream == stream_id) {
498 if (ctx->current_method == HTTP_METHOD_GET) {
499 if (process_uri_path(ctx, ctx->uri_path, stream_id) < 0) {
500 refuse_stream(h2, stream_id);
501 return 0; /* End processing - don't submit to wirebuffer. */
502 }
503 }
504
505 if (submit_to_wirebuffer(ctx) < 0)
506 return NGHTTP2_ERR_CALLBACK_FAILURE;
507 }
508
509 return 0;
510 }
511
512 /*
513 * Call on_write() callback for written (or failed) packet data.
514 */
on_pkt_write(struct http_data * data,int status)515 static void on_pkt_write(struct http_data *data, int status)
516 {
517 if (!data || !data->req || !data->on_write)
518 return;
519
520 data->on_write(data->req, status);
521 free(data);
522 }
523
stream_write_data_free_err(trie_val_t * val,void * null)524 static int stream_write_data_free_err(trie_val_t *val, void *null)
525 {
526 on_pkt_write(*val, kr_error(EIO));
527 return 0;
528 }
529
530 /*
531 * Cleanup for closed streams.
532 */
on_stream_close_callback(nghttp2_session * h2,int32_t stream_id,uint32_t error_code,void * user_data)533 static int on_stream_close_callback(nghttp2_session *h2, int32_t stream_id,
534 uint32_t error_code, void *user_data)
535 {
536 struct http_data *data;
537 struct http_ctx *ctx = (struct http_ctx *)user_data;
538 int ret;
539
540 /* Ensure connection state is cleaned up in case the stream gets
541 * unexpectedly closed, e.g. by PROTOCOL_ERROR issued from nghttp2. */
542 if (ctx->incomplete_stream == stream_id)
543 http_cleanup_stream(ctx);
544
545 ret = trie_del(ctx->stream_write_data, (char *)&stream_id, sizeof(stream_id), (trie_val_t*)&data);
546 if (ret == KNOT_EOK && data)
547 on_pkt_write(data, error_code == 0 ? 0 : kr_error(EIO));
548
549 return 0;
550 }
551
552 /*
553 * Setup and initialize connection with new HTTP/2 context.
554 */
http_new(struct session * session,http_send_callback send_cb)555 struct http_ctx* http_new(struct session *session, http_send_callback send_cb)
556 {
557 if (!session || !send_cb)
558 return NULL;
559
560 nghttp2_session_callbacks *callbacks;
561 struct http_ctx *ctx = NULL;
562 static const nghttp2_settings_entry iv[] = {
563 { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, HTTP_MAX_CONCURRENT_STREAMS }
564 };
565
566 if (nghttp2_session_callbacks_new(&callbacks) < 0)
567 return ctx;
568 nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
569 nghttp2_session_callbacks_set_send_data_callback(callbacks, send_data_callback);
570 nghttp2_session_callbacks_set_on_header_callback(callbacks, header_callback);
571 nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, begin_headers_callback);
572 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
573 callbacks, data_chunk_recv_callback);
574 nghttp2_session_callbacks_set_on_frame_recv_callback(
575 callbacks, on_frame_recv_callback);
576 nghttp2_session_callbacks_set_on_stream_close_callback(
577 callbacks, on_stream_close_callback);
578
579 ctx = calloc(1UL, sizeof(struct http_ctx));
580 if (!ctx)
581 goto finish;
582
583 ctx->send_cb = send_cb;
584 ctx->session = session;
585 queue_init(ctx->streams);
586 ctx->stream_write_data = trie_create(NULL);
587 ctx->incomplete_stream = -1;
588 ctx->submitted = 0;
589 ctx->current_method = HTTP_METHOD_NONE;
590 ctx->uri_path = NULL;
591
592 nghttp2_session_server_new(&ctx->h2, callbacks, ctx);
593 nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE,
594 iv, sizeof(iv)/sizeof(*iv));
595
596 struct sockaddr *peer = session_get_peer(session);
597 kr_log_debug(DOH, "[%p] h2 session created for %s\n", (void *)ctx->h2, kr_straddr(peer));
598 finish:
599 nghttp2_session_callbacks_del(callbacks);
600 return ctx;
601 }
602
603 /*
604 * Process inbound HTTP/2 data and return number of bytes read into session wire buffer.
605 *
606 * This function may trigger outgoing HTTP/2 data, such as stream resets, window updates etc.
607 */
http_process_input_data(struct session * session,const uint8_t * buf,ssize_t nread)608 ssize_t http_process_input_data(struct session *session, const uint8_t *buf,
609 ssize_t nread)
610 {
611 struct http_ctx *ctx = session_http_get_server_ctx(session);
612 ssize_t ret = 0;
613
614 if (!ctx->h2)
615 return kr_error(ENOSYS);
616 if (kr_fails_assert(ctx->session == session))
617 return kr_error(EINVAL);
618
619 /* FIXME It is possible for the TLS/HTTP processing to be cut off at
620 * any point, waiting for more data. If we're using POST which is split
621 * into multiple DATA frames and such a stream is in the middle of
622 * processing, resetting buf_pos will corrupt its contents (and the
623 * query will be ignored). This may also be problematic in other
624 * cases. */
625 ctx->submitted = 0;
626 ctx->buf = session_wirebuf_get_free_start(session);
627 ctx->buf_pos = 0;
628 ctx->buf_size = session_wirebuf_get_free_size(session);
629
630 ret = nghttp2_session_mem_recv(ctx->h2, buf, nread);
631 if (ret < 0) {
632 kr_log_debug(DOH, "[%p] nghttp2_session_mem_recv failed: %s (%zd)\n",
633 (void *)ctx->h2, nghttp2_strerror(ret), ret);
634 return kr_error(EIO);
635 }
636
637 ret = nghttp2_session_send(ctx->h2);
638 if (ret < 0) {
639 kr_log_debug(DOH, "[%p] nghttp2_session_send failed: %s (%zd)\n",
640 (void *)ctx->h2, nghttp2_strerror(ret), ret);
641 return kr_error(EIO);
642 }
643
644 return ctx->submitted;
645 }
646
647 /*
648 * Provide data from buffer to HTTP/2 library.
649 *
650 * To avoid copying the packet wire buffer, we use NGHTTP2_DATA_FLAG_NO_COPY
651 * and take care of sending entire DATA frames ourselves with nghttp2_send_data_callback.
652 *
653 * See https://www.nghttp2.org/documentation/types.html#c.nghttp2_data_source_read_callback
654 */
read_callback(nghttp2_session * h2,int32_t stream_id,uint8_t * buf,size_t length,uint32_t * data_flags,nghttp2_data_source * source,void * user_data)655 static ssize_t read_callback(nghttp2_session *h2, int32_t stream_id, uint8_t *buf,
656 size_t length, uint32_t *data_flags,
657 nghttp2_data_source *source, void *user_data)
658 {
659 struct http_data *data;
660 size_t avail;
661 size_t send;
662
663 data = (struct http_data*)source->ptr;
664 avail = data->len - data->pos;
665 send = MIN(avail, length);
666
667 if (avail == send)
668 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
669
670 *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
671 return send;
672 }
673
674 /*
675 * Send dns response provided by the HTTP/2 data provider.
676 *
677 * Data isn't guaranteed to be sent immediately due to underlying HTTP/2 flow control.
678 */
http_send_response(struct http_ctx * ctx,int32_t stream_id,nghttp2_data_provider * prov)679 static int http_send_response(struct http_ctx *ctx, int32_t stream_id,
680 nghttp2_data_provider *prov)
681 {
682 nghttp2_session *h2 = ctx->h2;
683 struct http_data *data = (struct http_data*)prov->source.ptr;
684 int ret;
685 const char *directive_max_age = "max-age=";
686 char size[MAX_DECIMAL_LENGTH(data->len)] = { 0 };
687 int max_age_len = MAX_DECIMAL_LENGTH(data->ttl) + strlen(directive_max_age);
688 char max_age[max_age_len];
689 int size_len;
690
691 memset(max_age, 0, max_age_len * sizeof(*max_age));
692 size_len = snprintf(size, MAX_DECIMAL_LENGTH(data->len), "%zu", data->len);
693 max_age_len = snprintf(max_age, max_age_len, "%s%u", directive_max_age, data->ttl);
694
695 nghttp2_nv hdrs[] = {
696 MAKE_STATIC_NV(":status", "200"),
697 MAKE_STATIC_NV("content-type", "application/dns-message"),
698 MAKE_NV("content-length", 14, size, size_len),
699 MAKE_NV("cache-control", 13, max_age, max_age_len),
700 };
701
702 ret = nghttp2_submit_response(h2, stream_id, hdrs, sizeof(hdrs)/sizeof(*hdrs), prov);
703 if (ret != 0) {
704 kr_log_debug(DOH, "[%p] nghttp2_submit_response failed: %s\n", (void *)h2, nghttp2_strerror(ret));
705 free(data);
706 return kr_error(EIO);
707 }
708
709 /* Keep reference to data, since we need to free it later on.
710 * Due to HTTP/2 flow control, this stream data may be sent at a later point, or not at all.
711 */
712 trie_val_t *stream_data_p = trie_get_ins(ctx->stream_write_data, (char *)&stream_id, sizeof(stream_id));
713 if (kr_fails_assert(stream_data_p)) {
714 kr_log_debug(DOH, "[%p] failed to insert to stream_write_data\n", (void *)h2);
715 free(data);
716 return kr_error(EIO);
717 }
718 *stream_data_p = data;
719 ret = nghttp2_session_send(h2);
720 if(ret < 0) {
721 kr_log_debug(DOH, "[%p] nghttp2_session_send failed: %s\n", (void *)h2, nghttp2_strerror(ret));
722
723 /* At this point, there was an error in some nghttp2 callback. The on_pkt_write()
724 * callback which also calls free(data) may or may not have been called. Therefore,
725 * we must guarantee it will have been called by explicitly closing the stream.
726 * Afterwards, we have no option but to pretend this function was a success. If we
727 * returned an error, qr_task_send() logic would lead to a double-free because
728 * on_write() was already called. */
729 nghttp2_submit_rst_stream(h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_INTERNAL_ERROR);
730 return 0;
731 }
732
733 return 0;
734 }
735
736 /*
737 * Send HTTP/2 stream data created from packet's wire buffer.
738 *
739 * If this function returns an error, the on_write() callback isn't (and
740 * mustn't be!) called, since such errors are handled in an upper layer - in
741 * qr_task_step() in daemon/worker.
742 */
http_write_pkt(struct http_ctx * ctx,knot_pkt_t * pkt,int32_t stream_id,uv_write_t * req,uv_write_cb on_write)743 static int http_write_pkt(struct http_ctx *ctx, knot_pkt_t *pkt, int32_t stream_id,
744 uv_write_t *req, uv_write_cb on_write)
745 {
746 struct http_data *data;
747 nghttp2_data_provider prov;
748 const bool is_negative = kr_response_classify(pkt) & (PKT_NODATA|PKT_NXDOMAIN);
749
750 data = malloc(sizeof(struct http_data));
751 if (!data)
752 return kr_error(ENOMEM);
753
754 data->buf = pkt->wire;
755 data->len = pkt->size;
756 data->pos = 0;
757 data->on_write = on_write;
758 data->req = req;
759 data->ttl = packet_ttl(pkt, is_negative);
760
761 prov.source.ptr = data;
762 prov.read_callback = read_callback;
763
764 return http_send_response(ctx, stream_id, &prov);
765 }
766
767 /*
768 * Write request to HTTP/2 stream.
769 *
770 * Packet wire buffer must stay valid until the on_write callback.
771 */
http_write(uv_write_t * req,uv_handle_t * handle,knot_pkt_t * pkt,int32_t stream_id,uv_write_cb on_write)772 int http_write(uv_write_t *req, uv_handle_t *handle, knot_pkt_t *pkt, int32_t stream_id,
773 uv_write_cb on_write)
774 {
775 struct session *session;
776 struct http_ctx *ctx;
777 int ret;
778
779 if (!req || !pkt || !handle || !handle->data || stream_id < 0)
780 return kr_error(EINVAL);
781 req->handle = (uv_stream_t *)handle;
782
783 session = handle->data;
784 if (session_flags(session)->outgoing)
785 return kr_error(ENOSYS);
786
787 ctx = session_http_get_server_ctx(session);
788 if (!ctx || !ctx->h2)
789 return kr_error(EINVAL);
790
791 ret = http_write_pkt(ctx, pkt, stream_id, req, on_write);
792 if (ret < 0)
793 return ret;
794
795 return kr_ok();
796 }
797
798 /*
799 * Release HTTP/2 context.
800 */
http_free(struct http_ctx * ctx)801 void http_free(struct http_ctx *ctx)
802 {
803 if (!ctx)
804 return;
805
806 kr_log_debug(DOH, "[%p] h2 session freed\n", (void *)ctx->h2);
807
808 /* Clean up any headers whose ownership may not have been transferred.
809 * This may happen when connection is abruptly ended (e.g. due to errors while
810 * processing HTTP stream. */
811 while (queue_len(ctx->streams) > 0) {
812 struct http_stream stream = queue_head(ctx->streams);
813 http_free_headers(stream.headers);
814 if (stream.headers == ctx->headers)
815 ctx->headers = NULL; // to prevent double-free
816 queue_pop(ctx->streams);
817 }
818
819 trie_apply(ctx->stream_write_data, stream_write_data_free_err, NULL);
820 trie_free(ctx->stream_write_data);
821
822 http_cleanup_stream(ctx);
823 queue_deinit(ctx->streams);
824 nghttp2_session_del(ctx->h2);
825 free(ctx);
826 }
827