1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25 #include "curl_setup.h"
26
27 #if defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY)
28
29 #include <nghttp2/nghttp2.h>
30 #include "urldata.h"
31 #include "cfilters.h"
32 #include "connect.h"
33 #include "curl_trc.h"
34 #include "bufq.h"
35 #include "dynbuf.h"
36 #include "dynhds.h"
37 #include "http1.h"
38 #include "http2.h"
39 #include "http_proxy.h"
40 #include "multiif.h"
41 #include "sendf.h"
42 #include "cf-h2-proxy.h"
43
44 /* The last 3 #include files should be in this order */
45 #include "curl_printf.h"
46 #include "curl_memory.h"
47 #include "memdebug.h"
48
49 #define PROXY_H2_CHUNK_SIZE (16*1024)
50
51 #define PROXY_HTTP2_HUGE_WINDOW_SIZE (100 * 1024 * 1024)
52 #define H2_TUNNEL_WINDOW_SIZE (10 * 1024 * 1024)
53
54 #define PROXY_H2_NW_RECV_CHUNKS (H2_TUNNEL_WINDOW_SIZE / PROXY_H2_CHUNK_SIZE)
55 #define PROXY_H2_NW_SEND_CHUNKS 1
56
57 #define H2_TUNNEL_RECV_CHUNKS (H2_TUNNEL_WINDOW_SIZE / PROXY_H2_CHUNK_SIZE)
58 #define H2_TUNNEL_SEND_CHUNKS ((128 * 1024) / PROXY_H2_CHUNK_SIZE)
59
60
61 typedef enum {
62 H2_TUNNEL_INIT, /* init/default/no tunnel state */
63 H2_TUNNEL_CONNECT, /* CONNECT request is being send */
64 H2_TUNNEL_RESPONSE, /* CONNECT response received completely */
65 H2_TUNNEL_ESTABLISHED,
66 H2_TUNNEL_FAILED
67 } h2_tunnel_state;
68
69 struct tunnel_stream {
70 struct http_resp *resp;
71 struct bufq recvbuf;
72 struct bufq sendbuf;
73 char *authority;
74 int32_t stream_id;
75 uint32_t error;
76 h2_tunnel_state state;
77 BIT(has_final_response);
78 BIT(closed);
79 BIT(reset);
80 };
81
tunnel_stream_init(struct Curl_cfilter * cf,struct tunnel_stream * ts)82 static CURLcode tunnel_stream_init(struct Curl_cfilter *cf,
83 struct tunnel_stream *ts)
84 {
85 const char *hostname;
86 int port;
87 bool ipv6_ip;
88 CURLcode result;
89
90 ts->state = H2_TUNNEL_INIT;
91 ts->stream_id = -1;
92 Curl_bufq_init2(&ts->recvbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_RECV_CHUNKS,
93 BUFQ_OPT_SOFT_LIMIT);
94 Curl_bufq_init(&ts->sendbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS);
95
96 result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip);
97 if(result)
98 return result;
99
100 ts->authority = /* host:port with IPv6 support */
101 aprintf("%s%s%s:%d", ipv6_ip ? "[":"", hostname,
102 ipv6_ip ? "]" : "", port);
103 if(!ts->authority)
104 return CURLE_OUT_OF_MEMORY;
105
106 return CURLE_OK;
107 }
108
tunnel_stream_clear(struct tunnel_stream * ts)109 static void tunnel_stream_clear(struct tunnel_stream *ts)
110 {
111 Curl_http_resp_free(ts->resp);
112 Curl_bufq_free(&ts->recvbuf);
113 Curl_bufq_free(&ts->sendbuf);
114 Curl_safefree(ts->authority);
115 memset(ts, 0, sizeof(*ts));
116 ts->state = H2_TUNNEL_INIT;
117 }
118
h2_tunnel_go_state(struct Curl_cfilter * cf,struct tunnel_stream * ts,h2_tunnel_state new_state,struct Curl_easy * data)119 static void h2_tunnel_go_state(struct Curl_cfilter *cf,
120 struct tunnel_stream *ts,
121 h2_tunnel_state new_state,
122 struct Curl_easy *data)
123 {
124 (void)cf;
125
126 if(ts->state == new_state)
127 return;
128 /* leaving this one */
129 switch(ts->state) {
130 case H2_TUNNEL_CONNECT:
131 data->req.ignorebody = FALSE;
132 break;
133 default:
134 break;
135 }
136 /* entering this one */
137 switch(new_state) {
138 case H2_TUNNEL_INIT:
139 CURL_TRC_CF(data, cf, "[%d] new tunnel state 'init'", ts->stream_id);
140 tunnel_stream_clear(ts);
141 break;
142
143 case H2_TUNNEL_CONNECT:
144 CURL_TRC_CF(data, cf, "[%d] new tunnel state 'connect'", ts->stream_id);
145 ts->state = H2_TUNNEL_CONNECT;
146 break;
147
148 case H2_TUNNEL_RESPONSE:
149 CURL_TRC_CF(data, cf, "[%d] new tunnel state 'response'", ts->stream_id);
150 ts->state = H2_TUNNEL_RESPONSE;
151 break;
152
153 case H2_TUNNEL_ESTABLISHED:
154 CURL_TRC_CF(data, cf, "[%d] new tunnel state 'established'",
155 ts->stream_id);
156 infof(data, "CONNECT phase completed");
157 data->state.authproxy.done = TRUE;
158 data->state.authproxy.multipass = FALSE;
159 FALLTHROUGH();
160 case H2_TUNNEL_FAILED:
161 if(new_state == H2_TUNNEL_FAILED)
162 CURL_TRC_CF(data, cf, "[%d] new tunnel state 'failed'", ts->stream_id);
163 ts->state = new_state;
164 /* If a proxy-authorization header was used for the proxy, then we should
165 make sure that it is not accidentally used for the document request
166 after we have connected. So let's free and clear it here. */
167 Curl_safefree(data->state.aptr.proxyuserpwd);
168 break;
169 }
170 }
171
172 struct cf_h2_proxy_ctx {
173 nghttp2_session *h2;
174 /* The easy handle used in the current filter call, cleared at return */
175 struct cf_call_data call_data;
176
177 struct bufq inbufq; /* network receive buffer */
178 struct bufq outbufq; /* network send buffer */
179
180 struct tunnel_stream tunnel; /* our tunnel CONNECT stream */
181 int32_t goaway_error;
182 int32_t last_stream_id;
183 BIT(conn_closed);
184 BIT(rcvd_goaway);
185 BIT(sent_goaway);
186 BIT(nw_out_blocked);
187 };
188
189 /* How to access `call_data` from a cf_h2 filter */
190 #undef CF_CTX_CALL_DATA
191 #define CF_CTX_CALL_DATA(cf) \
192 ((struct cf_h2_proxy_ctx *)(cf)->ctx)->call_data
193
cf_h2_proxy_ctx_clear(struct cf_h2_proxy_ctx * ctx)194 static void cf_h2_proxy_ctx_clear(struct cf_h2_proxy_ctx *ctx)
195 {
196 struct cf_call_data save = ctx->call_data;
197
198 if(ctx->h2) {
199 nghttp2_session_del(ctx->h2);
200 }
201 Curl_bufq_free(&ctx->inbufq);
202 Curl_bufq_free(&ctx->outbufq);
203 tunnel_stream_clear(&ctx->tunnel);
204 memset(ctx, 0, sizeof(*ctx));
205 ctx->call_data = save;
206 }
207
cf_h2_proxy_ctx_free(struct cf_h2_proxy_ctx * ctx)208 static void cf_h2_proxy_ctx_free(struct cf_h2_proxy_ctx *ctx)
209 {
210 if(ctx) {
211 cf_h2_proxy_ctx_clear(ctx);
212 free(ctx);
213 }
214 }
215
drain_tunnel(struct Curl_cfilter * cf,struct Curl_easy * data,struct tunnel_stream * tunnel)216 static void drain_tunnel(struct Curl_cfilter *cf,
217 struct Curl_easy *data,
218 struct tunnel_stream *tunnel)
219 {
220 struct cf_h2_proxy_ctx *ctx = cf->ctx;
221 unsigned char bits;
222
223 (void)cf;
224 bits = CURL_CSELECT_IN;
225 if(!tunnel->closed && !tunnel->reset &&
226 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf))
227 bits |= CURL_CSELECT_OUT;
228 if(data->state.select_bits != bits) {
229 CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x",
230 tunnel->stream_id, bits);
231 data->state.select_bits = bits;
232 Curl_expire(data, 0, EXPIRE_RUN_NOW);
233 }
234 }
235
proxy_nw_in_reader(void * reader_ctx,unsigned char * buf,size_t buflen,CURLcode * err)236 static ssize_t proxy_nw_in_reader(void *reader_ctx,
237 unsigned char *buf, size_t buflen,
238 CURLcode *err)
239 {
240 struct Curl_cfilter *cf = reader_ctx;
241 ssize_t nread;
242
243 if(cf) {
244 struct Curl_easy *data = CF_DATA_CURRENT(cf);
245 nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, buflen, err);
246 CURL_TRC_CF(data, cf, "[0] nw_in_reader(len=%zu) -> %zd, %d",
247 buflen, nread, *err);
248 }
249 else {
250 nread = 0;
251 }
252 return nread;
253 }
254
proxy_h2_nw_out_writer(void * writer_ctx,const unsigned char * buf,size_t buflen,CURLcode * err)255 static ssize_t proxy_h2_nw_out_writer(void *writer_ctx,
256 const unsigned char *buf, size_t buflen,
257 CURLcode *err)
258 {
259 struct Curl_cfilter *cf = writer_ctx;
260 ssize_t nwritten;
261
262 if(cf) {
263 struct Curl_easy *data = CF_DATA_CURRENT(cf);
264 nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen,
265 FALSE, err);
266 CURL_TRC_CF(data, cf, "[0] nw_out_writer(len=%zu) -> %zd, %d",
267 buflen, nwritten, *err);
268 }
269 else {
270 nwritten = 0;
271 }
272 return nwritten;
273 }
274
proxy_h2_client_new(struct Curl_cfilter * cf,nghttp2_session_callbacks * cbs)275 static int proxy_h2_client_new(struct Curl_cfilter *cf,
276 nghttp2_session_callbacks *cbs)
277 {
278 struct cf_h2_proxy_ctx *ctx = cf->ctx;
279 nghttp2_option *o;
280 nghttp2_mem mem = {NULL, Curl_nghttp2_malloc, Curl_nghttp2_free,
281 Curl_nghttp2_calloc, Curl_nghttp2_realloc};
282
283 int rc = nghttp2_option_new(&o);
284 if(rc)
285 return rc;
286 /* We handle window updates ourself to enforce buffer limits */
287 nghttp2_option_set_no_auto_window_update(o, 1);
288 #if NGHTTP2_VERSION_NUM >= 0x013200
289 /* with 1.50.0 */
290 /* turn off RFC 9113 leading and trailing white spaces validation against
291 HTTP field value. */
292 nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1);
293 #endif
294 rc = nghttp2_session_client_new3(&ctx->h2, cbs, cf, o, &mem);
295 nghttp2_option_del(o);
296 return rc;
297 }
298
299 static ssize_t on_session_send(nghttp2_session *h2,
300 const uint8_t *buf, size_t blen,
301 int flags, void *userp);
302 static int proxy_h2_on_frame_recv(nghttp2_session *session,
303 const nghttp2_frame *frame,
304 void *userp);
305 #ifndef CURL_DISABLE_VERBOSE_STRINGS
306 static int proxy_h2_on_frame_send(nghttp2_session *session,
307 const nghttp2_frame *frame,
308 void *userp);
309 #endif
310 static int proxy_h2_on_stream_close(nghttp2_session *session,
311 int32_t stream_id,
312 uint32_t error_code, void *userp);
313 static int proxy_h2_on_header(nghttp2_session *session,
314 const nghttp2_frame *frame,
315 const uint8_t *name, size_t namelen,
316 const uint8_t *value, size_t valuelen,
317 uint8_t flags,
318 void *userp);
319 static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags,
320 int32_t stream_id,
321 const uint8_t *mem, size_t len, void *userp);
322
323 /*
324 * Initialize the cfilter context
325 */
cf_h2_proxy_ctx_init(struct Curl_cfilter * cf,struct Curl_easy * data)326 static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf,
327 struct Curl_easy *data)
328 {
329 struct cf_h2_proxy_ctx *ctx = cf->ctx;
330 CURLcode result = CURLE_OUT_OF_MEMORY;
331 nghttp2_session_callbacks *cbs = NULL;
332 int rc;
333
334 DEBUGASSERT(!ctx->h2);
335 memset(&ctx->tunnel, 0, sizeof(ctx->tunnel));
336
337 Curl_bufq_init(&ctx->inbufq, PROXY_H2_CHUNK_SIZE, PROXY_H2_NW_RECV_CHUNKS);
338 Curl_bufq_init(&ctx->outbufq, PROXY_H2_CHUNK_SIZE, PROXY_H2_NW_SEND_CHUNKS);
339
340 if(tunnel_stream_init(cf, &ctx->tunnel))
341 goto out;
342
343 rc = nghttp2_session_callbacks_new(&cbs);
344 if(rc) {
345 failf(data, "Couldn't initialize nghttp2 callbacks");
346 goto out;
347 }
348
349 nghttp2_session_callbacks_set_send_callback(cbs, on_session_send);
350 nghttp2_session_callbacks_set_on_frame_recv_callback(
351 cbs, proxy_h2_on_frame_recv);
352 #ifndef CURL_DISABLE_VERBOSE_STRINGS
353 nghttp2_session_callbacks_set_on_frame_send_callback(cbs,
354 proxy_h2_on_frame_send);
355 #endif
356 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
357 cbs, tunnel_recv_callback);
358 nghttp2_session_callbacks_set_on_stream_close_callback(
359 cbs, proxy_h2_on_stream_close);
360 nghttp2_session_callbacks_set_on_header_callback(cbs, proxy_h2_on_header);
361
362 /* The nghttp2 session is not yet setup, do it */
363 rc = proxy_h2_client_new(cf, cbs);
364 if(rc) {
365 failf(data, "Couldn't initialize nghttp2");
366 goto out;
367 }
368
369 {
370 nghttp2_settings_entry iv[3];
371
372 iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
373 iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
374 iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
375 iv[1].value = H2_TUNNEL_WINDOW_SIZE;
376 iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
377 iv[2].value = 0;
378 rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, iv, 3);
379 if(rc) {
380 failf(data, "nghttp2_submit_settings() failed: %s(%d)",
381 nghttp2_strerror(rc), rc);
382 result = CURLE_HTTP2;
383 goto out;
384 }
385 }
386
387 rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0,
388 PROXY_HTTP2_HUGE_WINDOW_SIZE);
389 if(rc) {
390 failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
391 nghttp2_strerror(rc), rc);
392 result = CURLE_HTTP2;
393 goto out;
394 }
395
396
397 /* all set, traffic will be send on connect */
398 result = CURLE_OK;
399
400 out:
401 if(cbs)
402 nghttp2_session_callbacks_del(cbs);
403 CURL_TRC_CF(data, cf, "[0] init proxy ctx -> %d", result);
404 return result;
405 }
406
proxy_h2_should_close_session(struct cf_h2_proxy_ctx * ctx)407 static int proxy_h2_should_close_session(struct cf_h2_proxy_ctx *ctx)
408 {
409 return !nghttp2_session_want_read(ctx->h2) &&
410 !nghttp2_session_want_write(ctx->h2);
411 }
412
proxy_h2_nw_out_flush(struct Curl_cfilter * cf,struct Curl_easy * data)413 static CURLcode proxy_h2_nw_out_flush(struct Curl_cfilter *cf,
414 struct Curl_easy *data)
415 {
416 struct cf_h2_proxy_ctx *ctx = cf->ctx;
417 ssize_t nwritten;
418 CURLcode result;
419
420 (void)data;
421 if(Curl_bufq_is_empty(&ctx->outbufq))
422 return CURLE_OK;
423
424 nwritten = Curl_bufq_pass(&ctx->outbufq, proxy_h2_nw_out_writer, cf,
425 &result);
426 if(nwritten < 0) {
427 if(result == CURLE_AGAIN) {
428 CURL_TRC_CF(data, cf, "[0] flush nw send buffer(%zu) -> EAGAIN",
429 Curl_bufq_len(&ctx->outbufq));
430 ctx->nw_out_blocked = 1;
431 }
432 return result;
433 }
434 CURL_TRC_CF(data, cf, "[0] nw send buffer flushed");
435 return Curl_bufq_is_empty(&ctx->outbufq) ? CURLE_OK : CURLE_AGAIN;
436 }
437
438 /*
439 * Processes pending input left in network input buffer.
440 * This function returns 0 if it succeeds, or -1 and error code will
441 * be assigned to *err.
442 */
proxy_h2_process_pending_input(struct Curl_cfilter * cf,struct Curl_easy * data,CURLcode * err)443 static int proxy_h2_process_pending_input(struct Curl_cfilter *cf,
444 struct Curl_easy *data,
445 CURLcode *err)
446 {
447 struct cf_h2_proxy_ctx *ctx = cf->ctx;
448 const unsigned char *buf;
449 size_t blen;
450 ssize_t rv;
451
452 while(Curl_bufq_peek(&ctx->inbufq, &buf, &blen)) {
453
454 rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen);
455 CURL_TRC_CF(data, cf, "[0] %zu bytes to nghttp2 -> %zd", blen, rv);
456 if(rv < 0) {
457 failf(data,
458 "process_pending_input: nghttp2_session_mem_recv() returned "
459 "%zd:%s", rv, nghttp2_strerror((int)rv));
460 *err = CURLE_RECV_ERROR;
461 return -1;
462 }
463 Curl_bufq_skip(&ctx->inbufq, (size_t)rv);
464 if(Curl_bufq_is_empty(&ctx->inbufq)) {
465 CURL_TRC_CF(data, cf, "[0] all data in connection buffer processed");
466 break;
467 }
468 else {
469 CURL_TRC_CF(data, cf, "[0] process_pending_input: %zu bytes left "
470 "in connection buffer", Curl_bufq_len(&ctx->inbufq));
471 }
472 }
473
474 return 0;
475 }
476
proxy_h2_progress_ingress(struct Curl_cfilter * cf,struct Curl_easy * data)477 static CURLcode proxy_h2_progress_ingress(struct Curl_cfilter *cf,
478 struct Curl_easy *data)
479 {
480 struct cf_h2_proxy_ctx *ctx = cf->ctx;
481 CURLcode result = CURLE_OK;
482 ssize_t nread;
483
484 /* Process network input buffer fist */
485 if(!Curl_bufq_is_empty(&ctx->inbufq)) {
486 CURL_TRC_CF(data, cf, "[0] process %zu bytes in connection buffer",
487 Curl_bufq_len(&ctx->inbufq));
488 if(proxy_h2_process_pending_input(cf, data, &result) < 0)
489 return result;
490 }
491
492 /* Receive data from the "lower" filters, e.g. network until
493 * it is time to stop or we have enough data for this stream */
494 while(!ctx->conn_closed && /* not closed the connection */
495 !ctx->tunnel.closed && /* nor the tunnel */
496 Curl_bufq_is_empty(&ctx->inbufq) && /* and we consumed our input */
497 !Curl_bufq_is_full(&ctx->tunnel.recvbuf)) {
498
499 nread = Curl_bufq_slurp(&ctx->inbufq, proxy_nw_in_reader, cf, &result);
500 CURL_TRC_CF(data, cf, "[0] read %zu bytes nw data -> %zd, %d",
501 Curl_bufq_len(&ctx->inbufq), nread, result);
502 if(nread < 0) {
503 if(result != CURLE_AGAIN) {
504 failf(data, "Failed receiving HTTP2 data");
505 return result;
506 }
507 break;
508 }
509 else if(nread == 0) {
510 ctx->conn_closed = TRUE;
511 break;
512 }
513
514 if(proxy_h2_process_pending_input(cf, data, &result))
515 return result;
516 }
517
518 if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
519 connclose(cf->conn, "GOAWAY received");
520 }
521
522 return CURLE_OK;
523 }
524
proxy_h2_progress_egress(struct Curl_cfilter * cf,struct Curl_easy * data)525 static CURLcode proxy_h2_progress_egress(struct Curl_cfilter *cf,
526 struct Curl_easy *data)
527 {
528 struct cf_h2_proxy_ctx *ctx = cf->ctx;
529 int rv = 0;
530
531 ctx->nw_out_blocked = 0;
532 while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2))
533 rv = nghttp2_session_send(ctx->h2);
534
535 if(nghttp2_is_fatal(rv)) {
536 CURL_TRC_CF(data, cf, "[0] nghttp2_session_send error (%s)%d",
537 nghttp2_strerror(rv), rv);
538 return CURLE_SEND_ERROR;
539 }
540 return proxy_h2_nw_out_flush(cf, data);
541 }
542
on_session_send(nghttp2_session * h2,const uint8_t * buf,size_t blen,int flags,void * userp)543 static ssize_t on_session_send(nghttp2_session *h2,
544 const uint8_t *buf, size_t blen, int flags,
545 void *userp)
546 {
547 struct Curl_cfilter *cf = userp;
548 struct cf_h2_proxy_ctx *ctx = cf->ctx;
549 struct Curl_easy *data = CF_DATA_CURRENT(cf);
550 ssize_t nwritten;
551 CURLcode result = CURLE_OK;
552
553 (void)h2;
554 (void)flags;
555 DEBUGASSERT(data);
556
557 nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen,
558 proxy_h2_nw_out_writer, cf, &result);
559 if(nwritten < 0) {
560 if(result == CURLE_AGAIN) {
561 return NGHTTP2_ERR_WOULDBLOCK;
562 }
563 failf(data, "Failed sending HTTP2 data");
564 return NGHTTP2_ERR_CALLBACK_FAILURE;
565 }
566
567 if(!nwritten)
568 return NGHTTP2_ERR_WOULDBLOCK;
569
570 return nwritten;
571 }
572
573 #ifndef CURL_DISABLE_VERBOSE_STRINGS
proxy_h2_fr_print(const nghttp2_frame * frame,char * buffer,size_t blen)574 static int proxy_h2_fr_print(const nghttp2_frame *frame,
575 char *buffer, size_t blen)
576 {
577 switch(frame->hd.type) {
578 case NGHTTP2_DATA: {
579 return msnprintf(buffer, blen,
580 "FRAME[DATA, len=%d, eos=%d, padlen=%d]",
581 (int)frame->hd.length,
582 !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM),
583 (int)frame->data.padlen);
584 }
585 case NGHTTP2_HEADERS: {
586 return msnprintf(buffer, blen,
587 "FRAME[HEADERS, len=%d, hend=%d, eos=%d]",
588 (int)frame->hd.length,
589 !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
590 !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM));
591 }
592 case NGHTTP2_PRIORITY: {
593 return msnprintf(buffer, blen,
594 "FRAME[PRIORITY, len=%d, flags=%d]",
595 (int)frame->hd.length, frame->hd.flags);
596 }
597 case NGHTTP2_RST_STREAM: {
598 return msnprintf(buffer, blen,
599 "FRAME[RST_STREAM, len=%d, flags=%d, error=%u]",
600 (int)frame->hd.length, frame->hd.flags,
601 frame->rst_stream.error_code);
602 }
603 case NGHTTP2_SETTINGS: {
604 if(frame->hd.flags & NGHTTP2_FLAG_ACK) {
605 return msnprintf(buffer, blen, "FRAME[SETTINGS, ack=1]");
606 }
607 return msnprintf(buffer, blen,
608 "FRAME[SETTINGS, len=%d]", (int)frame->hd.length);
609 }
610 case NGHTTP2_PUSH_PROMISE:
611 return msnprintf(buffer, blen,
612 "FRAME[PUSH_PROMISE, len=%d, hend=%d]",
613 (int)frame->hd.length,
614 !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS));
615 case NGHTTP2_PING:
616 return msnprintf(buffer, blen,
617 "FRAME[PING, len=%d, ack=%d]",
618 (int)frame->hd.length,
619 frame->hd.flags & NGHTTP2_FLAG_ACK);
620 case NGHTTP2_GOAWAY: {
621 char scratch[128];
622 size_t s_len = sizeof(scratch)/sizeof(scratch[0]);
623 size_t len = (frame->goaway.opaque_data_len < s_len) ?
624 frame->goaway.opaque_data_len : s_len-1;
625 if(len)
626 memcpy(scratch, frame->goaway.opaque_data, len);
627 scratch[len] = '\0';
628 return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', "
629 "last_stream=%d]", frame->goaway.error_code,
630 scratch, frame->goaway.last_stream_id);
631 }
632 case NGHTTP2_WINDOW_UPDATE: {
633 return msnprintf(buffer, blen,
634 "FRAME[WINDOW_UPDATE, incr=%d]",
635 frame->window_update.window_size_increment);
636 }
637 default:
638 return msnprintf(buffer, blen, "FRAME[%d, len=%d, flags=%d]",
639 frame->hd.type, (int)frame->hd.length,
640 frame->hd.flags);
641 }
642 }
643
proxy_h2_on_frame_send(nghttp2_session * session,const nghttp2_frame * frame,void * userp)644 static int proxy_h2_on_frame_send(nghttp2_session *session,
645 const nghttp2_frame *frame,
646 void *userp)
647 {
648 struct Curl_cfilter *cf = userp;
649 struct Curl_easy *data = CF_DATA_CURRENT(cf);
650
651 (void)session;
652 DEBUGASSERT(data);
653 if(data && Curl_trc_cf_is_verbose(cf, data)) {
654 char buffer[256];
655 int len;
656 len = proxy_h2_fr_print(frame, buffer, sizeof(buffer)-1);
657 buffer[len] = 0;
658 CURL_TRC_CF(data, cf, "[%d] -> %s", frame->hd.stream_id, buffer);
659 }
660 return 0;
661 }
662 #endif /* !CURL_DISABLE_VERBOSE_STRINGS */
663
proxy_h2_on_frame_recv(nghttp2_session * session,const nghttp2_frame * frame,void * userp)664 static int proxy_h2_on_frame_recv(nghttp2_session *session,
665 const nghttp2_frame *frame,
666 void *userp)
667 {
668 struct Curl_cfilter *cf = userp;
669 struct cf_h2_proxy_ctx *ctx = cf->ctx;
670 struct Curl_easy *data = CF_DATA_CURRENT(cf);
671 int32_t stream_id = frame->hd.stream_id;
672
673 (void)session;
674 DEBUGASSERT(data);
675 #ifndef CURL_DISABLE_VERBOSE_STRINGS
676 if(Curl_trc_cf_is_verbose(cf, data)) {
677 char buffer[256];
678 int len;
679 len = proxy_h2_fr_print(frame, buffer, sizeof(buffer)-1);
680 buffer[len] = 0;
681 CURL_TRC_CF(data, cf, "[%d] <- %s",frame->hd.stream_id, buffer);
682 }
683 #endif /* !CURL_DISABLE_VERBOSE_STRINGS */
684
685 if(!stream_id) {
686 /* stream ID zero is for connection-oriented stuff */
687 DEBUGASSERT(data);
688 switch(frame->hd.type) {
689 case NGHTTP2_SETTINGS:
690 /* Since the initial stream window is 64K, a request might be on HOLD,
691 * due to exhaustion. The (initial) SETTINGS may announce a much larger
692 * window and *assume* that we treat this like a WINDOW_UPDATE. Some
693 * servers send an explicit WINDOW_UPDATE, but not all seem to do that.
694 * To be safe, we UNHOLD a stream in order not to stall. */
695 if(CURL_WANT_SEND(data)) {
696 drain_tunnel(cf, data, &ctx->tunnel);
697 }
698 break;
699 case NGHTTP2_GOAWAY:
700 ctx->rcvd_goaway = TRUE;
701 break;
702 default:
703 break;
704 }
705 return 0;
706 }
707
708 if(stream_id != ctx->tunnel.stream_id) {
709 CURL_TRC_CF(data, cf, "[%d] rcvd FRAME not for tunnel", stream_id);
710 return NGHTTP2_ERR_CALLBACK_FAILURE;
711 }
712
713 switch(frame->hd.type) {
714 case NGHTTP2_HEADERS:
715 /* nghttp2 guarantees that :status is received, and we store it to
716 stream->status_code. Fuzzing has proven this can still be reached
717 without status code having been set. */
718 if(!ctx->tunnel.resp)
719 return NGHTTP2_ERR_CALLBACK_FAILURE;
720 /* Only final status code signals the end of header */
721 CURL_TRC_CF(data, cf, "[%d] got http status: %d",
722 stream_id, ctx->tunnel.resp->status);
723 if(!ctx->tunnel.has_final_response) {
724 if(ctx->tunnel.resp->status / 100 != 1) {
725 ctx->tunnel.has_final_response = TRUE;
726 }
727 }
728 break;
729 case NGHTTP2_WINDOW_UPDATE:
730 if(CURL_WANT_SEND(data)) {
731 drain_tunnel(cf, data, &ctx->tunnel);
732 }
733 break;
734 default:
735 break;
736 }
737 return 0;
738 }
739
proxy_h2_on_header(nghttp2_session * session,const nghttp2_frame * frame,const uint8_t * name,size_t namelen,const uint8_t * value,size_t valuelen,uint8_t flags,void * userp)740 static int proxy_h2_on_header(nghttp2_session *session,
741 const nghttp2_frame *frame,
742 const uint8_t *name, size_t namelen,
743 const uint8_t *value, size_t valuelen,
744 uint8_t flags,
745 void *userp)
746 {
747 struct Curl_cfilter *cf = userp;
748 struct cf_h2_proxy_ctx *ctx = cf->ctx;
749 struct Curl_easy *data = CF_DATA_CURRENT(cf);
750 int32_t stream_id = frame->hd.stream_id;
751 CURLcode result;
752
753 (void)flags;
754 (void)data;
755 (void)session;
756 DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
757 if(stream_id != ctx->tunnel.stream_id) {
758 CURL_TRC_CF(data, cf, "[%d] header for non-tunnel stream: "
759 "%.*s: %.*s", stream_id,
760 (int)namelen, name, (int)valuelen, value);
761 return NGHTTP2_ERR_CALLBACK_FAILURE;
762 }
763
764 if(frame->hd.type == NGHTTP2_PUSH_PROMISE)
765 return NGHTTP2_ERR_CALLBACK_FAILURE;
766
767 if(ctx->tunnel.has_final_response) {
768 /* we do not do anything with trailers for tunnel streams */
769 return 0;
770 }
771
772 if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 &&
773 memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) {
774 int http_status;
775 struct http_resp *resp;
776
777 /* status: always comes first, we might get more than one response,
778 * link the previous ones for keepers */
779 result = Curl_http_decode_status(&http_status,
780 (const char *)value, valuelen);
781 if(result)
782 return NGHTTP2_ERR_CALLBACK_FAILURE;
783 result = Curl_http_resp_make(&resp, http_status, NULL);
784 if(result)
785 return NGHTTP2_ERR_CALLBACK_FAILURE;
786 resp->prev = ctx->tunnel.resp;
787 ctx->tunnel.resp = resp;
788 CURL_TRC_CF(data, cf, "[%d] status: HTTP/2 %03d",
789 stream_id, ctx->tunnel.resp->status);
790 return 0;
791 }
792
793 if(!ctx->tunnel.resp)
794 return NGHTTP2_ERR_CALLBACK_FAILURE;
795
796 result = Curl_dynhds_add(&ctx->tunnel.resp->headers,
797 (const char *)name, namelen,
798 (const char *)value, valuelen);
799 if(result)
800 return NGHTTP2_ERR_CALLBACK_FAILURE;
801
802 CURL_TRC_CF(data, cf, "[%d] header: %.*s: %.*s",
803 stream_id, (int)namelen, name, (int)valuelen, value);
804
805 return 0; /* 0 is successful */
806 }
807
tunnel_send_callback(nghttp2_session * session,int32_t stream_id,uint8_t * buf,size_t length,uint32_t * data_flags,nghttp2_data_source * source,void * userp)808 static ssize_t tunnel_send_callback(nghttp2_session *session,
809 int32_t stream_id,
810 uint8_t *buf, size_t length,
811 uint32_t *data_flags,
812 nghttp2_data_source *source,
813 void *userp)
814 {
815 struct Curl_cfilter *cf = userp;
816 struct cf_h2_proxy_ctx *ctx = cf->ctx;
817 struct Curl_easy *data = CF_DATA_CURRENT(cf);
818 struct tunnel_stream *ts;
819 CURLcode result;
820 ssize_t nread;
821
822 (void)source;
823 (void)data;
824 (void)ctx;
825
826 if(!stream_id)
827 return NGHTTP2_ERR_INVALID_ARGUMENT;
828
829 ts = nghttp2_session_get_stream_user_data(session, stream_id);
830 if(!ts)
831 return NGHTTP2_ERR_CALLBACK_FAILURE;
832 DEBUGASSERT(ts == &ctx->tunnel);
833
834 nread = Curl_bufq_read(&ts->sendbuf, buf, length, &result);
835 if(nread < 0) {
836 if(result != CURLE_AGAIN)
837 return NGHTTP2_ERR_CALLBACK_FAILURE;
838 return NGHTTP2_ERR_DEFERRED;
839 }
840 if(ts->closed && Curl_bufq_is_empty(&ts->sendbuf))
841 *data_flags = NGHTTP2_DATA_FLAG_EOF;
842
843 CURL_TRC_CF(data, cf, "[%d] tunnel_send_callback -> %zd",
844 ts->stream_id, nread);
845 return nread;
846 }
847
tunnel_recv_callback(nghttp2_session * session,uint8_t flags,int32_t stream_id,const uint8_t * mem,size_t len,void * userp)848 static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags,
849 int32_t stream_id,
850 const uint8_t *mem, size_t len, void *userp)
851 {
852 struct Curl_cfilter *cf = userp;
853 struct cf_h2_proxy_ctx *ctx = cf->ctx;
854 ssize_t nwritten;
855 CURLcode result;
856
857 (void)flags;
858 (void)session;
859 DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
860
861 if(stream_id != ctx->tunnel.stream_id)
862 return NGHTTP2_ERR_CALLBACK_FAILURE;
863
864 nwritten = Curl_bufq_write(&ctx->tunnel.recvbuf, mem, len, &result);
865 if(nwritten < 0) {
866 if(result != CURLE_AGAIN)
867 return NGHTTP2_ERR_CALLBACK_FAILURE;
868 nwritten = 0;
869 }
870 DEBUGASSERT((size_t)nwritten == len);
871 return 0;
872 }
873
proxy_h2_on_stream_close(nghttp2_session * session,int32_t stream_id,uint32_t error_code,void * userp)874 static int proxy_h2_on_stream_close(nghttp2_session *session,
875 int32_t stream_id,
876 uint32_t error_code, void *userp)
877 {
878 struct Curl_cfilter *cf = userp;
879 struct cf_h2_proxy_ctx *ctx = cf->ctx;
880 struct Curl_easy *data = CF_DATA_CURRENT(cf);
881
882 (void)session;
883 (void)data;
884
885 if(stream_id != ctx->tunnel.stream_id)
886 return 0;
887
888 CURL_TRC_CF(data, cf, "[%d] proxy_h2_on_stream_close, %s (err %d)",
889 stream_id, nghttp2_http2_strerror(error_code), error_code);
890 ctx->tunnel.closed = TRUE;
891 ctx->tunnel.error = error_code;
892
893 return 0;
894 }
895
proxy_h2_submit(int32_t * pstream_id,struct Curl_cfilter * cf,struct Curl_easy * data,nghttp2_session * h2,struct httpreq * req,const nghttp2_priority_spec * pri_spec,void * stream_user_data,nghttp2_data_source_read_callback read_callback,void * read_ctx)896 static CURLcode proxy_h2_submit(int32_t *pstream_id,
897 struct Curl_cfilter *cf,
898 struct Curl_easy *data,
899 nghttp2_session *h2,
900 struct httpreq *req,
901 const nghttp2_priority_spec *pri_spec,
902 void *stream_user_data,
903 nghttp2_data_source_read_callback read_callback,
904 void *read_ctx)
905 {
906 struct dynhds h2_headers;
907 nghttp2_nv *nva = NULL;
908 int32_t stream_id = -1;
909 size_t nheader;
910 CURLcode result;
911
912 (void)cf;
913 Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
914 result = Curl_http_req_to_h2(&h2_headers, req, data);
915 if(result)
916 goto out;
917
918 nva = Curl_dynhds_to_nva(&h2_headers, &nheader);
919 if(!nva) {
920 result = CURLE_OUT_OF_MEMORY;
921 goto out;
922 }
923
924 if(read_callback) {
925 nghttp2_data_provider data_prd;
926
927 data_prd.read_callback = read_callback;
928 data_prd.source.ptr = read_ctx;
929 stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
930 &data_prd, stream_user_data);
931 }
932 else {
933 stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
934 NULL, stream_user_data);
935 }
936
937 if(stream_id < 0) {
938 failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
939 nghttp2_strerror(stream_id), stream_id);
940 result = CURLE_SEND_ERROR;
941 goto out;
942 }
943 result = CURLE_OK;
944
945 out:
946 free(nva);
947 Curl_dynhds_free(&h2_headers);
948 *pstream_id = stream_id;
949 return result;
950 }
951
submit_CONNECT(struct Curl_cfilter * cf,struct Curl_easy * data,struct tunnel_stream * ts)952 static CURLcode submit_CONNECT(struct Curl_cfilter *cf,
953 struct Curl_easy *data,
954 struct tunnel_stream *ts)
955 {
956 struct cf_h2_proxy_ctx *ctx = cf->ctx;
957 CURLcode result;
958 struct httpreq *req = NULL;
959
960 result = Curl_http_proxy_create_CONNECT(&req, cf, data, 2);
961 if(result)
962 goto out;
963 result = Curl_creader_set_null(data);
964 if(result)
965 goto out;
966
967 infof(data, "Establish HTTP/2 proxy tunnel to %s", req->authority);
968
969 result = proxy_h2_submit(&ts->stream_id, cf, data, ctx->h2, req,
970 NULL, ts, tunnel_send_callback, cf);
971 if(result) {
972 CURL_TRC_CF(data, cf, "[%d] send, nghttp2_submit_request error: %s",
973 ts->stream_id, nghttp2_strerror(ts->stream_id));
974 }
975
976 out:
977 if(req)
978 Curl_http_req_free(req);
979 if(result)
980 failf(data, "Failed sending CONNECT to proxy");
981 return result;
982 }
983
inspect_response(struct Curl_cfilter * cf,struct Curl_easy * data,struct tunnel_stream * ts)984 static CURLcode inspect_response(struct Curl_cfilter *cf,
985 struct Curl_easy *data,
986 struct tunnel_stream *ts)
987 {
988 CURLcode result = CURLE_OK;
989 struct dynhds_entry *auth_reply = NULL;
990 (void)cf;
991
992 DEBUGASSERT(ts->resp);
993 if(ts->resp->status/100 == 2) {
994 infof(data, "CONNECT tunnel established, response %d", ts->resp->status);
995 h2_tunnel_go_state(cf, ts, H2_TUNNEL_ESTABLISHED, data);
996 return CURLE_OK;
997 }
998
999 if(ts->resp->status == 401) {
1000 auth_reply = Curl_dynhds_cget(&ts->resp->headers, "WWW-Authenticate");
1001 }
1002 else if(ts->resp->status == 407) {
1003 auth_reply = Curl_dynhds_cget(&ts->resp->headers, "Proxy-Authenticate");
1004 }
1005
1006 if(auth_reply) {
1007 CURL_TRC_CF(data, cf, "[0] CONNECT: fwd auth header '%s'",
1008 auth_reply->value);
1009 result = Curl_http_input_auth(data, ts->resp->status == 407,
1010 auth_reply->value);
1011 if(result)
1012 return result;
1013 if(data->req.newurl) {
1014 /* Indicator that we should try again */
1015 Curl_safefree(data->req.newurl);
1016 h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data);
1017 return CURLE_OK;
1018 }
1019 }
1020
1021 /* Seems to have failed */
1022 return CURLE_RECV_ERROR;
1023 }
1024
H2_CONNECT(struct Curl_cfilter * cf,struct Curl_easy * data,struct tunnel_stream * ts)1025 static CURLcode H2_CONNECT(struct Curl_cfilter *cf,
1026 struct Curl_easy *data,
1027 struct tunnel_stream *ts)
1028 {
1029 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1030 CURLcode result = CURLE_OK;
1031
1032 DEBUGASSERT(ts);
1033 DEBUGASSERT(ts->authority);
1034 do {
1035 switch(ts->state) {
1036 case H2_TUNNEL_INIT:
1037 /* Prepare the CONNECT request and make a first attempt to send. */
1038 CURL_TRC_CF(data, cf, "[0] CONNECT start for %s", ts->authority);
1039 result = submit_CONNECT(cf, data, ts);
1040 if(result)
1041 goto out;
1042 h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data);
1043 FALLTHROUGH();
1044
1045 case H2_TUNNEL_CONNECT:
1046 /* see that the request is completely sent */
1047 result = proxy_h2_progress_ingress(cf, data);
1048 if(!result)
1049 result = proxy_h2_progress_egress(cf, data);
1050 if(result && result != CURLE_AGAIN) {
1051 h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
1052 break;
1053 }
1054
1055 if(ts->has_final_response) {
1056 h2_tunnel_go_state(cf, ts, H2_TUNNEL_RESPONSE, data);
1057 }
1058 else {
1059 result = CURLE_OK;
1060 goto out;
1061 }
1062 FALLTHROUGH();
1063
1064 case H2_TUNNEL_RESPONSE:
1065 DEBUGASSERT(ts->has_final_response);
1066 result = inspect_response(cf, data, ts);
1067 if(result)
1068 goto out;
1069 break;
1070
1071 case H2_TUNNEL_ESTABLISHED:
1072 return CURLE_OK;
1073
1074 case H2_TUNNEL_FAILED:
1075 return CURLE_RECV_ERROR;
1076
1077 default:
1078 break;
1079 }
1080
1081 } while(ts->state == H2_TUNNEL_INIT);
1082
1083 out:
1084 if((result && (result != CURLE_AGAIN)) || ctx->tunnel.closed)
1085 h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
1086 return result;
1087 }
1088
cf_h2_proxy_connect(struct Curl_cfilter * cf,struct Curl_easy * data,bool blocking,bool * done)1089 static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf,
1090 struct Curl_easy *data,
1091 bool blocking, bool *done)
1092 {
1093 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1094 CURLcode result = CURLE_OK;
1095 struct cf_call_data save;
1096 timediff_t check;
1097 struct tunnel_stream *ts = &ctx->tunnel;
1098
1099 if(cf->connected) {
1100 *done = TRUE;
1101 return CURLE_OK;
1102 }
1103
1104 /* Connect the lower filters first */
1105 if(!cf->next->connected) {
1106 result = Curl_conn_cf_connect(cf->next, data, blocking, done);
1107 if(result || !*done)
1108 return result;
1109 }
1110
1111 *done = FALSE;
1112
1113 CF_DATA_SAVE(save, cf, data);
1114 if(!ctx->h2) {
1115 result = cf_h2_proxy_ctx_init(cf, data);
1116 if(result)
1117 goto out;
1118 }
1119 DEBUGASSERT(ts->authority);
1120
1121 check = Curl_timeleft(data, NULL, TRUE);
1122 if(check <= 0) {
1123 failf(data, "Proxy CONNECT aborted due to timeout");
1124 result = CURLE_OPERATION_TIMEDOUT;
1125 goto out;
1126 }
1127
1128 /* for the secondary socket (FTP), use the "connect to host"
1129 * but ignore the "connect to port" (use the secondary port)
1130 */
1131 result = H2_CONNECT(cf, data, ts);
1132
1133 out:
1134 *done = (result == CURLE_OK) && (ts->state == H2_TUNNEL_ESTABLISHED);
1135 if(*done) {
1136 cf->connected = TRUE;
1137 /* The real request will follow the CONNECT, reset request partially */
1138 Curl_req_soft_reset(&data->req, data);
1139 Curl_client_reset(data);
1140 }
1141 CF_DATA_RESTORE(cf, save);
1142 return result;
1143 }
1144
cf_h2_proxy_close(struct Curl_cfilter * cf,struct Curl_easy * data)1145 static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data)
1146 {
1147 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1148
1149 if(ctx) {
1150 struct cf_call_data save;
1151
1152 CF_DATA_SAVE(save, cf, data);
1153 cf_h2_proxy_ctx_clear(ctx);
1154 CF_DATA_RESTORE(cf, save);
1155 }
1156 if(cf->next)
1157 cf->next->cft->do_close(cf->next, data);
1158 }
1159
cf_h2_proxy_destroy(struct Curl_cfilter * cf,struct Curl_easy * data)1160 static void cf_h2_proxy_destroy(struct Curl_cfilter *cf,
1161 struct Curl_easy *data)
1162 {
1163 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1164
1165 (void)data;
1166 if(ctx) {
1167 cf_h2_proxy_ctx_free(ctx);
1168 cf->ctx = NULL;
1169 }
1170 }
1171
cf_h2_proxy_shutdown(struct Curl_cfilter * cf,struct Curl_easy * data,bool * done)1172 static CURLcode cf_h2_proxy_shutdown(struct Curl_cfilter *cf,
1173 struct Curl_easy *data, bool *done)
1174 {
1175 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1176 struct cf_call_data save;
1177 CURLcode result;
1178 int rv;
1179
1180 if(!cf->connected || !ctx->h2 || cf->shutdown || ctx->conn_closed) {
1181 *done = TRUE;
1182 return CURLE_OK;
1183 }
1184
1185 CF_DATA_SAVE(save, cf, data);
1186
1187 if(!ctx->sent_goaway) {
1188 rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE,
1189 0, 0,
1190 (const uint8_t *)"shutdown",
1191 sizeof("shutdown"));
1192 if(rv) {
1193 failf(data, "nghttp2_submit_goaway() failed: %s(%d)",
1194 nghttp2_strerror(rv), rv);
1195 result = CURLE_SEND_ERROR;
1196 goto out;
1197 }
1198 ctx->sent_goaway = TRUE;
1199 }
1200 /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */
1201 result = CURLE_OK;
1202 if(nghttp2_session_want_write(ctx->h2))
1203 result = proxy_h2_progress_egress(cf, data);
1204 if(!result && nghttp2_session_want_read(ctx->h2))
1205 result = proxy_h2_progress_ingress(cf, data);
1206
1207 *done = (ctx->conn_closed ||
1208 (!result && !nghttp2_session_want_write(ctx->h2) &&
1209 !nghttp2_session_want_read(ctx->h2)));
1210 out:
1211 CF_DATA_RESTORE(cf, save);
1212 cf->shutdown = (result || *done);
1213 return result;
1214 }
1215
cf_h2_proxy_data_pending(struct Curl_cfilter * cf,const struct Curl_easy * data)1216 static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf,
1217 const struct Curl_easy *data)
1218 {
1219 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1220 if((ctx && !Curl_bufq_is_empty(&ctx->inbufq)) ||
1221 (ctx && ctx->tunnel.state == H2_TUNNEL_ESTABLISHED &&
1222 !Curl_bufq_is_empty(&ctx->tunnel.recvbuf)))
1223 return TRUE;
1224 return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
1225 }
1226
cf_h2_proxy_adjust_pollset(struct Curl_cfilter * cf,struct Curl_easy * data,struct easy_pollset * ps)1227 static void cf_h2_proxy_adjust_pollset(struct Curl_cfilter *cf,
1228 struct Curl_easy *data,
1229 struct easy_pollset *ps)
1230 {
1231 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1232 struct cf_call_data save;
1233 curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
1234 bool want_recv, want_send;
1235
1236 if(!cf->connected && ctx->h2) {
1237 want_send = nghttp2_session_want_write(ctx->h2) ||
1238 !Curl_bufq_is_empty(&ctx->outbufq) ||
1239 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf);
1240 want_recv = nghttp2_session_want_read(ctx->h2);
1241 }
1242 else
1243 Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
1244
1245 if(ctx->h2 && (want_recv || want_send)) {
1246 bool c_exhaust, s_exhaust;
1247
1248 CF_DATA_SAVE(save, cf, data);
1249 c_exhaust = !nghttp2_session_get_remote_window_size(ctx->h2);
1250 s_exhaust = ctx->tunnel.stream_id >= 0 &&
1251 !nghttp2_session_get_stream_remote_window_size(
1252 ctx->h2, ctx->tunnel.stream_id);
1253 want_recv = (want_recv || c_exhaust || s_exhaust);
1254 want_send = (!s_exhaust && want_send) ||
1255 (!c_exhaust && nghttp2_session_want_write(ctx->h2)) ||
1256 !Curl_bufq_is_empty(&ctx->outbufq) ||
1257 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf);
1258
1259 Curl_pollset_set(data, ps, sock, want_recv, want_send);
1260 CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d",
1261 want_recv, want_send);
1262 CF_DATA_RESTORE(cf, save);
1263 }
1264 else if(ctx->sent_goaway && !cf->shutdown) {
1265 /* shutdown in progress */
1266 CF_DATA_SAVE(save, cf, data);
1267 want_send = nghttp2_session_want_write(ctx->h2) ||
1268 !Curl_bufq_is_empty(&ctx->outbufq) ||
1269 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf);
1270 want_recv = nghttp2_session_want_read(ctx->h2);
1271 Curl_pollset_set(data, ps, sock, want_recv, want_send);
1272 CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d",
1273 want_recv, want_send);
1274 CF_DATA_RESTORE(cf, save);
1275 }
1276 }
1277
h2_handle_tunnel_close(struct Curl_cfilter * cf,struct Curl_easy * data,CURLcode * err)1278 static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf,
1279 struct Curl_easy *data,
1280 CURLcode *err)
1281 {
1282 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1283 ssize_t rv = 0;
1284
1285 if(ctx->tunnel.error == NGHTTP2_REFUSED_STREAM) {
1286 CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new "
1287 "connection", ctx->tunnel.stream_id);
1288 connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */
1289 *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
1290 return -1;
1291 }
1292 else if(ctx->tunnel.error != NGHTTP2_NO_ERROR) {
1293 failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)",
1294 ctx->tunnel.stream_id, nghttp2_http2_strerror(ctx->tunnel.error),
1295 ctx->tunnel.error);
1296 *err = CURLE_HTTP2_STREAM;
1297 return -1;
1298 }
1299 else if(ctx->tunnel.reset) {
1300 failf(data, "HTTP/2 stream %u was reset", ctx->tunnel.stream_id);
1301 *err = CURLE_RECV_ERROR;
1302 return -1;
1303 }
1304
1305 *err = CURLE_OK;
1306 rv = 0;
1307 CURL_TRC_CF(data, cf, "[%d] handle_tunnel_close -> %zd, %d",
1308 ctx->tunnel.stream_id, rv, *err);
1309 return rv;
1310 }
1311
tunnel_recv(struct Curl_cfilter * cf,struct Curl_easy * data,char * buf,size_t len,CURLcode * err)1312 static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
1313 char *buf, size_t len, CURLcode *err)
1314 {
1315 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1316 ssize_t nread = -1;
1317
1318 *err = CURLE_AGAIN;
1319 if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
1320 nread = Curl_bufq_read(&ctx->tunnel.recvbuf,
1321 (unsigned char *)buf, len, err);
1322 if(nread < 0)
1323 goto out;
1324 DEBUGASSERT(nread > 0);
1325 }
1326
1327 if(nread < 0) {
1328 if(ctx->tunnel.closed) {
1329 nread = h2_handle_tunnel_close(cf, data, err);
1330 }
1331 else if(ctx->tunnel.reset ||
1332 (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
1333 (ctx->rcvd_goaway &&
1334 ctx->last_stream_id < ctx->tunnel.stream_id)) {
1335 *err = CURLE_RECV_ERROR;
1336 nread = -1;
1337 }
1338 }
1339 else if(nread == 0) {
1340 *err = CURLE_AGAIN;
1341 nread = -1;
1342 }
1343
1344 out:
1345 CURL_TRC_CF(data, cf, "[%d] tunnel_recv(len=%zu) -> %zd, %d",
1346 ctx->tunnel.stream_id, len, nread, *err);
1347 return nread;
1348 }
1349
cf_h2_proxy_recv(struct Curl_cfilter * cf,struct Curl_easy * data,char * buf,size_t len,CURLcode * err)1350 static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf,
1351 struct Curl_easy *data,
1352 char *buf, size_t len, CURLcode *err)
1353 {
1354 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1355 ssize_t nread = -1;
1356 struct cf_call_data save;
1357 CURLcode result;
1358
1359 if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
1360 *err = CURLE_RECV_ERROR;
1361 return -1;
1362 }
1363 CF_DATA_SAVE(save, cf, data);
1364
1365 if(Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
1366 *err = proxy_h2_progress_ingress(cf, data);
1367 if(*err)
1368 goto out;
1369 }
1370
1371 nread = tunnel_recv(cf, data, buf, len, err);
1372
1373 if(nread > 0) {
1374 CURL_TRC_CF(data, cf, "[%d] increase window by %zd",
1375 ctx->tunnel.stream_id, nread);
1376 nghttp2_session_consume(ctx->h2, ctx->tunnel.stream_id, (size_t)nread);
1377 }
1378
1379 result = proxy_h2_progress_egress(cf, data);
1380 if(result && (result != CURLE_AGAIN)) {
1381 *err = result;
1382 nread = -1;
1383 }
1384
1385 out:
1386 if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) &&
1387 (nread >= 0 || *err == CURLE_AGAIN)) {
1388 /* data pending and no fatal error to report. Need to trigger
1389 * draining to avoid stalling when no socket events happen. */
1390 drain_tunnel(cf, data, &ctx->tunnel);
1391 }
1392 CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d",
1393 ctx->tunnel.stream_id, len, nread, *err);
1394 CF_DATA_RESTORE(cf, save);
1395 return nread;
1396 }
1397
cf_h2_proxy_send(struct Curl_cfilter * cf,struct Curl_easy * data,const void * buf,size_t len,bool eos,CURLcode * err)1398 static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf,
1399 struct Curl_easy *data,
1400 const void *buf, size_t len, bool eos,
1401 CURLcode *err)
1402 {
1403 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1404 struct cf_call_data save;
1405 int rv;
1406 ssize_t nwritten;
1407 CURLcode result;
1408
1409 (void)eos; /* TODO, maybe useful for blocks? */
1410 if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
1411 *err = CURLE_SEND_ERROR;
1412 return -1;
1413 }
1414 CF_DATA_SAVE(save, cf, data);
1415
1416 if(ctx->tunnel.closed) {
1417 nwritten = -1;
1418 *err = CURLE_SEND_ERROR;
1419 goto out;
1420 }
1421 else {
1422 nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err);
1423 if(nwritten < 0 && (*err != CURLE_AGAIN))
1424 goto out;
1425 }
1426
1427 if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
1428 /* req body data is buffered, resume the potentially suspended stream */
1429 rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
1430 if(nghttp2_is_fatal(rv)) {
1431 *err = CURLE_SEND_ERROR;
1432 nwritten = -1;
1433 goto out;
1434 }
1435 }
1436
1437 result = proxy_h2_progress_ingress(cf, data);
1438 if(result) {
1439 *err = result;
1440 nwritten = -1;
1441 goto out;
1442 }
1443
1444 /* Call the nghttp2 send loop and flush to write ALL buffered data,
1445 * headers and/or request body completely out to the network */
1446 result = proxy_h2_progress_egress(cf, data);
1447 if(result && (result != CURLE_AGAIN)) {
1448 *err = result;
1449 nwritten = -1;
1450 goto out;
1451 }
1452
1453 if(proxy_h2_should_close_session(ctx)) {
1454 /* nghttp2 thinks this session is done. If the stream has not been
1455 * closed, this is an error state for out transfer */
1456 if(ctx->tunnel.closed) {
1457 *err = CURLE_SEND_ERROR;
1458 nwritten = -1;
1459 }
1460 else {
1461 CURL_TRC_CF(data, cf, "[0] send: nothing to do in this session");
1462 *err = CURLE_HTTP2;
1463 nwritten = -1;
1464 }
1465 }
1466
1467 out:
1468 if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) &&
1469 (nwritten >= 0 || *err == CURLE_AGAIN)) {
1470 /* data pending and no fatal error to report. Need to trigger
1471 * draining to avoid stalling when no socket events happen. */
1472 drain_tunnel(cf, data, &ctx->tunnel);
1473 }
1474 CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %zd, %d, "
1475 "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)",
1476 ctx->tunnel.stream_id, len, nwritten, *err,
1477 nghttp2_session_get_stream_remote_window_size(
1478 ctx->h2, ctx->tunnel.stream_id),
1479 nghttp2_session_get_remote_window_size(ctx->h2),
1480 Curl_bufq_len(&ctx->tunnel.sendbuf),
1481 Curl_bufq_len(&ctx->outbufq));
1482 CF_DATA_RESTORE(cf, save);
1483 return nwritten;
1484 }
1485
cf_h2_proxy_flush(struct Curl_cfilter * cf,struct Curl_easy * data)1486 static CURLcode cf_h2_proxy_flush(struct Curl_cfilter *cf,
1487 struct Curl_easy *data)
1488 {
1489 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1490 struct cf_call_data save;
1491 CURLcode result = CURLE_OK;
1492
1493 CF_DATA_SAVE(save, cf, data);
1494 if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
1495 /* resume the potentially suspended tunnel */
1496 int rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
1497 if(nghttp2_is_fatal(rv)) {
1498 result = CURLE_SEND_ERROR;
1499 goto out;
1500 }
1501 }
1502
1503 result = proxy_h2_progress_egress(cf, data);
1504
1505 out:
1506 CURL_TRC_CF(data, cf, "[%d] flush -> %d, "
1507 "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)",
1508 ctx->tunnel.stream_id, result,
1509 nghttp2_session_get_stream_remote_window_size(
1510 ctx->h2, ctx->tunnel.stream_id),
1511 nghttp2_session_get_remote_window_size(ctx->h2),
1512 Curl_bufq_len(&ctx->tunnel.sendbuf),
1513 Curl_bufq_len(&ctx->outbufq));
1514 CF_DATA_RESTORE(cf, save);
1515 return result;
1516 }
1517
proxy_h2_connisalive(struct Curl_cfilter * cf,struct Curl_easy * data,bool * input_pending)1518 static bool proxy_h2_connisalive(struct Curl_cfilter *cf,
1519 struct Curl_easy *data,
1520 bool *input_pending)
1521 {
1522 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1523 bool alive = TRUE;
1524
1525 *input_pending = FALSE;
1526 if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
1527 return FALSE;
1528
1529 if(*input_pending) {
1530 /* This happens before we have sent off a request and the connection is
1531 not in use by any other transfer, there should not be any data here,
1532 only "protocol frames" */
1533 CURLcode result;
1534 ssize_t nread = -1;
1535
1536 *input_pending = FALSE;
1537 nread = Curl_bufq_slurp(&ctx->inbufq, proxy_nw_in_reader, cf, &result);
1538 if(nread != -1) {
1539 if(proxy_h2_process_pending_input(cf, data, &result) < 0)
1540 /* immediate error, considered dead */
1541 alive = FALSE;
1542 else {
1543 alive = !proxy_h2_should_close_session(ctx);
1544 }
1545 }
1546 else if(result != CURLE_AGAIN) {
1547 /* the read failed so let's say this is dead anyway */
1548 alive = FALSE;
1549 }
1550 }
1551
1552 return alive;
1553 }
1554
cf_h2_proxy_is_alive(struct Curl_cfilter * cf,struct Curl_easy * data,bool * input_pending)1555 static bool cf_h2_proxy_is_alive(struct Curl_cfilter *cf,
1556 struct Curl_easy *data,
1557 bool *input_pending)
1558 {
1559 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1560 CURLcode result;
1561 struct cf_call_data save;
1562
1563 CF_DATA_SAVE(save, cf, data);
1564 result = (ctx && ctx->h2 && proxy_h2_connisalive(cf, data, input_pending));
1565 CURL_TRC_CF(data, cf, "[0] conn alive -> %d, input_pending=%d",
1566 result, *input_pending);
1567 CF_DATA_RESTORE(cf, save);
1568 return result;
1569 }
1570
cf_h2_proxy_query(struct Curl_cfilter * cf,struct Curl_easy * data,int query,int * pres1,void * pres2)1571 static CURLcode cf_h2_proxy_query(struct Curl_cfilter *cf,
1572 struct Curl_easy *data,
1573 int query, int *pres1, void *pres2)
1574 {
1575 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1576
1577 switch(query) {
1578 case CF_QUERY_NEED_FLUSH: {
1579 if(!Curl_bufq_is_empty(&ctx->outbufq) ||
1580 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
1581 CURL_TRC_CF(data, cf, "needs flush");
1582 *pres1 = TRUE;
1583 return CURLE_OK;
1584 }
1585 break;
1586 }
1587 default:
1588 break;
1589 }
1590 return cf->next ?
1591 cf->next->cft->query(cf->next, data, query, pres1, pres2) :
1592 CURLE_UNKNOWN_OPTION;
1593 }
1594
cf_h2_proxy_cntrl(struct Curl_cfilter * cf,struct Curl_easy * data,int event,int arg1,void * arg2)1595 static CURLcode cf_h2_proxy_cntrl(struct Curl_cfilter *cf,
1596 struct Curl_easy *data,
1597 int event, int arg1, void *arg2)
1598 {
1599 CURLcode result = CURLE_OK;
1600 struct cf_call_data save;
1601
1602 (void)arg1;
1603 (void)arg2;
1604
1605 switch(event) {
1606 case CF_CTRL_FLUSH:
1607 CF_DATA_SAVE(save, cf, data);
1608 result = cf_h2_proxy_flush(cf, data);
1609 CF_DATA_RESTORE(cf, save);
1610 break;
1611 default:
1612 break;
1613 }
1614 return result;
1615 }
1616
1617 struct Curl_cftype Curl_cft_h2_proxy = {
1618 "H2-PROXY",
1619 CF_TYPE_IP_CONNECT|CF_TYPE_PROXY,
1620 CURL_LOG_LVL_NONE,
1621 cf_h2_proxy_destroy,
1622 cf_h2_proxy_connect,
1623 cf_h2_proxy_close,
1624 cf_h2_proxy_shutdown,
1625 Curl_cf_http_proxy_get_host,
1626 cf_h2_proxy_adjust_pollset,
1627 cf_h2_proxy_data_pending,
1628 cf_h2_proxy_send,
1629 cf_h2_proxy_recv,
1630 cf_h2_proxy_cntrl,
1631 cf_h2_proxy_is_alive,
1632 Curl_cf_def_conn_keep_alive,
1633 cf_h2_proxy_query,
1634 };
1635
Curl_cf_h2_proxy_insert_after(struct Curl_cfilter * cf,struct Curl_easy * data)1636 CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf,
1637 struct Curl_easy *data)
1638 {
1639 struct Curl_cfilter *cf_h2_proxy = NULL;
1640 struct cf_h2_proxy_ctx *ctx;
1641 CURLcode result = CURLE_OUT_OF_MEMORY;
1642
1643 (void)data;
1644 ctx = calloc(1, sizeof(*ctx));
1645 if(!ctx)
1646 goto out;
1647
1648 result = Curl_cf_create(&cf_h2_proxy, &Curl_cft_h2_proxy, ctx);
1649 if(result)
1650 goto out;
1651
1652 Curl_conn_cf_insert_after(cf, cf_h2_proxy);
1653 result = CURLE_OK;
1654
1655 out:
1656 if(result)
1657 cf_h2_proxy_ctx_free(ctx);
1658 return result;
1659 }
1660
1661 #endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */
1662