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