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