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