1 /**
2 * Copyright 2010 Christian Liesch
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 /**
18 * @file
19 *
20 * @Author christian liesch <liesch@gmx.ch>
21 * @Author armin abfalterer <armin.abfalterer@united-security-providers.ch>
22 *
23 * Implementation of the HTTP Test Tool h2 module
24 */
25
26 /************************************************************************
27 * Includes
28 ***********************************************************************/
29
30 #include <apr.h>
31 #include <apr_poll.h>
32 #include <apr_rmm.h>
33 #include <apr_strings.h>
34 #include <apr_ring.h>
35 #include <openssl/ssl.h>
36
37 #include "nghttp2/nghttp2.h"
38 #include "regex.h"
39 #include "module.h"
40 #include "ssl_module.h"
41 #include "body.h"
42
43 /************************************************************************
44 * Globals
45 ***********************************************************************/
46 extern command_t local_commands[];
47 extern int success;
48
49 /************************************************************************
50 * Definitions
51 ***********************************************************************/
52 const char * h2_module = "h2_module";
53
54 const unsigned char *h2 = (const unsigned char *)"\x2h2";
55 const unsigned char *h214 = (const unsigned char *)"\x5h2-14";
56 const unsigned char *h216 = (const unsigned char *)"\x5h2-16";
57
58 enum { IO_NONE, WANT_READ, WANT_WRITE };
59
60 /* as defined in nghttp2_session.h */
61 #define NGHTTP2_INBOUND_BUFFER_LENGTH 16384
62
63 #define MAKE_NV(NAME, NAMELEN, VALUE, VALUELEN) \
64 { \
65 (uint8_t *)NAME, (uint8_t *)VALUE, NAMELEN, VALUELEN, \
66 NGHTTP2_NV_FLAG_NONE \
67 }
68
69 #define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
70 #define DEFER_MARKER "CTL_DEFER_MARKER"
71 #define DEFER_MARKER_CHECKED "CTL_DEFER_CHECKED"
72
73 typedef struct _event_t {
74 #define EVENT_FLUSH 1
75 #define EVENT_DEFER 2
76 APR_RING_ENTRY(_event_t) link;
77
78 apr_size_t at;
79 int type;
80 apr_size_t sleep;
81 int defer;
82 } event_t;
83 typedef struct _event_ring_t event_ring_t;
84 APR_RING_HEAD(_event_ring_t, _event_t);
85
86 typedef struct h2_stream_s {
87 int id;
88 int closed;
89
90 apr_pool_t *p;
91
92 char *data;
93 apr_size_t data_len;
94 apr_size_t data_sent;
95
96 char *data_in;
97 apr_size_t data_in_len;
98 apr_size_t data_in_read;
99 apr_size_t data_in_expect;
100
101 int reset_expect;
102 int reset_received;
103
104 apr_table_t *headers_in;
105 apr_table_t *headers_out;
106
107 validation_t match;
108 validation_t expect;
109
110 event_ring_t *events;
111 } h2_stream_t;
112
113 typedef struct h2_sconf_s {
114 SSL *ssl;
115 int is_server;
116 nghttp2_session *session;
117 char *authority;
118 } h2_sconf_t;
119
120 typedef struct h2_wconf_t {
121 h2_stream_t *current_stream;
122 apr_hash_t *streams;
123 #define H2_STATE_CLOSED 0x00
124 #define H2_STATE_INIT 0x01
125 #define H2_STATE_NEGOTIATE 0x02
126 #define H2_STATE_ESTABLISHED 0x04
127 int state;
128 int settings;
129 int pings;
130 int open_streams;
131 int buffer_size;
132 int goaway;
133 const char *goaway_expect;
134 } h2_wconf_t;
135
136 static ssize_t h2_data_read_callback(nghttp2_session *session,
137 int32_t stream_id, uint8_t *buf,
138 size_t length, uint32_t *data_flags,
139 nghttp2_data_source *source,
140 void *user_data);
141
142 /************************************************************************
143 * Local
144 ***********************************************************************/
145
h2_get_socket_config(worker_t * worker)146 static h2_sconf_t *h2_get_socket_config(worker_t *worker) {
147 h2_sconf_t *config;
148
149 if (!worker || !worker->socket) {
150 return NULL;
151 }
152
153 config = module_get_config(worker->socket->config, h2_module);
154 if (config == NULL) {
155 worker_log(worker, LOG_DEBUG,
156 "create new sconf for socket %" APR_UINT64_T_HEX_FMT,
157 worker->socket);
158 config = apr_pcalloc(worker->pbody, sizeof(*config));
159 module_set_config(worker->socket->config,
160 apr_pstrdup(worker->pbody, h2_module), config);
161 }
162 return config;
163 }
164
h2_get_worker_config(worker_t * worker)165 static h2_wconf_t *h2_get_worker_config(worker_t *worker) {
166 h2_wconf_t *config;
167
168 if (!worker) {
169 return NULL;
170 }
171
172 config = module_get_config(worker->config, h2_module);
173 if (config == NULL) {
174 worker_log(worker, LOG_DEBUG, "create new wconf for worker %"APR_UINT64_T_HEX_FMT, worker);
175 config = apr_pcalloc(worker->pbody, sizeof(*config));
176 config->streams = apr_hash_make(worker->pbody);
177 config->buffer_size = NGHTTP2_INBOUND_BUFFER_LENGTH * 10;
178 module_set_config(worker->config, apr_pstrdup(worker->pbody, h2_module), config);
179 }
180 return config;
181 }
182
copy_table_entry(void * rec,const char * key,const char * val)183 static int copy_table_entry(void *rec, const char *key, const char *val) {
184 apr_table_addn(rec, key, val);
185
186 const int non_zero_meaning_continue_iteration = 1;
187 return non_zero_meaning_continue_iteration;
188 }
189
h2_worker_body(worker_t ** body,worker_t * worker,char * end)190 static apr_status_t h2_worker_body(worker_t **body, worker_t *worker,
191 char *end) {
192 char *file_and_line;
193 char *line = "", *copy;
194 apr_table_entry_t *e;
195 apr_pool_t *p;
196 int end_len, headers_end = 0, ends = 0;
197
198 HT_POOL_CREATE(&p);
199 end_len = strlen(end);
200 (*body) = apr_pcalloc(p, sizeof(worker_t));
201 memcpy(*body, worker, sizeof(worker_t));
202 (*body)->heartbeat = p;
203
204 /* fill lines */
205 (*body)->lines = apr_table_make(p, 20);
206 e = (apr_table_entry_t *) apr_table_elts(worker->lines)->elts;
207 for (worker->cmd += 1; worker->cmd < apr_table_elts(worker->lines)->nelts;
208 worker->cmd++) {
209 file_and_line = e[worker->cmd].key;
210
211 line = e[worker->cmd].val;
212 copy = apr_pstrdup(p, line);
213 copy = worker_replace_vars(worker, copy, NULL, p);
214
215 if (!headers_end && copy[1] != '_') {
216 worker_log(worker, LOG_ERR, "command can not be used within headers definition", copy);
217 return APR_EGENERAL;
218 }
219 else if (strcmp("__", copy) == 0) {
220 headers_end = 1;
221 }
222
223 if (strncmp(copy, end, end_len) == 0) {
224 worker_log(worker, LOG_INFO, end);
225 ends = 1;
226 break;
227 } else if (strncmp("_GREP", copy, 5) == 0) {
228 worker_log(worker, LOG_ERR, "not implemented for h2", line);
229 return APR_EINVAL;
230 } else if (strncmp("_SLEEP", copy, 6) == 0) {
231 copy = apr_psprintf(p, "_H2:SLEEP %s", ©[6]);
232 apr_table_add((*body)->lines, file_and_line, copy);
233 } else if (strncmp("_FLUSH", copy, 6) == 0) {
234 apr_table_add((*body)->lines, file_and_line, "_H2:FLUSH");
235 } else if (strncmp("_H2:WAIT", copy, 8) == 0) {
236 apr_table_add((*body)->lines, file_and_line, "_H2:DEFER");
237 } else {
238 apr_table_addn((*body)->lines, file_and_line, line);
239 }
240 worker_log(worker, LOG_INFO, "%s", line);
241 }
242
243 if (!ends) {
244 worker_log(worker, LOG_ERR, "Interpreter failed: no %s found", end);
245 return APR_EGENERAL;
246 }
247
248 return APR_SUCCESS;
249 }
250
251 typedef apr_status_t (*validate_func)(worker_t *, apr_table_t *, const char *,
252 apr_size_t);
253
check(worker_t * worker,h2_stream_t * stream,apr_table_t * orig,char * type,validate_func func)254 static apr_table_t *check(worker_t *worker, h2_stream_t *stream,
255 apr_table_t *orig, char *type, validate_func func) {
256 apr_table_t *copy =
257 apr_table_make(worker->pbody, apr_table_elts(orig)->nelts);
258 apr_table_entry_t *e;
259 int i;
260
261 e = (apr_table_entry_t *)apr_table_elts(orig)->elts;
262 for (i = 0; i < apr_table_elts(orig)->nelts; i++) {
263 if (strcmp(e[i].key, DEFER_MARKER_CHECKED) == 0) {
264 continue;
265 } else if (strcmp(e[i].key, DEFER_MARKER) == 0) {
266 /* mark as checked */
267 e[i].key = DEFER_MARKER_CHECKED;
268 break;
269 }
270
271 apr_table_addn(copy, e[i].key, e[i].val);
272 e[i].key = DEFER_MARKER_CHECKED;
273 }
274
275 if (strcmp(type, "HEADERS") == 0) {
276 e = (apr_table_entry_t *)apr_table_elts(stream->headers_in)->elts;
277 for (i = 0; i < apr_table_elts(stream->headers_in)->nelts; i++) {
278 char *header = apr_pstrcat(worker->pbody, e[i].key, ": ", e[i].val, NULL);
279
280 func(worker, copy, header, strlen(header));
281 }
282 } else { /* data */
283 func(worker, copy, stream->data_in, stream->data_in_len);
284 }
285
286 return copy;
287 }
288
check_headers(worker_t * worker,h2_stream_t * stream)289 static int check_headers(worker_t *worker, h2_stream_t *stream) {
290 apr_status_t status = APR_SUCCESS;
291 apr_table_t *checked;
292 char *errmsg;
293
294 checked =
295 check(worker, stream, stream->expect.headers, "HEADERS", worker_expect);
296 errmsg =
297 apr_psprintf(worker->pbody, "EXPECT headers for stream %d", stream->id);
298 status = worker_assert_expect(worker, checked, errmsg, status);
299 if (status != APR_SUCCESS) {
300 goto exit;
301 }
302
303 checked =
304 check(worker, stream, stream->match.headers, "HEADERS", worker_match);
305 errmsg =
306 apr_psprintf(worker->pbody, "MATCH headers for stream %d", stream->id);
307 status = worker_assert_match(worker, checked, errmsg, status);
308 if (status != APR_SUCCESS) {
309 goto exit;
310 }
311
312 /* do assertion after receiving of body */
313 check(worker, stream, stream->expect.dot, "HEADERS", worker_expect);
314 check(worker, stream, stream->match.dot, "HEADERS", worker_match);
315
316 exit:
317 apr_table_clear(stream->headers_in);
318
319 if (status != APR_SUCCESS) {
320 return NGHTTP2_ERR_CALLBACK_FAILURE;
321 } else {
322 return 0;
323 }
324 }
325
check_data(worker_t * worker,h2_stream_t * stream)326 static int check_data(worker_t *worker, h2_stream_t *stream) {
327 apr_status_t status = APR_SUCCESS;
328 apr_table_t *checked;
329 char *errmsg;
330
331 if (stream->data_in_expect &&
332 stream->data_in_read != stream->data_in_expect) {
333 worker_log(worker, LOG_ERR, "EXPECT bodysize for stream %d: read %d bytes",
334 stream->id, stream->data_in_read);
335 return APR_EINVAL;
336 }
337
338 checked = check(worker, stream, stream->expect.body, "DATA", worker_expect);
339 errmsg = apr_psprintf(worker->pbody, "EXPECT body for stream %d", stream->id);
340 status = worker_assert_expect(worker, checked, errmsg, status);
341 if (status != APR_SUCCESS) {
342 goto exit;
343 }
344
345 checked = check(worker, stream, stream->expect.dot, "DATA", worker_expect);
346 errmsg = apr_psprintf(worker->pbody, "EXPECT . for stream %d", stream->id);
347 status = worker_assert_expect(worker, checked, errmsg, status);
348 if (status != APR_SUCCESS) {
349 goto exit;
350 }
351
352 checked = check(worker, stream, stream->match.body, "DATA", worker_match);
353 errmsg = apr_psprintf(worker->pbody, "MATCH body for stream %d", stream->id);
354 status = worker_assert_match(worker, checked, errmsg, status);
355 if (status != APR_SUCCESS) {
356 goto exit;
357 }
358
359 checked = check(worker, stream, stream->match.dot, "DATA", worker_match);
360 errmsg = apr_psprintf(worker->pbody, "MATCH . for stream %d", stream->id);
361 status = worker_assert_match(worker, checked, errmsg, status);
362 if (status != APR_SUCCESS) {
363 goto exit;
364 }
365
366 exit:
367 stream->data_in = NULL;
368
369 if (status != APR_SUCCESS) {
370 return NGHTTP2_ERR_CALLBACK_FAILURE;
371 } else {
372 return 0;
373 }
374 }
375
376 /************************************************************************
377 * nghttp2 stuff
378 ***********************************************************************/
379 const char *h2_settings_array[] = {
380 "NONE",
381 "SETTINGS_HEADER_TABLE_SIZE",
382 "SETTINGS_ENABLE_PUSH",
383 "SETTINGS_MAX_CONCURRENT_STREAMS",
384 "SETTINGS_INITIAL_WINDOW_SIZE",
385 "SETTINGS_MAX_FRAME_SIZE",
386 "SETTINGS_MAX_HEADER_LIST_SIZE",
387 NULL
388 };
389
390 const char *h2_error_code_array[] = {
391 "NO_ERROR",
392 "PROTOCOL_ERROR",
393 "INTERNAL_ERROR",
394 "FLOW_CONTROL_ERROR",
395 "SETTINGS_TIMEOUT",
396 "STREAM_CLOSED",
397 "FRAME_SIZE_ERROR",
398 "REFUSED_STREAM",
399 "CANCEL",
400 "COMPRESSION_ERROR",
401 "CONNECT_ERROR",
402 "ENHANCE_YOUR_CALM",
403 "INADEQUATE_SECURITY",
404 "HTTP_1_1_REQUIRED"
405 };
406
h2_get_id_of(const char * array[],const char * key)407 static int32_t h2_get_id_of(const char *array[], const char *key) {
408 int i;
409 if (key != NULL) {
410 for (i = 0; i < 7; i++) {
411 if (strcmp(key, array[i]) == 0) {
412 return i;
413 }
414 }
415 }
416 return -1;
417 }
418
h2_get_name_of(const char * array[],int32_t id)419 static const char *h2_get_name_of(const char *array[], int32_t id) {
420 if (array[id]) {
421 return array[id];
422 } else {
423 return "NOT_FOUND";
424 }
425 }
426
h2_get_settings_as_text(apr_pool_t * pool,nghttp2_settings_entry * settings,int size)427 static const char *h2_get_settings_as_text(apr_pool_t *pool,
428 nghttp2_settings_entry *settings,
429 int size) {
430 int i;
431 char *result = "";
432 for (i = 0; i < size; i++) {
433 result =
434 apr_psprintf(pool, "%s%s=%d%s", result,
435 h2_get_name_of(h2_settings_array, settings[i].settings_id),
436 settings[i].value, i + 1 < size ? ", " : "");
437 }
438 return result;
439 }
440
h2_open_session(worker_t * worker)441 static apr_status_t h2_open_session(worker_t *worker) {
442 h2_sconf_t *sconf = h2_get_socket_config(worker);
443
444 if (!sconf || !sconf->session) {
445 worker_log(worker, LOG_ERR, "no open h2 session");
446 return APR_EGENERAL;
447 }
448
449 return APR_SUCCESS;
450 }
451
452 #define WANT_READ_WRITE(s) nghttp2_session_want_read(s) || nghttp2_session_want_write(s)
453
mypoll(worker_t * worker)454 static apr_status_t mypoll(worker_t *worker) {
455 h2_wconf_t *wconf = h2_get_worker_config(worker);
456 h2_sconf_t *sconf = h2_get_socket_config(worker);
457 apr_pollset_t *pollset;
458 apr_pollfd_t pollfd;
459 apr_status_t status;
460 apr_pool_t *p;
461 int poll;
462 int rv;
463
464 apr_pool_create(&p, NULL);
465
466 if ((status = apr_pollset_create(&pollset, 1, p, 0)) != APR_SUCCESS) {
467 worker_log(worker, LOG_ERR, "Can not create pollset %s(%d)",
468 my_status_str(p, status), status);
469 return status;
470 }
471
472 pollfd.p = p;
473 pollfd.desc_type = APR_POLL_SOCKET;
474 pollfd.reqevents = APR_POLLIN | APR_POLLERR | APR_POLLHUP;
475 pollfd.desc.s = worker->socket->socket;
476
477 if ((status = apr_pollset_add(pollset, &pollfd)) != APR_SUCCESS) {
478 worker_log(worker, LOG_ERR, "Can not add pollfd to pollset: %s(%d)",
479 my_status_str(p, status), status);
480 return status;
481 }
482
483 poll = WANT_READ_WRITE(sconf->session);
484
485 if (!poll) {
486 return APR_EGENERAL;
487 }
488
489 while (poll) {
490 const apr_pollfd_t *result;
491 apr_int32_t num;
492
493 worker_log(worker, LOG_DEBUG, "start poll cycle");
494
495 if ((rv = nghttp2_session_send(sconf->session)) != 0) {
496 worker_log(worker, LOG_DEBUG, "error on sending session data frame %d",
497 rv);
498 return APR_EGENERAL;
499 }
500
501 if ((status = apr_pollset_poll(pollset, worker->socktmo, &num, &result)) !=
502 APR_SUCCESS) {
503 if (APR_STATUS_IS_EINTR(status)) {
504 continue;
505 }
506 worker_log(worker, LOG_ERR, "can not poll on pollset: %s (%d)",
507 my_status_str(p, status), status);
508 return status;
509 }
510
511 if (result[0].rtnevents & APR_POLLIN) {
512 worker_log(worker, LOG_DEBUG, "ready to receive session data frames");
513 if ((rv = nghttp2_session_recv(sconf->session)) != 0) {
514 worker_log(worker, LOG_DEBUG, "error on recieving session data frame %d", rv);
515 return APR_EGENERAL;
516 }
517 }
518
519 if (result[0].rtnevents & APR_POLLERR) {
520 worker_log(worker, LOG_ERR, "Error on connection");
521 return APR_EGENERAL;
522 }
523
524 if (result[0].rtnevents & APR_POLLHUP) {
525 worker_log(worker, LOG_ERR, "Connection hangup");
526 return APR_EGENERAL;
527 }
528
529 poll = (wconf->settings || wconf->open_streams || wconf->pings);
530
531 worker_log(worker, LOG_DEBUG,
532 "next poll cycle: settings=%d open_streams=%d pings=%d",
533 wconf->settings, wconf->open_streams, wconf->pings);
534
535 worker_log(worker, LOG_DEBUG, "end poll cycle");
536 }
537
538 apr_pollset_destroy(pollset);
539 apr_pool_destroy(p);
540
541 return APR_SUCCESS;
542 }
543
h2_send_callback(nghttp2_session * session,const uint8_t * data,size_t length,int flags,void * user_data)544 static ssize_t h2_send_callback(nghttp2_session *session, const uint8_t *data,
545 size_t length, int flags, void *user_data) {
546 int rv;
547 worker_t *worker = user_data;
548 h2_sconf_t *sconf = h2_get_socket_config(worker);
549
550 worker_log(worker, LOG_DEBUG,
551 "h2_send_callback length: %d, flags: %d", length, flags);
552
553 rv = SSL_write(sconf->ssl, data, (int)length);
554 if (rv <= 0) {
555 int err = SSL_get_error(sconf->ssl, rv);
556 if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
557 rv = NGHTTP2_ERR_WOULDBLOCK;
558 } else {
559 worker_log(worker, LOG_ERR, "Could not send %d", rv);
560 rv = NGHTTP2_ERR_CALLBACK_FAILURE;
561 }
562 }
563
564 worker_log(worker, LOG_DEBUG, "send callback rv: %d", rv);
565 return rv;
566 }
567
h2_recv_callback(nghttp2_session * session,uint8_t * buf,size_t length,int flags,void * user_data)568 static ssize_t h2_recv_callback(nghttp2_session *session, uint8_t *buf,
569 size_t length, int flags, void *user_data) {
570 worker_t *worker = user_data;
571 h2_sconf_t *sconf = h2_get_socket_config(worker);
572 int rv;
573
574 worker_log(worker, LOG_DEBUG,
575 "h2_recv_callback length: %d, flags: %d", length, flags);
576
577 rv = SSL_read(sconf->ssl, buf, (int)length);
578 if (rv < 0) {
579 int err = SSL_get_error(sconf->ssl, rv);
580 if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
581 rv = NGHTTP2_ERR_WOULDBLOCK;
582 } else {
583 worker_log(worker, LOG_ERR, "Could not recv %d", rv);
584 rv = NGHTTP2_ERR_CALLBACK_FAILURE;
585 }
586 } else if (rv == 0) {
587 rv = NGHTTP2_ERR_EOF;
588 }
589
590 worker_log(worker, LOG_DEBUG, "recv callback rv: %d", rv);
591 return rv;
592 }
593
h2_on_frame_send_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)594 static int h2_on_frame_send_callback(nghttp2_session *session,
595 const nghttp2_frame *frame,
596 void *user_data) {
597 int rv = 0;
598 size_t i;
599 apr_pool_t *p;
600 worker_t *worker = user_data;
601 h2_wconf_t *wconf = h2_get_worker_config(worker);
602 apr_pool_create(&p, NULL);
603
604 worker_log(worker, LOG_DEBUG, "> frame header stream %d, type: %d, flag: %d",
605 frame->hd.stream_id, frame->hd.type, frame->hd.flags);
606
607 switch (frame->hd.type) {
608 case NGHTTP2_WINDOW_UPDATE:
609 worker_log(worker, LOG_DEBUG, "> WINDOW_UPDATE");
610 break;
611 case NGHTTP2_HEADERS:
612 worker_log(worker, LOG_DEBUG, "> HEADERS");
613 h2_stream_t *stream =
614 apr_hash_get(wconf->streams, apr_itoa(worker->pbody, frame->hd.stream_id),
615 APR_HASH_KEY_STRING);
616 nghttp2_data_provider data_prd;
617
618 /* submit data */
619 data_prd.read_callback = h2_data_read_callback;
620 if (stream->data_len > 0 ) {
621 nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, stream->id,
622 &data_prd);
623 }
624
625 if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) {
626 const nghttp2_nv *nva = frame->headers.nva;
627 for (i = 0; i < frame->headers.nvlen; ++i) {
628 worker_log(worker, LOG_INFO, ">%d %s: %s", frame->hd.stream_id,
629 nva[i].name, nva[i].value);
630 }
631 }
632 case NGHTTP2_DATA:
633 worker_log(worker, LOG_DEBUG, "> DATA %s",
634 frame->hd.flags & NGHTTP2_DATA_FLAG_EOF ? "[EOF]" : "");
635 break;
636 case NGHTTP2_RST_STREAM:
637 worker_log(worker, LOG_DEBUG, "> RST_STREAM");
638 break;
639 case NGHTTP2_GOAWAY: {
640 char *reason = NULL;
641
642 if (frame->goaway.opaque_data_len) {
643 char *opaque = apr_pmemdup(p, frame->goaway.opaque_data,
644 frame->goaway.opaque_data_len);
645 opaque[frame->goaway.opaque_data_len] = 0;
646 reason = apr_psprintf(p, " reason=%s", opaque);
647 }
648 worker_log(worker, LOG_INFO, "> GOAWAY [error=%s%s%s",
649 h2_get_name_of(h2_error_code_array, frame->goaway.error_code),
650 reason ? reason : "", "]");
651 } break;
652 case NGHTTP2_SETTINGS:
653 if (frame->hd.flags == NGHTTP2_FLAG_ACK) {
654 worker_log(worker, LOG_INFO, "> SETTINGS ACK");
655 } else {
656 const char *settings = h2_get_settings_as_text(
657 worker->pbody, frame->settings.iv, frame->settings.niv);
658 worker_log(worker, LOG_INFO, "> SETTINGS [%s]", settings);
659 }
660 break;
661 case NGHTTP2_PING:
662 if (frame->hd.flags == NGHTTP2_FLAG_ACK) {
663 char *opaque_data = apr_pcalloc(p, 9);
664 memcpy(opaque_data, frame->ping.opaque_data, 8);
665 worker_log(worker, LOG_INFO, "> PING ACK: %s", opaque_data);
666 } else {
667 worker_log(worker, LOG_INFO, "> PING");
668 }
669 break;
670 default:
671 worker_log(worker, LOG_INFO, "> UNKNOWN");
672 break;
673 }
674
675 apr_pool_destroy(p);
676
677 return rv;
678 }
679
h2_on_frame_not_send_callback(nghttp2_session * session,const nghttp2_frame * frame,int lib_error_code,void * user_data)680 static int h2_on_frame_not_send_callback(nghttp2_session *session,
681 const nghttp2_frame *frame,
682 int lib_error_code, void *user_data) {
683 worker_t *worker = user_data;
684
685 worker_log(worker, LOG_ERR, "could not sent frame for stream %d",
686 frame->hd.stream_id);
687
688 const int callback_succeeded = 0;
689 return callback_succeeded;
690 }
691
h2_on_frame_recv_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)692 static int h2_on_frame_recv_callback(nghttp2_session *session,
693 const nghttp2_frame *frame,
694 void *user_data) {
695 worker_t *worker = user_data;
696 h2_wconf_t *wconf = h2_get_worker_config(worker);
697 h2_stream_t *stream = apr_hash_get(
698 wconf->streams, apr_itoa(worker->pbody, frame->hd.stream_id), APR_HASH_KEY_STRING);
699 apr_pool_t *p;
700 size_t rv = 0;
701
702 apr_pool_create(&p, NULL);
703
704 worker_log(worker, LOG_DEBUG, "< frame header type: %d, flag: %d",
705 frame->hd.type, frame->hd.flags);
706
707 switch (frame->hd.type) {
708 case NGHTTP2_HEADERS:
709 if (frame->hd.flags == NGHTTP2_FLAG_END_HEADERS) {
710 worker_log(worker, LOG_DEBUG, "< END_HEADERS");
711 nghttp2_session_resume_data(session, frame->hd.stream_id);
712 rv = check_headers(worker, stream);
713 }
714 break;
715 case NGHTTP2_DATA:
716 if (frame->hd.flags == NGHTTP2_FLAG_END_STREAM) {
717 worker_log(worker, LOG_DEBUG, "< END_STREAM");
718 rv = check_data(worker, stream);
719
720 stream->closed = 1;
721 wconf->open_streams--;
722 }
723 else {
724 worker_log(worker, LOG_DEBUG, "< DATA");
725 }
726 break;
727 case NGHTTP2_WINDOW_UPDATE:
728 worker_log(worker, LOG_INFO, "< WINDOW_UPDATE");
729 break;
730 case NGHTTP2_RST_STREAM:
731 worker_log(
732 worker, LOG_INFO, "< RST_STREAM [error=%s]",
733 h2_get_name_of(h2_error_code_array, frame->rst_stream.error_code));
734
735 /* mark stream as closed if not already done due to
736 * END_STREAM */
737 if (stream->closed == 0) {
738 stream->closed = 1;
739 wconf->open_streams--;
740 }
741
742 if (!stream->reset_expect) {
743 rv = NGHTTP2_ERR_CALLBACK_FAILURE;
744 } else {
745 stream->reset_received = 1;
746 }
747 break;
748 case NGHTTP2_GOAWAY: {
749 char *reason = NULL;
750
751 if (frame->goaway.opaque_data_len) {
752 char *opaque = apr_pmemdup(p, frame->goaway.opaque_data,
753 frame->goaway.opaque_data_len);
754 opaque[frame->goaway.opaque_data_len] = 0;
755 reason = apr_psprintf(p, " reason=%s", opaque);
756 }
757
758 wconf->goaway = 1;
759 worker_log(worker, LOG_INFO, "> GOAWAY [error=%s%s]",
760 h2_get_name_of(h2_error_code_array, frame->goaway.error_code),
761 reason ? reason : "");
762 if (wconf->goaway_expect && strcasecmp(reason, wconf->goaway_expect)) {
763 rv = NGHTTP2_ERR_CALLBACK_FAILURE;
764 }
765 } break;
766 case NGHTTP2_SETTINGS:
767 if (frame->hd.flags == NGHTTP2_FLAG_ACK) {
768 const char *settings = apr_pstrdup(worker->pbody, "SETTINGS ACK");
769 worker_log(worker, LOG_INFO, "< %s", settings);
770 wconf->settings--;
771 } else {
772 const char *settings = h2_get_settings_as_text(
773 worker->pbody, frame->settings.iv, frame->settings.niv);
774 worker_log(worker, LOG_INFO, "< SETTINGS [%s]", settings);
775 }
776 break;
777 case NGHTTP2_PING: {
778 char *text = apr_pcalloc(p, 9);
779 memcpy(text, frame->ping.opaque_data, 8);
780 if (frame->hd.flags == NGHTTP2_FLAG_ACK) {
781 const char *pingText = apr_pstrcat(p, "PING ACK: ", text, NULL);
782 worker_log(worker, LOG_INFO, "< %s", pingText);
783 wconf->pings--;
784 } else {
785 const char *pingText = apr_pstrcat(p, "PING: ", text, NULL);
786 worker_log(worker, LOG_INFO, "< %s", pingText);
787 }
788 } break;
789 default:
790 worker_log(worker, LOG_INFO, "< UNKNOWN");
791 }
792 apr_pool_destroy(p);
793
794 return rv;
795 }
796
h2_on_begin_headers_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)797 static int h2_on_begin_headers_callback(nghttp2_session *session,
798 const nghttp2_frame *frame,
799 void *user_data) {
800 worker_t *worker = user_data;
801 worker_log(worker, LOG_DEBUG, "< HEADERS");
802
803 return 0;
804 }
805
h2_on_stream_close_callback(nghttp2_session * session,int32_t stream_id,uint32_t error_code,void * user_data)806 static int h2_on_stream_close_callback(nghttp2_session *session,
807 int32_t stream_id, uint32_t error_code,
808 void *user_data) {
809 worker_t *worker = user_data;
810 h2_wconf_t *wconf = h2_get_worker_config(worker);
811 h2_stream_t *stream = apr_hash_get(
812 wconf->streams, apr_itoa(worker->pbody, stream_id), APR_HASH_KEY_STRING);
813
814 worker_log(worker, LOG_DEBUG, "stream %d closed", stream->id);
815
816 return 0;
817 }
818
h2_check_content_length(h2_stream_t * stream,const char * name,const char * val)819 static void h2_check_content_length(h2_stream_t *stream, const char *name,
820 const char *val) {
821 if (strcmp(name, "content-length") == 0) {
822 stream->data_in_len = apr_atoi64(val);
823 }
824 }
825
h2_check_response_header(worker_t * worker,const char * name)826 static int h2_check_response_header(worker_t *worker, const char *name) {
827 #define H2_RES_HEADER_DENY 0x1
828 #define H2_RES_HEADER_FILTER 0x2
829 if (worker->headers_filter) {
830 if (apr_table_get(worker->headers_filter, name)) {
831 return H2_RES_HEADER_FILTER;
832 }
833 }
834 if (worker->headers_allow) {
835 if (!apr_table_get(worker->headers_allow, name)) {
836 worker_log(worker, LOG_ERR, "%s header not allowed", name);
837 return H2_RES_HEADER_DENY;
838 }
839 }
840 return 0;
841 }
842
h2_on_header_callback(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)843 static int h2_on_header_callback(nghttp2_session *session,
844 const nghttp2_frame *frame,
845 const uint8_t *name, size_t namelen,
846 const uint8_t *value, size_t valuelen,
847 uint8_t flags, void *user_data) {
848 worker_t *worker = user_data;
849 h2_wconf_t *wconf = h2_get_worker_config(worker);
850 h2_stream_t *stream =
851 apr_hash_get(wconf->streams, apr_itoa(worker->pbody, frame->hd.stream_id),
852 APR_HASH_KEY_STRING);
853
854 switch (frame->hd.type) {
855 case NGHTTP2_HEADERS: {
856 const char *nameStr = (const char *)name;
857 const char *valueStr = (const char *)value;
858 int action = h2_check_response_header(worker, nameStr);
859 if (action & H2_RES_HEADER_DENY) {
860 return NGHTTP2_ERR_CALLBACK_FAILURE;
861 }
862 h2_check_content_length(stream, nameStr, valueStr);
863
864 if (!(action & H2_RES_HEADER_FILTER)) {
865 apr_table_add(stream->headers_in, nameStr, valueStr);
866 worker_log(worker, LOG_INFO, "<%d %s: %s", stream->id, nameStr, valueStr);
867 }
868
869 } break;
870 }
871
872 return 0;
873 }
874
h2_on_data_chunk_recv_callback(nghttp2_session * session,uint8_t flags,int32_t stream_id,const uint8_t * data,size_t len,void * user_data)875 static int h2_on_data_chunk_recv_callback(nghttp2_session *session,
876 uint8_t flags, int32_t stream_id,
877 const uint8_t *data, size_t len,
878 void *user_data) {
879 worker_t *worker = user_data;
880 h2_wconf_t *wconf = h2_get_worker_config(worker);
881 h2_stream_t *stream = apr_hash_get(
882 wconf->streams, apr_itoa(worker->pbody, stream_id), APR_HASH_KEY_STRING);
883 int cur = stream->data_in_read;
884
885 if (!stream->data_in) {
886 stream->data_in = apr_palloc(stream->p, stream->data_in_len);
887 stream->data_in_read = 0;
888 }
889
890 worker_log(worker, LOG_DEBUG, "read %d from stream %d", len, stream_id);
891
892 if (stream->data_in_read + len > stream->data_in_len) {
893 worker_log(worker, LOG_ERR, "Buffer to small (%d), stopping receving",
894 stream->data_in_len);
895
896 return NGHTTP2_ERR_CALLBACK_FAILURE;
897 }
898
899 memcpy(&stream->data_in[stream->data_in_read], data, len);
900 stream->data_in_read += len;
901 stream->data_in[stream->data_in_read] = 0;
902
903 worker_log(worker, LOG_INFO, "<%d %s", stream_id, &stream->data_in[cur]);
904 return 0;
905 }
906
h2_setup_callbacks(nghttp2_session_callbacks * callbacks)907 static void h2_setup_callbacks(nghttp2_session_callbacks *callbacks) {
908 nghttp2_session_callbacks_set_send_callback(callbacks, h2_send_callback);
909
910 nghttp2_session_callbacks_set_recv_callback(callbacks, h2_recv_callback);
911
912 nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
913 h2_on_frame_send_callback);
914
915 nghttp2_session_callbacks_set_on_frame_not_send_callback(callbacks,
916 h2_on_frame_not_send_callback);
917
918 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
919 h2_on_frame_recv_callback);
920
921 nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks,
922 h2_on_begin_headers_callback);
923 nghttp2_session_callbacks_set_on_stream_close_callback(
924 callbacks, h2_on_stream_close_callback);
925
926 nghttp2_session_callbacks_set_on_header_callback(
927 callbacks, h2_on_header_callback);
928
929 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
930 callbacks, h2_on_data_chunk_recv_callback);
931 }
932
933
934 /************************************************************************
935 * Optional Functions
936 ************************************************************************/
937
938 /************************************************************************
939 * Commands
940 ***********************************************************************/
block_H2_STREAM_BUFFER(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)941 apr_status_t block_H2_STREAM_BUFFER(worker_t *worker, worker_t *parent,
942 apr_pool_t *ptmp) {
943 h2_wconf_t *wconf = h2_get_worker_config(parent);
944 const char *size = store_get(worker->params, "1");
945
946 wconf->buffer_size = apr_atoi64(size);
947
948 return APR_SUCCESS;
949 }
950
h2_setup(worker_t * worker,worker_t * parent)951 static apr_status_t h2_setup(worker_t *worker, worker_t *parent) {
952 h2_wconf_t *wconf = h2_get_worker_config(parent);
953 nghttp2_session_callbacks *callbacks;
954 h2_sconf_t *sconf;
955 apr_status_t rv;
956
957 sconf = h2_get_socket_config(parent);
958 sconf->ssl = ssl_get_session(parent);
959 sconf->authority = apr_pstrdup(parent->pbody, "sesdev.tarsec.com");
960
961 wconf->state |= H2_STATE_ESTABLISHED;
962
963 /* hacky: see worker.c:command_CALL() */
964 worker->socket = parent->socket;
965
966 rv = nghttp2_session_callbacks_new(&callbacks);
967 if (rv != 0) {
968 worker_log(worker, LOG_ERR, "Can not create http2 callbacks");
969 return APR_EGENERAL;
970 }
971 h2_setup_callbacks(callbacks);
972
973 if (!sconf->is_server) {
974 worker_log(worker, LOG_DEBUG,
975 "client nghttp2 install worker: %" APR_UINT64_T_HEX_FMT, parent);
976 rv = nghttp2_session_client_new(&sconf->session, callbacks, parent);
977 }
978 else {
979 worker_log(worker, LOG_DEBUG,
980 "server nghttp2 install worker: %" APR_UINT64_T_HEX_FMT, parent);
981 rv = nghttp2_session_server_new(&sconf->session, callbacks, parent);
982 }
983 nghttp2_session_callbacks_del(callbacks);
984 if (rv != 0) {
985 worker_log(worker, LOG_ERR, "Can not create http2 session");
986 return APR_EGENERAL;
987 }
988
989 return rv;
990 }
991
block_H2_UPGRADE(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)992 apr_status_t block_H2_UPGRADE(worker_t *worker, worker_t *parent,
993 apr_pool_t *ptmp) {
994 return h2_setup(worker, parent);
995 }
996
block_H2_SESSION(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)997 apr_status_t block_H2_SESSION(worker_t *worker, worker_t *parent,
998 apr_pool_t *ptmp) {
999 h2_wconf_t *wconf = h2_get_worker_config(parent);
1000 const char *host = store_get(worker->params, "1");
1001 const char *port = store_get(worker->params, "2");
1002 const char *cert = store_get(worker->params, "3");
1003 const char *key = store_get(worker->params, "4");
1004 const char *cacert = store_get(worker->params, "5");
1005 const char *data;
1006 apr_status_t rv;
1007
1008 data = apr_psprintf(ptmp, "%s %s%s", host,
1009 strstr(port, "SSL") ? "" : "SSL:", port);
1010 if (cert && key) {
1011 data = apr_pstrcat(ptmp, data, " ", cert, " ", key, NULL);
1012 }
1013 if (cacert) {
1014 data = apr_pstrcat(ptmp, data, " ", cacert, NULL);
1015 }
1016
1017 wconf->state |= H2_STATE_INIT;
1018 rv = command_REQ(NULL, parent, (char *)data, parent->pbody);
1019
1020 if (rv != 0) {
1021 return APR_EGENERAL;
1022 }
1023
1024 return h2_setup(worker, parent);
1025 }
1026
block_H2_SETTINGS(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1027 apr_status_t block_H2_SETTINGS(worker_t *worker, worker_t *parent,
1028 apr_pool_t *ptmp) {
1029 h2_sconf_t *sconf = h2_get_socket_config(parent);
1030 h2_wconf_t *wconf = h2_get_worker_config(parent);
1031 nghttp2_settings_entry settings[6];
1032 size_t cur = 0;
1033 const char *param;
1034 int i = 0, rv;
1035
1036 param = store_get(worker->params, apr_itoa(ptmp, ++i));
1037 memset(&settings, 0, sizeof(settings));
1038
1039 while (param && cur < 6) {
1040 char *setting = apr_pstrdup(parent->pbody, param);
1041 char *value;
1042 char *key = apr_strtok(setting, "=", &value);
1043 int32_t settings_id = h2_get_id_of(h2_settings_array, key);
1044
1045 if (settings_id > 0) {
1046 settings[cur].settings_id = settings_id;
1047 settings[cur].value = apr_atoi64(value);
1048 cur++;
1049 } else {
1050 worker_log(worker, LOG_ERR, "Unknown h2 setting '%s'", key);
1051 return APR_EINVAL;
1052 }
1053 param = store_get(worker->params, apr_itoa(ptmp, ++i));
1054 }
1055
1056 if ((rv = nghttp2_submit_settings(sconf->session, NGHTTP2_FLAG_NONE, settings,
1057 cur)) != 0) {
1058 worker_log(worker, LOG_ERR, "%s", nghttp2_strerror(rv));
1059 return APR_EINVAL;
1060 }
1061 wconf->settings++;
1062
1063 return APR_SUCCESS;
1064 }
1065
h2_data_read_callback(nghttp2_session * session,int32_t stream_id,uint8_t * buf,size_t length,uint32_t * data_flags,nghttp2_data_source * source,void * user_data)1066 static ssize_t h2_data_read_callback(nghttp2_session *session,
1067 int32_t stream_id, uint8_t *buf,
1068 size_t length, uint32_t *data_flags,
1069 nghttp2_data_source *source,
1070 void *user_data) {
1071 worker_t *worker = user_data;
1072 h2_wconf_t *wconf = h2_get_worker_config(worker);
1073 h2_stream_t *stream = apr_hash_get(
1074 wconf->streams, apr_itoa(worker->pbody, stream_id), APR_HASH_KEY_STRING);
1075 event_t *event = APR_RING_FIRST(stream->events);
1076 apr_size_t len = 0;
1077
1078 if (event) {
1079 apr_size_t diff = -1;
1080
1081 if (event->at >= stream->data_sent) {
1082 diff = event->at - stream->data_sent;
1083 }
1084
1085 if (diff == 0) {
1086 APR_RING_UNSPLICE(event, event, link);
1087
1088 if (event->type == EVENT_FLUSH) {
1089 apr_sleep(event->sleep * 1000);
1090 } else if (event->type == EVENT_DEFER) {
1091 return NGHTTP2_ERR_DEFERRED;
1092 }
1093
1094 return h2_data_read_callback(session, stream_id, buf, length, data_flags,
1095 source, user_data);
1096 } else if (diff <= length) {
1097 length = diff;
1098 }
1099 }
1100
1101 if (stream->data_len == 0) {
1102 len = 0;
1103 goto eofcheck;
1104 } else if (stream->data_len - stream->data_sent <= length) {
1105 len = stream->data_len - stream->data_sent;
1106 memcpy(buf, &stream->data[stream->data_sent], len);
1107 } else {
1108 len = length;
1109 memcpy(buf, &stream->data[stream->data_sent], length);
1110 }
1111
1112 buf[len] = 0;
1113 stream->data_sent += len;
1114
1115 worker_log(worker, LOG_INFO, ">%d %s", stream_id, buf);
1116 worker_log(worker, LOG_DEBUG, "send %u bytes (%u/%u)", len, stream->data_sent,
1117 stream->data_len);
1118
1119 eofcheck:
1120 if (APR_RING_EMPTY(stream->events, _event_t, link) &&
1121 stream->data_len == stream->data_sent) {
1122 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
1123 }
1124
1125 return len;
1126 }
1127
h2_get_new_stream(worker_t * worker,int stream_id)1128 h2_stream_t* h2_get_new_stream(worker_t *worker, int stream_id) {
1129 h2_wconf_t *wconf = h2_get_worker_config(worker);
1130 h2_stream_t *stream = apr_pcalloc(worker->pbody, sizeof(*stream));
1131
1132 stream->id = stream_id;
1133 apr_pool_create(&stream->p, worker->pbody);
1134
1135 stream->data_in_len = wconf->buffer_size;
1136 stream->headers_in = apr_table_make(stream->p, 20);
1137 stream->headers_out = apr_table_make(stream->p, 20);
1138 stream->events = apr_palloc(stream->p, sizeof(event_ring_t));
1139 APR_RING_INIT(stream->events, _event_t, link);
1140
1141 return stream;
1142 }
1143
copy_data(worker_t * worker,h2_stream_t * stream,int pos)1144 static apr_status_t copy_data(worker_t *worker, h2_stream_t *stream, int pos) {
1145 enum { CALC, COPY };
1146 apr_status_t status = APR_SUCCESS;
1147 apr_size_t data_len;
1148 apr_table_entry_t *e;
1149 int i, action = 0;
1150
1151 /* first calculate size, then copy data */
1152 for (action = CALC; action <= COPY; action++) {
1153 i = pos;
1154 data_len = 0;
1155
1156 e = (apr_table_entry_t *)apr_table_elts(worker->cache)->elts;
1157 for (; i < apr_table_elts(worker->cache)->nelts; i++) {
1158 line_t line;
1159 line.info = e[i].key;
1160 line.buf = e[i].val;
1161 line.len = 0;
1162
1163 if (strcmp("DEFER", line.info) == 0) {
1164 if (action == CALC) {
1165 event_t *event = apr_palloc(stream->p, sizeof(event_t));
1166 event->type = EVENT_DEFER;
1167 event->at = data_len;
1168 event->defer = 1;
1169 APR_RING_INSERT_TAIL(stream->events, event, _event_t, link);
1170 }
1171 }
1172 else if (strcmp("FLUSH", line.info) == 0) {
1173 if (action == CALC) {
1174 event_t *event = apr_palloc(stream->p, sizeof(event_t));
1175 event->type = EVENT_FLUSH;
1176 event->at = data_len;
1177 event->sleep = apr_atoi64(line.buf);
1178 APR_RING_INSERT_TAIL(stream->events, event, _event_t, link);
1179 }
1180 continue;
1181 }
1182
1183 if (action == CALC && strstr(line.info, "resolve")) {
1184 int unresolved;
1185 e[i].val = line.buf =
1186 worker_replace_vars(worker, line.buf, &unresolved, worker->pbody);
1187 }
1188
1189 if ((status = htt_run_line_flush(worker, &line)) != APR_SUCCESS) {
1190 return status;
1191 }
1192
1193 if (strncasecmp(line.info, "NOCRLF:", 7) == 0) {
1194 line.len = apr_atoi64(&line.info[7]);
1195 } else if (strcasecmp(line.info, "NOCRLF") == 0) {
1196 line.len = strlen(line.buf);
1197 } else if (strcasecmp(line.info, "PLAIN") == 0) {
1198 /* add CRLF */
1199 if (action == CALC) {
1200 e[i].val = line.buf =
1201 apr_pstrcat(worker->pbody, line.buf, "\r\n", NULL);
1202 }
1203 line.len = strlen(line.buf);
1204 } else {
1205 line.len = strlen(line.buf);
1206 }
1207
1208 if (action == COPY) {
1209 memcpy(&stream->data[data_len], line.buf, line.len);
1210 }
1211 data_len += line.len;
1212 }
1213
1214 if (action == CALC) {
1215 stream->data_len = data_len;
1216 stream->data = apr_palloc(worker->pbody, data_len);
1217 }
1218 }
1219
1220 return status;
1221 }
1222
block_H2_SLEEP(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1223 apr_status_t block_H2_SLEEP(worker_t *worker, worker_t *parent,
1224 apr_pool_t *ptmp) {
1225 const char *ms = store_get(worker->params, "1");
1226
1227 apr_table_add(worker->cache, "FLUSH", ms);
1228
1229 return APR_SUCCESS;
1230 }
1231
block_H2_FLUSH(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1232 apr_status_t block_H2_FLUSH(worker_t *worker, worker_t *parent,
1233 apr_pool_t *ptmp) {
1234 apr_table_add(worker->cache, "FLUSH", "0");
1235
1236 return APR_SUCCESS;
1237 }
1238
block_H2_DEFER(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1239 apr_status_t block_H2_DEFER(worker_t *worker, worker_t *parent,
1240 apr_pool_t *ptmp) {
1241 apr_table_add(worker->cache, "DEFER", "");
1242
1243 /* add marker */
1244 apr_table_add(worker->expect.headers, DEFER_MARKER, "");
1245 apr_table_add(worker->expect.dot, DEFER_MARKER, "");
1246 apr_table_add(worker->match.headers, DEFER_MARKER, "");
1247 apr_table_add(worker->match.dot, DEFER_MARKER, "");
1248
1249 return APR_SUCCESS;
1250 }
1251
block_H2_REQ(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1252 apr_status_t block_H2_REQ(worker_t *worker, worker_t *parent,
1253 apr_pool_t *ptmp) {
1254 h2_wconf_t *wconf = h2_get_worker_config(parent);
1255 h2_sconf_t *sconf = h2_get_socket_config(parent);
1256 const char *method = store_get(worker->params, "1");
1257 const char *path = store_get(worker->params, "2");
1258 int i = 0, hdrn = 0, with_data = 0;
1259 apr_table_entry_t *e;
1260 apr_status_t status;
1261 h2_stream_t *stream;
1262 int32_t stream_id;
1263 worker_t *body;
1264 nghttp2_nv *hdrs;
1265
1266 nghttp2_data_provider data_prd;
1267 data_prd.read_callback = h2_data_read_callback;
1268
1269 if ((status = h2_open_session(parent)) != APR_SUCCESS) {
1270 return status;
1271 }
1272
1273 if ((status = h2_worker_body(&body, parent, "_H2:END")) != APR_SUCCESS) {
1274 return status;
1275 }
1276
1277 stream_id = nghttp2_session_get_next_stream_id(sconf->session);
1278 worker_log(parent, LOG_DEBUG, "new stream %d", stream_id);
1279
1280 stream = h2_get_new_stream(parent, stream_id);
1281 wconf->current_stream = stream;
1282 status = body->interpret(body, parent, NULL);
1283
1284 /* copy expectations */
1285 stream->expect.headers = apr_table_make(parent->pbody, 10);
1286 stream->expect.body = apr_table_make(parent->pbody, 10);
1287 stream->expect.dot = apr_table_make(parent->pbody, 10);
1288 stream->match.headers = apr_table_make(parent->pbody, 10);
1289 stream->match.body = apr_table_make(parent->pbody, 10);
1290 stream->match.dot = apr_table_make(parent->pbody, 10);
1291 apr_table_do(copy_table_entry, stream->expect.headers,
1292 parent->expect.headers, NULL);
1293 apr_table_do(copy_table_entry, stream->expect.body,
1294 parent->expect.body, NULL);
1295 apr_table_do(copy_table_entry, stream->expect.dot,
1296 parent->expect.dot, NULL);
1297 apr_table_do(copy_table_entry, stream->match.headers,
1298 parent->match.headers, NULL);
1299 apr_table_do(copy_table_entry, stream->match.body,
1300 parent->match.body, NULL);
1301 apr_table_do(copy_table_entry, stream->match.dot,
1302 parent->match.dot, NULL);
1303 apr_table_clear(parent->expect.headers);
1304 apr_table_clear(parent->expect.body);
1305 apr_table_clear(parent->expect.dot);
1306 apr_table_clear(parent->match.headers);
1307 apr_table_clear(parent->match.body);
1308 apr_table_clear(parent->match.dot);
1309
1310 /* copy headers */
1311 e = (apr_table_entry_t *)apr_table_elts(parent->cache)->elts;
1312
1313 while (i < apr_table_elts(parent->cache)->nelts && *e[i].val) {
1314 char *name, *val;
1315
1316 name = apr_strtok(e[i].val, ":", &val);
1317 if (*val && apr_isspace(*val)) {
1318 val++;
1319 }
1320
1321 if (strcasecmp("host", name) == 0) {
1322 sconf->authority = val;
1323 }
1324 apr_table_add(stream->headers_out, name, val);
1325 i++;
1326 }
1327
1328 /* jump over empty line that separates headers from data */
1329 if (++i >= apr_table_elts(parent->cache)->nelts) {
1330 /* no data */
1331 goto submit;
1332 }
1333
1334 with_data = (i < apr_table_elts(parent->cache)->nelts);
1335 if (with_data) {
1336 /* copy data */
1337 if ((status = copy_data(parent, stream, i)) != APR_SUCCESS) {
1338 goto on_error;
1339 }
1340 }
1341
1342 submit:
1343 apr_table_clear(parent->cache);
1344
1345 hdrs = apr_pcalloc(parent->pbody, sizeof(*hdrs) * (4 + apr_table_elts(stream->headers_out)->nelts));
1346 nghttp2_nv meth_nv = MAKE_NV(":method", 7, method, strlen(method));
1347 nghttp2_nv path_nv = MAKE_NV(":path", 5, path, strlen(path));
1348 nghttp2_nv scheme_nv = MAKE_NV(":scheme", 7, "https", 5);
1349 nghttp2_nv auth_nv = MAKE_NV(":authority", 10, sconf->authority, strlen(sconf->authority));
1350 hdrs[hdrn++] = meth_nv;
1351 hdrs[hdrn++] = path_nv;
1352 hdrs[hdrn++] = scheme_nv;
1353 hdrs[hdrn++] = auth_nv;
1354
1355 e = (apr_table_entry_t *) apr_table_elts(stream->headers_out)->elts;
1356 for (i = 0; i < apr_table_elts(stream->headers_out)->nelts; i++) {
1357 char *name = e[i].key;
1358 char *val = e[i].val;
1359
1360 /* calculate content length (AUTO) */
1361 if (strcasecmp(name, "Content-Length") == 0 && *val == 0) {
1362 val = with_data ? apr_psprintf(parent->pbody, "%" APR_SIZE_T_FMT , stream->data_len) : "0";
1363 }
1364 nghttp2_nv hdr_nv = MAKE_NV(name, strlen(name), val, strlen(val));
1365 hdrs[hdrn++] = hdr_nv;
1366 }
1367
1368 if (stream->data_len > 0) {
1369 /* data is submitted later in order to support deferring */
1370 stream_id =
1371 nghttp2_submit_headers(sconf->session, 0, -1, NULL, hdrs, hdrn, parent);
1372 } else {
1373 stream_id =
1374 nghttp2_submit_request(sconf->session, NULL, hdrs, hdrn, &data_prd, parent);
1375 }
1376
1377 if (stream_id < 0) {
1378 worker_log(parent, LOG_ERR, "Could not submit request: %s",
1379 nghttp2_strerror(stream_id));
1380 return APR_EGENERAL;
1381 }
1382 apr_hash_set(wconf->streams, apr_itoa(parent->pbody, stream_id),
1383 APR_HASH_KEY_STRING, stream);
1384 wconf->open_streams++;
1385 wconf->current_stream = NULL;
1386
1387 on_error:
1388 worker_body_end(body, parent);
1389 return status;
1390 }
1391
block_H2_EXPECT(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1392 apr_status_t block_H2_EXPECT(worker_t *worker, worker_t *parent,
1393 apr_pool_t *ptmp) {
1394 h2_wconf_t *wconf = h2_get_worker_config(parent);
1395 const char *cat = store_get(worker->params, "1");
1396 const char *expect = store_get(worker->params, "2");
1397 h2_stream_t *stream = wconf->current_stream;
1398 apr_status_t status = APR_SUCCESS;
1399
1400 /* session expectations */
1401 if (strcmp(cat, "goaway") == 0) {
1402 wconf->goaway_expect = expect;
1403 status = APR_SUCCESS;
1404 goto exit;
1405 }
1406
1407 /* stream expectations */
1408 if (!stream) {
1409 worker_log(worker, LOG_ERR, "Invalid scope for command, no submitted streams");
1410 status = APR_EINVAL;
1411 goto exit;
1412 }
1413
1414 if (strcmp(cat, "bodysize") == 0) {
1415 stream->data_in_expect = apr_atoi64(expect);
1416 } else if (strcmp(cat, "rst_stream") == 0) {
1417 stream->reset_expect = 1;
1418 } else {
1419 worker_log(worker, LOG_ERR, "Invalid expectation");
1420 status = APR_EINVAL;
1421 }
1422
1423 exit:
1424 return status;
1425 }
1426
block_H2_PING(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1427 apr_status_t block_H2_PING(worker_t *worker, worker_t *parent, apr_pool_t *ptmp) {
1428 h2_sconf_t *sconf = h2_get_socket_config(parent);
1429 h2_wconf_t *wconf = h2_get_worker_config(parent);
1430 const char *data = store_get(worker->params, "1");
1431 apr_status_t status;
1432
1433 if ((status = h2_open_session(parent)) != APR_SUCCESS) {
1434 return status;
1435 }
1436
1437 if (nghttp2_submit_ping(sconf->session, NGHTTP2_FLAG_NONE,
1438 (const uint8_t *)data) != 0) {
1439 worker_log(worker, LOG_ERR, "Could not submit PING");
1440 return APR_EINVAL;
1441 }
1442 wconf->pings++;
1443
1444 return APR_SUCCESS;
1445 }
1446
block_H2_GOAWAY(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1447 apr_status_t block_H2_GOAWAY(worker_t *worker, worker_t *parent, apr_pool_t *ptmp) {
1448 h2_sconf_t *sconf = h2_get_socket_config(parent);
1449 const char *error = store_get(worker->params, "1");
1450 const char *data = store_get(worker->params, "2");
1451 int32_t err_code;
1452 apr_status_t status;
1453
1454 if ((status = h2_open_session(parent)) != APR_SUCCESS) {
1455 return status;
1456 }
1457
1458 if (!error) {
1459 error = h2_error_code_array[0];
1460 }
1461 if ((err_code = h2_get_id_of(h2_error_code_array, error)) == -1) {
1462 worker_log(worker, LOG_ERR, "Invalid error code");
1463 return APR_EINVAL;
1464 }
1465 if (nghttp2_submit_goaway(
1466 sconf->session, NGHTTP2_FLAG_NONE,
1467 nghttp2_session_get_last_proc_stream_id(sconf->session),
1468 err_code , (void *)data,
1469 data ? strlen(data) : 0) != 0) {
1470
1471 worker_log(worker, LOG_ERR, "Could not submit GOAWAY");
1472 return APR_EINVAL;
1473 }
1474
1475 return APR_SUCCESS;
1476 }
1477
1478
1479
block_H2_WAIT(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)1480 apr_status_t block_H2_WAIT(worker_t *worker, worker_t *parent,
1481 apr_pool_t *ptmp) {
1482 h2_wconf_t *wconf = h2_get_worker_config(parent);
1483 apr_status_t status;
1484 apr_hash_index_t *hi;
1485
1486 if ((status = h2_open_session(parent)) != APR_SUCCESS) {
1487 return status;
1488 }
1489
1490 status = mypoll(parent);
1491
1492 if (wconf->goaway_expect && !wconf->goaway) {
1493 worker_log(worker, LOG_ERR, "EXPECT GOAWAY [reason=%s]", wconf->goaway_expect);
1494 status = APR_EGENERAL;
1495 } else if (wconf->goaway_expect && wconf->goaway) {
1496 status = APR_SUCCESS;
1497 } else if (status != APR_SUCCESS) {
1498 worker_log(worker, LOG_ERR, "error at reading from remote");
1499 }
1500
1501 cleanup:
1502 for (hi = apr_hash_first(worker->pbody, wconf->streams); hi; hi = apr_hash_next(hi)) {
1503 h2_stream_t *stream;
1504 apr_hash_this(hi, NULL, NULL, (void **)&stream);
1505
1506 if (stream->closed) {
1507 if (stream->reset_expect && !stream->reset_received) {
1508 worker_log(worker, LOG_ERR, "EXPECT RST_STREAM for stream %d", stream->id);
1509 return APR_EGENERAL;
1510 }
1511 worker_log(worker, LOG_DEBUG, "clean-up stream %p", stream);
1512 apr_pool_destroy(stream->p);
1513 apr_hash_set(wconf->streams, apr_itoa(parent->pbody, stream->id),
1514 APR_HASH_KEY_STRING, NULL);
1515 }
1516 }
1517 wconf->goaway_expect = NULL;
1518
1519 return status;
1520 }
1521
1522 /************************************************************************
1523 * Hooks
1524 ************************************************************************/
h2_hook_pre_close(worker_t * worker)1525 apr_status_t h2_hook_pre_close(worker_t *worker) {
1526 h2_wconf_t *wconf = h2_get_worker_config(worker);
1527 h2_sconf_t *sconf = h2_get_socket_config(worker);
1528 apr_status_t status = APR_SUCCESS;
1529
1530 if (!sconf || !sconf->session) {
1531 goto exit;
1532 }
1533 if (!wconf) {
1534 goto exit;
1535 }
1536 wconf->state = H2_STATE_CLOSED;
1537 module_set_config(worker->config, apr_pstrdup(worker->pbody, h2_module),
1538 NULL);
1539
1540 exit:
1541 return status;
1542 }
1543
select_proto(unsigned char ** const out,unsigned char * outlen,const unsigned char * const in,unsigned int inlen,const unsigned char * const key)1544 int select_proto( unsigned char ** const out, unsigned char *outlen,
1545 const unsigned char * const in, unsigned int inlen,
1546 const unsigned char * const key) {
1547 const char *p = (const char * const)in;
1548 const char * const end = (const char * const)(in + inlen);
1549
1550 const char * const keyStr = (const char * const)key;
1551
1552 for (; p + strlen(keyStr) <= end; p += *p + 1) {
1553 if (strncmp(p, keyStr, strlen(keyStr)) == 0) {
1554 /* From the manual of SSL_CTX_set_next_proto_select_cb():
1555 * "For the callback itself, out must be set to point to the selected protocol (which may be within in)."
1556 * This is why this "const-removing" cast is acceptable.*/
1557 *out = (unsigned char *)(p + 1);
1558 *outlen = *p;
1559 return 1;
1560 }
1561 }
1562
1563 return 0;
1564 }
1565
select_next_proto_cb(SSL * ssl,unsigned char ** out,unsigned char * outlen,const unsigned char * in,unsigned int inlen,void * arg)1566 int select_next_proto_cb(SSL *ssl, unsigned char **out,
1567 unsigned char *outlen, const unsigned char *in,
1568 unsigned int inlen, void *arg) {
1569 worker_t *worker = (worker_t *)arg;
1570 const unsigned char * const protos[] = {h2, h216, h214, NULL};
1571 int i;
1572
1573 for (i = 0; protos[i]; i++) {
1574 if (select_proto(out, outlen, in, inlen, protos[i])) {
1575 worker_log(worker, LOG_DEBUG, "select %s", protos[i] + 1);
1576 return SSL_TLSEXT_ERR_OK;
1577 }
1578 }
1579
1580 worker_log(worker, LOG_INFO, "remote does not offer h2 protocol");
1581 return ~ SSL_TLSEXT_ERR_OK;
1582 }
1583
h2_hook_pre_connect(worker_t * worker)1584 static apr_status_t h2_hook_pre_connect(worker_t *worker) {
1585 h2_wconf_t *wconf = h2_get_worker_config(worker);
1586 SSL_CTX *ssl_ctx = ssl_get_ctx(worker);
1587
1588 if (ssl_ctx && wconf->state & H2_STATE_INIT) {
1589 SSL_CTX_set_alpn_protos(ssl_ctx, h2, 3);
1590 SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, worker);
1591 wconf->state |= H2_STATE_NEGOTIATE;
1592 } else {
1593 /* reset in case of mixed protocol usage */
1594 /* SSL_CTX_set_alpn_protos(ssl_ctx, 0, 0); */
1595 }
1596
1597 return APR_SUCCESS;
1598 }
1599
h2_hook_close(worker_t * worker,char * info,char ** new_info)1600 static apr_status_t h2_hook_close(worker_t *worker, char *info, char **new_info) {
1601 h2_sconf_t *sconf = h2_get_socket_config(worker);
1602
1603 worker_log(worker, LOG_DEBUG, "h2_hook_close worker: %" APR_UINT64_T_HEX_FMT
1604 ", info: %s, sconf: %" APR_UINT64_T_HEX_FMT,
1605 worker, info, sconf);
1606 nghttp2_session_del(sconf->session);
1607 sconf->session = NULL;
1608 sconf->ssl = NULL;
1609
1610 return APR_SUCCESS;
1611 }
1612
h2_hook_accept(worker_t * worker,char * data)1613 static apr_status_t h2_hook_accept(worker_t *worker, char *data) {
1614 h2_sconf_t *sconfig = h2_get_socket_config(worker);
1615 sconfig->is_server = 1;
1616
1617 return APR_SUCCESS;
1618 }
1619
1620 /************************************************************************
1621 * Module
1622 ***********************************************************************/
h2_module_init(global_t * global)1623 apr_status_t h2_module_init(global_t *global) {
1624 apr_status_t status;
1625 if ((status = module_command_new(global, "H2", "_STREAM_BUFFER", "<size>",
1626 "Set receive buffer size.",
1627 block_H2_STREAM_BUFFER)) != APR_SUCCESS) {
1628 return status;
1629 }
1630
1631 if ((status = module_command_new(global, "H2", "_SESSION", "<host> SSL:<port> [<cert-file> <key-file> [<ca-cert-file>]]",
1632 "Connect to the remote peer and setup h2 session.\n"
1633 "<host>: host name or IPv4/IPv6 address (IPv6 address must be surrounded in square brackets)\n"
1634 "<cert-file>, <key-file> and <ca-cert-file> are optional for client/server authentication",
1635 block_H2_SESSION)) != APR_SUCCESS) {
1636 return status;
1637 }
1638
1639 if ((status = module_command_new(global, "H2", "_UPGRADE", "",
1640 "Upgrade connection.",
1641 block_H2_UPGRADE)) != APR_SUCCESS) {
1642 return status;
1643 }
1644
1645 if ((status = module_command_new(global, "H2", "_SETTINGS", "<settings>",
1646 "Submit session settings.",
1647 block_H2_SETTINGS)) != APR_SUCCESS) {
1648 return status;
1649 }
1650
1651 if ((status = module_command_new(global, "H2", "_FLUSH", "",
1652 "Flush submitted data.",
1653 block_H2_FLUSH)) != APR_SUCCESS) {
1654 return status;
1655 }
1656
1657 if ((status = module_command_new(global, "H2", "_DEFER", "",
1658 "Defers subsequent data frames until reception of new frames from the remote peer.",
1659 block_H2_DEFER)) != APR_SUCCESS) {
1660 return status;
1661 }
1662
1663 if ((status = module_command_new(global, "H2", "_SLEEP", "<ms>",
1664 "Sleep <ms> milliseconds during the transmission of two consecutive data frames.",
1665 block_H2_SLEEP)) != APR_SUCCESS) {
1666 return status;
1667 }
1668
1669 if ((status = module_command_new(global, "H2", "_REQ", "<method> <url>",
1670 "Submit request. Close the request body with _H2:END.",
1671 block_H2_REQ)) != APR_SUCCESS) {
1672 return status;
1673 }
1674
1675 if ((status = module_command_new(global, "H2", "_EXPECT", "<category> <expectation>",
1676 "Possible expectations are\n"
1677 " goaway <reason>\n"
1678 " can be expected for a session, e.g. \"H2:EXPECT goaway timeout\"\n"
1679 " bodysize <size>\n"
1680 " can be expected for a stream, e.g. \"H2:EXPECT bodysize 1024\"\n"
1681 " rst_stream\n"
1682 " can be expected for a stream, no specific expectation required\n",
1683 block_H2_EXPECT)) != APR_SUCCESS) {
1684 return status;
1685 }
1686
1687 if ((status = module_command_new(global, "H2", "_PING", "<8 byte>",
1688 "Send ping to the remote peer.",
1689 block_H2_PING)) != APR_SUCCESS) {
1690 return status;
1691 }
1692
1693 if ((status = module_command_new(global, "H2", "_GOAWAY", "<error-code> <string>",
1694 "Send goaway to the remote peer.",
1695 block_H2_GOAWAY)) != APR_SUCCESS) {
1696 return status;
1697 }
1698
1699 if ((status = module_command_new(global, "H2", "_WAIT", "",
1700 "Send and receive pending frames to/from the remote peer.",
1701 block_H2_WAIT)) != APR_SUCCESS) {
1702 return status;
1703 }
1704
1705 htt_hook_accept(h2_hook_accept, NULL, NULL, 0);
1706 htt_hook_pre_connect(h2_hook_pre_connect, NULL, NULL, 0);
1707 htt_hook_pre_close(h2_hook_pre_close, NULL, NULL, 0);
1708 htt_hook_close(h2_hook_close, NULL, NULL, 0);
1709 return APR_SUCCESS;
1710 }
1711
1712
1713