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 #ifdef DEBUGBUILD
869 nwritten = 0;
870 #endif
871 }
872 DEBUGASSERT((size_t)nwritten == len);
873 return 0;
874 }
875
proxy_h2_on_stream_close(nghttp2_session * session,int32_t stream_id,uint32_t error_code,void * userp)876 static int proxy_h2_on_stream_close(nghttp2_session *session,
877 int32_t stream_id,
878 uint32_t error_code, void *userp)
879 {
880 struct Curl_cfilter *cf = userp;
881 struct cf_h2_proxy_ctx *ctx = cf->ctx;
882 struct Curl_easy *data = CF_DATA_CURRENT(cf);
883
884 (void)session;
885 (void)data;
886
887 if(stream_id != ctx->tunnel.stream_id)
888 return 0;
889
890 CURL_TRC_CF(data, cf, "[%d] proxy_h2_on_stream_close, %s (err %d)",
891 stream_id, nghttp2_http2_strerror(error_code), error_code);
892 ctx->tunnel.closed = TRUE;
893 ctx->tunnel.error = error_code;
894
895 return 0;
896 }
897
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)898 static CURLcode proxy_h2_submit(int32_t *pstream_id,
899 struct Curl_cfilter *cf,
900 struct Curl_easy *data,
901 nghttp2_session *h2,
902 struct httpreq *req,
903 const nghttp2_priority_spec *pri_spec,
904 void *stream_user_data,
905 nghttp2_data_source_read_callback read_callback,
906 void *read_ctx)
907 {
908 struct dynhds h2_headers;
909 nghttp2_nv *nva = NULL;
910 int32_t stream_id = -1;
911 size_t nheader;
912 CURLcode result;
913
914 (void)cf;
915 Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
916 result = Curl_http_req_to_h2(&h2_headers, req, data);
917 if(result)
918 goto out;
919
920 nva = Curl_dynhds_to_nva(&h2_headers, &nheader);
921 if(!nva) {
922 result = CURLE_OUT_OF_MEMORY;
923 goto out;
924 }
925
926 if(read_callback) {
927 nghttp2_data_provider data_prd;
928
929 data_prd.read_callback = read_callback;
930 data_prd.source.ptr = read_ctx;
931 stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
932 &data_prd, stream_user_data);
933 }
934 else {
935 stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
936 NULL, stream_user_data);
937 }
938
939 if(stream_id < 0) {
940 failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
941 nghttp2_strerror(stream_id), stream_id);
942 result = CURLE_SEND_ERROR;
943 goto out;
944 }
945 result = CURLE_OK;
946
947 out:
948 free(nva);
949 Curl_dynhds_free(&h2_headers);
950 *pstream_id = stream_id;
951 return result;
952 }
953
submit_CONNECT(struct Curl_cfilter * cf,struct Curl_easy * data,struct tunnel_stream * ts)954 static CURLcode submit_CONNECT(struct Curl_cfilter *cf,
955 struct Curl_easy *data,
956 struct tunnel_stream *ts)
957 {
958 struct cf_h2_proxy_ctx *ctx = cf->ctx;
959 CURLcode result;
960 struct httpreq *req = NULL;
961
962 result = Curl_http_proxy_create_CONNECT(&req, cf, data, 2);
963 if(result)
964 goto out;
965 result = Curl_creader_set_null(data);
966 if(result)
967 goto out;
968
969 infof(data, "Establish HTTP/2 proxy tunnel to %s", req->authority);
970
971 result = proxy_h2_submit(&ts->stream_id, cf, data, ctx->h2, req,
972 NULL, ts, tunnel_send_callback, cf);
973 if(result) {
974 CURL_TRC_CF(data, cf, "[%d] send, nghttp2_submit_request error: %s",
975 ts->stream_id, nghttp2_strerror(ts->stream_id));
976 }
977
978 out:
979 if(req)
980 Curl_http_req_free(req);
981 if(result)
982 failf(data, "Failed sending CONNECT to proxy");
983 return result;
984 }
985
inspect_response(struct Curl_cfilter * cf,struct Curl_easy * data,struct tunnel_stream * ts)986 static CURLcode inspect_response(struct Curl_cfilter *cf,
987 struct Curl_easy *data,
988 struct tunnel_stream *ts)
989 {
990 CURLcode result = CURLE_OK;
991 struct dynhds_entry *auth_reply = NULL;
992 (void)cf;
993
994 DEBUGASSERT(ts->resp);
995 if(ts->resp->status/100 == 2) {
996 infof(data, "CONNECT tunnel established, response %d", ts->resp->status);
997 h2_tunnel_go_state(cf, ts, H2_TUNNEL_ESTABLISHED, data);
998 return CURLE_OK;
999 }
1000
1001 if(ts->resp->status == 401) {
1002 auth_reply = Curl_dynhds_cget(&ts->resp->headers, "WWW-Authenticate");
1003 }
1004 else if(ts->resp->status == 407) {
1005 auth_reply = Curl_dynhds_cget(&ts->resp->headers, "Proxy-Authenticate");
1006 }
1007
1008 if(auth_reply) {
1009 CURL_TRC_CF(data, cf, "[0] CONNECT: fwd auth header '%s'",
1010 auth_reply->value);
1011 result = Curl_http_input_auth(data, ts->resp->status == 407,
1012 auth_reply->value);
1013 if(result)
1014 return result;
1015 if(data->req.newurl) {
1016 /* Indicator that we should try again */
1017 Curl_safefree(data->req.newurl);
1018 h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data);
1019 return CURLE_OK;
1020 }
1021 }
1022
1023 /* Seems to have failed */
1024 return CURLE_RECV_ERROR;
1025 }
1026
H2_CONNECT(struct Curl_cfilter * cf,struct Curl_easy * data,struct tunnel_stream * ts)1027 static CURLcode H2_CONNECT(struct Curl_cfilter *cf,
1028 struct Curl_easy *data,
1029 struct tunnel_stream *ts)
1030 {
1031 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1032 CURLcode result = CURLE_OK;
1033
1034 DEBUGASSERT(ts);
1035 DEBUGASSERT(ts->authority);
1036 do {
1037 switch(ts->state) {
1038 case H2_TUNNEL_INIT:
1039 /* Prepare the CONNECT request and make a first attempt to send. */
1040 CURL_TRC_CF(data, cf, "[0] CONNECT start for %s", ts->authority);
1041 result = submit_CONNECT(cf, data, ts);
1042 if(result)
1043 goto out;
1044 h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data);
1045 FALLTHROUGH();
1046
1047 case H2_TUNNEL_CONNECT:
1048 /* see that the request is completely sent */
1049 result = proxy_h2_progress_ingress(cf, data);
1050 if(!result)
1051 result = proxy_h2_progress_egress(cf, data);
1052 if(result && result != CURLE_AGAIN) {
1053 h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
1054 break;
1055 }
1056
1057 if(ts->has_final_response) {
1058 h2_tunnel_go_state(cf, ts, H2_TUNNEL_RESPONSE, data);
1059 }
1060 else {
1061 result = CURLE_OK;
1062 goto out;
1063 }
1064 FALLTHROUGH();
1065
1066 case H2_TUNNEL_RESPONSE:
1067 DEBUGASSERT(ts->has_final_response);
1068 result = inspect_response(cf, data, ts);
1069 if(result)
1070 goto out;
1071 break;
1072
1073 case H2_TUNNEL_ESTABLISHED:
1074 return CURLE_OK;
1075
1076 case H2_TUNNEL_FAILED:
1077 return CURLE_RECV_ERROR;
1078
1079 default:
1080 break;
1081 }
1082
1083 } while(ts->state == H2_TUNNEL_INIT);
1084
1085 out:
1086 if((result && (result != CURLE_AGAIN)) || ctx->tunnel.closed)
1087 h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
1088 return result;
1089 }
1090
cf_h2_proxy_connect(struct Curl_cfilter * cf,struct Curl_easy * data,bool blocking,bool * done)1091 static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf,
1092 struct Curl_easy *data,
1093 bool blocking, bool *done)
1094 {
1095 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1096 CURLcode result = CURLE_OK;
1097 struct cf_call_data save;
1098 timediff_t check;
1099 struct tunnel_stream *ts = &ctx->tunnel;
1100
1101 if(cf->connected) {
1102 *done = TRUE;
1103 return CURLE_OK;
1104 }
1105
1106 /* Connect the lower filters first */
1107 if(!cf->next->connected) {
1108 result = Curl_conn_cf_connect(cf->next, data, blocking, done);
1109 if(result || !*done)
1110 return result;
1111 }
1112
1113 *done = FALSE;
1114
1115 CF_DATA_SAVE(save, cf, data);
1116 if(!ctx->h2) {
1117 result = cf_h2_proxy_ctx_init(cf, data);
1118 if(result)
1119 goto out;
1120 }
1121 DEBUGASSERT(ts->authority);
1122
1123 check = Curl_timeleft(data, NULL, TRUE);
1124 if(check <= 0) {
1125 failf(data, "Proxy CONNECT aborted due to timeout");
1126 result = CURLE_OPERATION_TIMEDOUT;
1127 goto out;
1128 }
1129
1130 /* for the secondary socket (FTP), use the "connect to host"
1131 * but ignore the "connect to port" (use the secondary port)
1132 */
1133 result = H2_CONNECT(cf, data, ts);
1134
1135 out:
1136 *done = (result == CURLE_OK) && (ts->state == H2_TUNNEL_ESTABLISHED);
1137 if(*done) {
1138 cf->connected = TRUE;
1139 /* The real request will follow the CONNECT, reset request partially */
1140 Curl_req_soft_reset(&data->req, data);
1141 Curl_client_reset(data);
1142 }
1143 CF_DATA_RESTORE(cf, save);
1144 return result;
1145 }
1146
cf_h2_proxy_close(struct Curl_cfilter * cf,struct Curl_easy * data)1147 static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data)
1148 {
1149 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1150
1151 if(ctx) {
1152 struct cf_call_data save;
1153
1154 CF_DATA_SAVE(save, cf, data);
1155 cf_h2_proxy_ctx_clear(ctx);
1156 CF_DATA_RESTORE(cf, save);
1157 }
1158 if(cf->next)
1159 cf->next->cft->do_close(cf->next, data);
1160 }
1161
cf_h2_proxy_destroy(struct Curl_cfilter * cf,struct Curl_easy * data)1162 static void cf_h2_proxy_destroy(struct Curl_cfilter *cf,
1163 struct Curl_easy *data)
1164 {
1165 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1166
1167 (void)data;
1168 if(ctx) {
1169 cf_h2_proxy_ctx_free(ctx);
1170 cf->ctx = NULL;
1171 }
1172 }
1173
cf_h2_proxy_shutdown(struct Curl_cfilter * cf,struct Curl_easy * data,bool * done)1174 static CURLcode cf_h2_proxy_shutdown(struct Curl_cfilter *cf,
1175 struct Curl_easy *data, bool *done)
1176 {
1177 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1178 struct cf_call_data save;
1179 CURLcode result;
1180 int rv;
1181
1182 if(!cf->connected || !ctx->h2 || cf->shutdown || ctx->conn_closed) {
1183 *done = TRUE;
1184 return CURLE_OK;
1185 }
1186
1187 CF_DATA_SAVE(save, cf, data);
1188
1189 if(!ctx->sent_goaway) {
1190 rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE,
1191 0, 0,
1192 (const uint8_t *)"shutdown",
1193 sizeof("shutdown"));
1194 if(rv) {
1195 failf(data, "nghttp2_submit_goaway() failed: %s(%d)",
1196 nghttp2_strerror(rv), rv);
1197 result = CURLE_SEND_ERROR;
1198 goto out;
1199 }
1200 ctx->sent_goaway = TRUE;
1201 }
1202 /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */
1203 result = CURLE_OK;
1204 if(nghttp2_session_want_write(ctx->h2))
1205 result = proxy_h2_progress_egress(cf, data);
1206 if(!result && nghttp2_session_want_read(ctx->h2))
1207 result = proxy_h2_progress_ingress(cf, data);
1208
1209 *done = (ctx->conn_closed ||
1210 (!result && !nghttp2_session_want_write(ctx->h2) &&
1211 !nghttp2_session_want_read(ctx->h2)));
1212 out:
1213 CF_DATA_RESTORE(cf, save);
1214 cf->shutdown = (result || *done);
1215 return result;
1216 }
1217
cf_h2_proxy_data_pending(struct Curl_cfilter * cf,const struct Curl_easy * data)1218 static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf,
1219 const struct Curl_easy *data)
1220 {
1221 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1222 if((ctx && !Curl_bufq_is_empty(&ctx->inbufq)) ||
1223 (ctx && ctx->tunnel.state == H2_TUNNEL_ESTABLISHED &&
1224 !Curl_bufq_is_empty(&ctx->tunnel.recvbuf)))
1225 return TRUE;
1226 return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
1227 }
1228
cf_h2_proxy_adjust_pollset(struct Curl_cfilter * cf,struct Curl_easy * data,struct easy_pollset * ps)1229 static void cf_h2_proxy_adjust_pollset(struct Curl_cfilter *cf,
1230 struct Curl_easy *data,
1231 struct easy_pollset *ps)
1232 {
1233 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1234 struct cf_call_data save;
1235 curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
1236 bool want_recv, want_send;
1237
1238 if(!cf->connected && ctx->h2) {
1239 want_send = nghttp2_session_want_write(ctx->h2) ||
1240 !Curl_bufq_is_empty(&ctx->outbufq) ||
1241 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf);
1242 want_recv = nghttp2_session_want_read(ctx->h2);
1243 }
1244 else
1245 Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
1246
1247 if(ctx->h2 && (want_recv || want_send)) {
1248 bool c_exhaust, s_exhaust;
1249
1250 CF_DATA_SAVE(save, cf, data);
1251 c_exhaust = !nghttp2_session_get_remote_window_size(ctx->h2);
1252 s_exhaust = ctx->tunnel.stream_id >= 0 &&
1253 !nghttp2_session_get_stream_remote_window_size(
1254 ctx->h2, ctx->tunnel.stream_id);
1255 want_recv = (want_recv || c_exhaust || s_exhaust);
1256 want_send = (!s_exhaust && want_send) ||
1257 (!c_exhaust && nghttp2_session_want_write(ctx->h2)) ||
1258 !Curl_bufq_is_empty(&ctx->outbufq) ||
1259 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf);
1260
1261 Curl_pollset_set(data, ps, sock, want_recv, want_send);
1262 CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d",
1263 want_recv, want_send);
1264 CF_DATA_RESTORE(cf, save);
1265 }
1266 else if(ctx->sent_goaway && !cf->shutdown) {
1267 /* shutdown in progress */
1268 CF_DATA_SAVE(save, cf, data);
1269 want_send = nghttp2_session_want_write(ctx->h2) ||
1270 !Curl_bufq_is_empty(&ctx->outbufq) ||
1271 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf);
1272 want_recv = nghttp2_session_want_read(ctx->h2);
1273 Curl_pollset_set(data, ps, sock, want_recv, want_send);
1274 CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d",
1275 want_recv, want_send);
1276 CF_DATA_RESTORE(cf, save);
1277 }
1278 }
1279
h2_handle_tunnel_close(struct Curl_cfilter * cf,struct Curl_easy * data,CURLcode * err)1280 static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf,
1281 struct Curl_easy *data,
1282 CURLcode *err)
1283 {
1284 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1285 ssize_t rv = 0;
1286
1287 if(ctx->tunnel.error == NGHTTP2_REFUSED_STREAM) {
1288 CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new "
1289 "connection", ctx->tunnel.stream_id);
1290 connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */
1291 *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
1292 return -1;
1293 }
1294 else if(ctx->tunnel.error != NGHTTP2_NO_ERROR) {
1295 failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)",
1296 ctx->tunnel.stream_id, nghttp2_http2_strerror(ctx->tunnel.error),
1297 ctx->tunnel.error);
1298 *err = CURLE_HTTP2_STREAM;
1299 return -1;
1300 }
1301 else if(ctx->tunnel.reset) {
1302 failf(data, "HTTP/2 stream %u was reset", ctx->tunnel.stream_id);
1303 *err = CURLE_RECV_ERROR;
1304 return -1;
1305 }
1306
1307 *err = CURLE_OK;
1308 rv = 0;
1309 CURL_TRC_CF(data, cf, "[%d] handle_tunnel_close -> %zd, %d",
1310 ctx->tunnel.stream_id, rv, *err);
1311 return rv;
1312 }
1313
tunnel_recv(struct Curl_cfilter * cf,struct Curl_easy * data,char * buf,size_t len,CURLcode * err)1314 static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
1315 char *buf, size_t len, CURLcode *err)
1316 {
1317 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1318 ssize_t nread = -1;
1319
1320 *err = CURLE_AGAIN;
1321 if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
1322 nread = Curl_bufq_read(&ctx->tunnel.recvbuf,
1323 (unsigned char *)buf, len, err);
1324 if(nread < 0)
1325 goto out;
1326 DEBUGASSERT(nread > 0);
1327 }
1328
1329 if(nread < 0) {
1330 if(ctx->tunnel.closed) {
1331 nread = h2_handle_tunnel_close(cf, data, err);
1332 }
1333 else if(ctx->tunnel.reset ||
1334 (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
1335 (ctx->rcvd_goaway &&
1336 ctx->last_stream_id < ctx->tunnel.stream_id)) {
1337 *err = CURLE_RECV_ERROR;
1338 nread = -1;
1339 }
1340 }
1341 else if(nread == 0) {
1342 *err = CURLE_AGAIN;
1343 nread = -1;
1344 }
1345
1346 out:
1347 CURL_TRC_CF(data, cf, "[%d] tunnel_recv(len=%zu) -> %zd, %d",
1348 ctx->tunnel.stream_id, len, nread, *err);
1349 return nread;
1350 }
1351
cf_h2_proxy_recv(struct Curl_cfilter * cf,struct Curl_easy * data,char * buf,size_t len,CURLcode * err)1352 static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf,
1353 struct Curl_easy *data,
1354 char *buf, size_t len, CURLcode *err)
1355 {
1356 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1357 ssize_t nread = -1;
1358 struct cf_call_data save;
1359 CURLcode result;
1360
1361 if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
1362 *err = CURLE_RECV_ERROR;
1363 return -1;
1364 }
1365 CF_DATA_SAVE(save, cf, data);
1366
1367 if(Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
1368 *err = proxy_h2_progress_ingress(cf, data);
1369 if(*err)
1370 goto out;
1371 }
1372
1373 nread = tunnel_recv(cf, data, buf, len, err);
1374
1375 if(nread > 0) {
1376 CURL_TRC_CF(data, cf, "[%d] increase window by %zd",
1377 ctx->tunnel.stream_id, nread);
1378 nghttp2_session_consume(ctx->h2, ctx->tunnel.stream_id, (size_t)nread);
1379 }
1380
1381 result = proxy_h2_progress_egress(cf, data);
1382 if(result && (result != CURLE_AGAIN)) {
1383 *err = result;
1384 nread = -1;
1385 }
1386
1387 out:
1388 if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) &&
1389 (nread >= 0 || *err == CURLE_AGAIN)) {
1390 /* data pending and no fatal error to report. Need to trigger
1391 * draining to avoid stalling when no socket events happen. */
1392 drain_tunnel(cf, data, &ctx->tunnel);
1393 }
1394 CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d",
1395 ctx->tunnel.stream_id, len, nread, *err);
1396 CF_DATA_RESTORE(cf, save);
1397 return nread;
1398 }
1399
cf_h2_proxy_send(struct Curl_cfilter * cf,struct Curl_easy * data,const void * buf,size_t len,bool eos,CURLcode * err)1400 static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf,
1401 struct Curl_easy *data,
1402 const void *buf, size_t len, bool eos,
1403 CURLcode *err)
1404 {
1405 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1406 struct cf_call_data save;
1407 int rv;
1408 ssize_t nwritten;
1409 CURLcode result;
1410
1411 (void)eos; /* TODO, maybe useful for blocks? */
1412 if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
1413 *err = CURLE_SEND_ERROR;
1414 return -1;
1415 }
1416 CF_DATA_SAVE(save, cf, data);
1417
1418 if(ctx->tunnel.closed) {
1419 nwritten = -1;
1420 *err = CURLE_SEND_ERROR;
1421 goto out;
1422 }
1423 else {
1424 nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err);
1425 if(nwritten < 0 && (*err != CURLE_AGAIN))
1426 goto out;
1427 }
1428
1429 if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
1430 /* req body data is buffered, resume the potentially suspended stream */
1431 rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
1432 if(nghttp2_is_fatal(rv)) {
1433 *err = CURLE_SEND_ERROR;
1434 nwritten = -1;
1435 goto out;
1436 }
1437 }
1438
1439 result = proxy_h2_progress_ingress(cf, data);
1440 if(result) {
1441 *err = result;
1442 nwritten = -1;
1443 goto out;
1444 }
1445
1446 /* Call the nghttp2 send loop and flush to write ALL buffered data,
1447 * headers and/or request body completely out to the network */
1448 result = proxy_h2_progress_egress(cf, data);
1449 if(result && (result != CURLE_AGAIN)) {
1450 *err = result;
1451 nwritten = -1;
1452 goto out;
1453 }
1454
1455 if(proxy_h2_should_close_session(ctx)) {
1456 /* nghttp2 thinks this session is done. If the stream has not been
1457 * closed, this is an error state for out transfer */
1458 if(ctx->tunnel.closed) {
1459 *err = CURLE_SEND_ERROR;
1460 nwritten = -1;
1461 }
1462 else {
1463 CURL_TRC_CF(data, cf, "[0] send: nothing to do in this session");
1464 *err = CURLE_HTTP2;
1465 nwritten = -1;
1466 }
1467 }
1468
1469 out:
1470 if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) &&
1471 (nwritten >= 0 || *err == CURLE_AGAIN)) {
1472 /* data pending and no fatal error to report. Need to trigger
1473 * draining to avoid stalling when no socket events happen. */
1474 drain_tunnel(cf, data, &ctx->tunnel);
1475 }
1476 CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %zd, %d, "
1477 "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)",
1478 ctx->tunnel.stream_id, len, nwritten, *err,
1479 nghttp2_session_get_stream_remote_window_size(
1480 ctx->h2, ctx->tunnel.stream_id),
1481 nghttp2_session_get_remote_window_size(ctx->h2),
1482 Curl_bufq_len(&ctx->tunnel.sendbuf),
1483 Curl_bufq_len(&ctx->outbufq));
1484 CF_DATA_RESTORE(cf, save);
1485 return nwritten;
1486 }
1487
cf_h2_proxy_flush(struct Curl_cfilter * cf,struct Curl_easy * data)1488 static CURLcode cf_h2_proxy_flush(struct Curl_cfilter *cf,
1489 struct Curl_easy *data)
1490 {
1491 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1492 struct cf_call_data save;
1493 CURLcode result = CURLE_OK;
1494
1495 CF_DATA_SAVE(save, cf, data);
1496 if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
1497 /* resume the potentially suspended tunnel */
1498 int rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
1499 if(nghttp2_is_fatal(rv)) {
1500 result = CURLE_SEND_ERROR;
1501 goto out;
1502 }
1503 }
1504
1505 result = proxy_h2_progress_egress(cf, data);
1506
1507 out:
1508 CURL_TRC_CF(data, cf, "[%d] flush -> %d, "
1509 "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)",
1510 ctx->tunnel.stream_id, result,
1511 nghttp2_session_get_stream_remote_window_size(
1512 ctx->h2, ctx->tunnel.stream_id),
1513 nghttp2_session_get_remote_window_size(ctx->h2),
1514 Curl_bufq_len(&ctx->tunnel.sendbuf),
1515 Curl_bufq_len(&ctx->outbufq));
1516 CF_DATA_RESTORE(cf, save);
1517 return result;
1518 }
1519
proxy_h2_connisalive(struct Curl_cfilter * cf,struct Curl_easy * data,bool * input_pending)1520 static bool proxy_h2_connisalive(struct Curl_cfilter *cf,
1521 struct Curl_easy *data,
1522 bool *input_pending)
1523 {
1524 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1525 bool alive = TRUE;
1526
1527 *input_pending = FALSE;
1528 if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
1529 return FALSE;
1530
1531 if(*input_pending) {
1532 /* This happens before we have sent off a request and the connection is
1533 not in use by any other transfer, there should not be any data here,
1534 only "protocol frames" */
1535 CURLcode result;
1536 ssize_t nread = -1;
1537
1538 *input_pending = FALSE;
1539 nread = Curl_bufq_slurp(&ctx->inbufq, proxy_nw_in_reader, cf, &result);
1540 if(nread != -1) {
1541 if(proxy_h2_process_pending_input(cf, data, &result) < 0)
1542 /* immediate error, considered dead */
1543 alive = FALSE;
1544 else {
1545 alive = !proxy_h2_should_close_session(ctx);
1546 }
1547 }
1548 else if(result != CURLE_AGAIN) {
1549 /* the read failed so let's say this is dead anyway */
1550 alive = FALSE;
1551 }
1552 }
1553
1554 return alive;
1555 }
1556
cf_h2_proxy_is_alive(struct Curl_cfilter * cf,struct Curl_easy * data,bool * input_pending)1557 static bool cf_h2_proxy_is_alive(struct Curl_cfilter *cf,
1558 struct Curl_easy *data,
1559 bool *input_pending)
1560 {
1561 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1562 CURLcode result;
1563 struct cf_call_data save;
1564
1565 CF_DATA_SAVE(save, cf, data);
1566 result = (ctx && ctx->h2 && proxy_h2_connisalive(cf, data, input_pending));
1567 CURL_TRC_CF(data, cf, "[0] conn alive -> %d, input_pending=%d",
1568 result, *input_pending);
1569 CF_DATA_RESTORE(cf, save);
1570 return result;
1571 }
1572
cf_h2_proxy_query(struct Curl_cfilter * cf,struct Curl_easy * data,int query,int * pres1,void * pres2)1573 static CURLcode cf_h2_proxy_query(struct Curl_cfilter *cf,
1574 struct Curl_easy *data,
1575 int query, int *pres1, void *pres2)
1576 {
1577 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1578
1579 switch(query) {
1580 case CF_QUERY_NEED_FLUSH: {
1581 if(!Curl_bufq_is_empty(&ctx->outbufq) ||
1582 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
1583 CURL_TRC_CF(data, cf, "needs flush");
1584 *pres1 = TRUE;
1585 return CURLE_OK;
1586 }
1587 break;
1588 }
1589 default:
1590 break;
1591 }
1592 return cf->next ?
1593 cf->next->cft->query(cf->next, data, query, pres1, pres2) :
1594 CURLE_UNKNOWN_OPTION;
1595 }
1596
cf_h2_proxy_cntrl(struct Curl_cfilter * cf,struct Curl_easy * data,int event,int arg1,void * arg2)1597 static CURLcode cf_h2_proxy_cntrl(struct Curl_cfilter *cf,
1598 struct Curl_easy *data,
1599 int event, int arg1, void *arg2)
1600 {
1601 CURLcode result = CURLE_OK;
1602 struct cf_call_data save;
1603
1604 (void)arg1;
1605 (void)arg2;
1606
1607 switch(event) {
1608 case CF_CTRL_FLUSH:
1609 CF_DATA_SAVE(save, cf, data);
1610 result = cf_h2_proxy_flush(cf, data);
1611 CF_DATA_RESTORE(cf, save);
1612 break;
1613 default:
1614 break;
1615 }
1616 return result;
1617 }
1618
1619 struct Curl_cftype Curl_cft_h2_proxy = {
1620 "H2-PROXY",
1621 CF_TYPE_IP_CONNECT|CF_TYPE_PROXY,
1622 CURL_LOG_LVL_NONE,
1623 cf_h2_proxy_destroy,
1624 cf_h2_proxy_connect,
1625 cf_h2_proxy_close,
1626 cf_h2_proxy_shutdown,
1627 Curl_cf_http_proxy_get_host,
1628 cf_h2_proxy_adjust_pollset,
1629 cf_h2_proxy_data_pending,
1630 cf_h2_proxy_send,
1631 cf_h2_proxy_recv,
1632 cf_h2_proxy_cntrl,
1633 cf_h2_proxy_is_alive,
1634 Curl_cf_def_conn_keep_alive,
1635 cf_h2_proxy_query,
1636 };
1637
Curl_cf_h2_proxy_insert_after(struct Curl_cfilter * cf,struct Curl_easy * data)1638 CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf,
1639 struct Curl_easy *data)
1640 {
1641 struct Curl_cfilter *cf_h2_proxy = NULL;
1642 struct cf_h2_proxy_ctx *ctx;
1643 CURLcode result = CURLE_OUT_OF_MEMORY;
1644
1645 (void)data;
1646 ctx = calloc(1, sizeof(*ctx));
1647 if(!ctx)
1648 goto out;
1649
1650 result = Curl_cf_create(&cf_h2_proxy, &Curl_cft_h2_proxy, ctx);
1651 if(result)
1652 goto out;
1653
1654 Curl_conn_cf_insert_after(cf, cf_h2_proxy);
1655 result = CURLE_OK;
1656
1657 out:
1658 if(result)
1659 cf_h2_proxy_ctx_free(ctx);
1660 return result;
1661 }
1662
1663 #endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */
1664