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
60typedef 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
68struct 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
82static 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
108static 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
118static 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
171struct 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
192static 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
206static 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
214static 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.dselect_bits != bits) {
225 CURL_TRC_CF(data, cf, "[%d] DRAIN dselect_bits=%x",
226 tunnel->stream_id, bits);
227 data->state.dselect_bits = bits;
228 Curl_expire(data, 0, EXPIRE_RUN_NOW);
229 }
230}
231
232static 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
251static 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
271static 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
293static ssize_t on_session_send(nghttp2_session *h2,
294 const uint8_t *buf, size_t blen,
295 int flags, void *userp);
296static int proxy_h2_on_frame_recv(nghttp2_session *session,
297 const nghttp2_frame *frame,
298 void *userp);
299#ifndef CURL_DISABLE_VERBOSE_STRINGS
300static int proxy_h2_on_frame_send(nghttp2_session *session,
301 const nghttp2_frame *frame,
302 void *userp);
303#endif
304static int proxy_h2_on_stream_close(nghttp2_session *session,
305 int32_t stream_id,
306 uint32_t error_code, void *userp);
307static 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);
313static 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 */
320static 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
394out:
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
401static 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
407static 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 */
437static 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
471static 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
519static 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
537static 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
568static 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
640static 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
660static 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((data->req.keepon & KEEP_SEND_HOLD) &&
692 (data->req.keepon & KEEP_SEND)) {
693 data->req.keepon &= ~KEEP_SEND_HOLD;
694 drain_tunnel(cf, data, &ctx->tunnel);
695 CURL_TRC_CF(data, cf, "[%d] un-holding after SETTINGS",
696 stream_id);
697 }
698 break;
699 case NGHTTP2_GOAWAY:
700 ctx->goaway = TRUE;
701 break;
702 default:
703 break;
704 }
705 return 0;
706 }
707
708 if(stream_id != ctx->tunnel.stream_id) {
709 CURL_TRC_CF(data, cf, "[%d] rcvd FRAME not for tunnel", stream_id);
710 return NGHTTP2_ERR_CALLBACK_FAILURE;
711 }
712
713 switch(frame->hd.type) {
714 case NGHTTP2_HEADERS:
715 /* nghttp2 guarantees that :status is received, and we store it to
716 stream->status_code. Fuzzing has proven this can still be reached
717 without status code having been set. */
718 if(!ctx->tunnel.resp)
719 return NGHTTP2_ERR_CALLBACK_FAILURE;
720 /* Only final status code signals the end of header */
721 CURL_TRC_CF(data, cf, "[%d] got http status: %d",
722 stream_id, ctx->tunnel.resp->status);
723 if(!ctx->tunnel.has_final_response) {
724 if(ctx->tunnel.resp->status / 100 != 1) {
725 ctx->tunnel.has_final_response = TRUE;
726 }
727 }
728 break;
729 case NGHTTP2_WINDOW_UPDATE:
730 if((data->req.keepon & KEEP_SEND_HOLD) &&
731 (data->req.keepon & KEEP_SEND)) {
732 data->req.keepon &= ~KEEP_SEND_HOLD;
733 Curl_expire(data, 0, EXPIRE_RUN_NOW);
734 CURL_TRC_CF(data, cf, "[%d] unpausing after win update",
735 stream_id);
736 }
737 break;
738 default:
739 break;
740 }
741 return 0;
742}
743
744static int proxy_h2_on_header(nghttp2_session *session,
745 const nghttp2_frame *frame,
746 const uint8_t *name, size_t namelen,
747 const uint8_t *value, size_t valuelen,
748 uint8_t flags,
749 void *userp)
750{
751 struct Curl_cfilter *cf = userp;
752 struct cf_h2_proxy_ctx *ctx = cf->ctx;
753 struct Curl_easy *data = CF_DATA_CURRENT(cf);
754 int32_t stream_id = frame->hd.stream_id;
755 CURLcode result;
756
757 (void)flags;
758 (void)data;
759 (void)session;
760 DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
761 if(stream_id != ctx->tunnel.stream_id) {
762 CURL_TRC_CF(data, cf, "[%d] header for non-tunnel stream: "
763 "%.*s: %.*s", stream_id,
764 (int)namelen, name, (int)valuelen, value);
765 return NGHTTP2_ERR_CALLBACK_FAILURE;
766 }
767
768 if(frame->hd.type == NGHTTP2_PUSH_PROMISE)
769 return NGHTTP2_ERR_CALLBACK_FAILURE;
770
771 if(ctx->tunnel.has_final_response) {
772 /* we do not do anything with trailers for tunnel streams */
773 return 0;
774 }
775
776 if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 &&
777 memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) {
778 int http_status;
779 struct http_resp *resp;
780
781 /* status: always comes first, we might get more than one response,
782 * link the previous ones for keepers */
783 result = Curl_http_decode_status(&http_status,
784 (const char *)value, valuelen);
785 if(result)
786 return NGHTTP2_ERR_CALLBACK_FAILURE;
787 result = Curl_http_resp_make(&resp, http_status, NULL);
788 if(result)
789 return NGHTTP2_ERR_CALLBACK_FAILURE;
790 resp->prev = ctx->tunnel.resp;
791 ctx->tunnel.resp = resp;
792 CURL_TRC_CF(data, cf, "[%d] status: HTTP/2 %03d",
793 stream_id, ctx->tunnel.resp->status);
794 return 0;
795 }
796
797 if(!ctx->tunnel.resp)
798 return NGHTTP2_ERR_CALLBACK_FAILURE;
799
800 result = Curl_dynhds_add(&ctx->tunnel.resp->headers,
801 (const char *)name, namelen,
802 (const char *)value, valuelen);
803 if(result)
804 return NGHTTP2_ERR_CALLBACK_FAILURE;
805
806 CURL_TRC_CF(data, cf, "[%d] header: %.*s: %.*s",
807 stream_id, (int)namelen, name, (int)valuelen, value);
808
809 return 0; /* 0 is successful */
810}
811
812static ssize_t tunnel_send_callback(nghttp2_session *session,
813 int32_t stream_id,
814 uint8_t *buf, size_t length,
815 uint32_t *data_flags,
816 nghttp2_data_source *source,
817 void *userp)
818{
819 struct Curl_cfilter *cf = userp;
820 struct cf_h2_proxy_ctx *ctx = cf->ctx;
821 struct Curl_easy *data = CF_DATA_CURRENT(cf);
822 struct tunnel_stream *ts;
823 CURLcode result;
824 ssize_t nread;
825
826 (void)source;
827 (void)data;
828 (void)ctx;
829
830 if(!stream_id)
831 return NGHTTP2_ERR_INVALID_ARGUMENT;
832
833 ts = nghttp2_session_get_stream_user_data(session, stream_id);
834 if(!ts)
835 return NGHTTP2_ERR_CALLBACK_FAILURE;
836 DEBUGASSERT(ts == &ctx->tunnel);
837
838 nread = Curl_bufq_read(&ts->sendbuf, buf, length, &result);
839 if(nread < 0) {
840 if(result != CURLE_AGAIN)
841 return NGHTTP2_ERR_CALLBACK_FAILURE;
842 return NGHTTP2_ERR_DEFERRED;
843 }
844 if(ts->closed && Curl_bufq_is_empty(&ts->sendbuf))
845 *data_flags = NGHTTP2_DATA_FLAG_EOF;
846
847 CURL_TRC_CF(data, cf, "[%d] tunnel_send_callback -> %zd",
848 ts->stream_id, nread);
849 return nread;
850}
851
852static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags,
853 int32_t stream_id,
854 const uint8_t *mem, size_t len, void *userp)
855{
856 struct Curl_cfilter *cf = userp;
857 struct cf_h2_proxy_ctx *ctx = cf->ctx;
858 ssize_t nwritten;
859 CURLcode result;
860
861 (void)flags;
862 (void)session;
863 DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
864
865 if(stream_id != ctx->tunnel.stream_id)
866 return NGHTTP2_ERR_CALLBACK_FAILURE;
867
868 nwritten = Curl_bufq_write(&ctx->tunnel.recvbuf, mem, len, &result);
869 if(nwritten < 0) {
870 if(result != CURLE_AGAIN)
871 return NGHTTP2_ERR_CALLBACK_FAILURE;
872 nwritten = 0;
873 }
874 DEBUGASSERT((size_t)nwritten == len);
875 return 0;
876}
877
878static int proxy_h2_on_stream_close(nghttp2_session *session,
879 int32_t stream_id,
880 uint32_t error_code, void *userp)
881{
882 struct Curl_cfilter *cf = userp;
883 struct cf_h2_proxy_ctx *ctx = cf->ctx;
884 struct Curl_easy *data = CF_DATA_CURRENT(cf);
885
886 (void)session;
887 (void)data;
888
889 if(stream_id != ctx->tunnel.stream_id)
890 return 0;
891
892 CURL_TRC_CF(data, cf, "[%d] proxy_h2_on_stream_close, %s (err %d)",
893 stream_id, nghttp2_http2_strerror(error_code), error_code);
894 ctx->tunnel.closed = TRUE;
895 ctx->tunnel.error = error_code;
896
897 return 0;
898}
899
900static CURLcode proxy_h2_submit(int32_t *pstream_id,
901 struct Curl_cfilter *cf,
902 struct Curl_easy *data,
903 nghttp2_session *h2,
904 struct httpreq *req,
905 const nghttp2_priority_spec *pri_spec,
906 void *stream_user_data,
907 nghttp2_data_source_read_callback read_callback,
908 void *read_ctx)
909{
910 struct dynhds h2_headers;
911 nghttp2_nv *nva = NULL;
912 unsigned int i;
913 int32_t stream_id = -1;
914 size_t nheader;
915 CURLcode result;
916
917 (void)cf;
918 Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
919 result = Curl_http_req_to_h2(&h2_headers, req, data);
920 if(result)
921 goto out;
922
923 nheader = Curl_dynhds_count(&h2_headers);
924 nva = malloc(sizeof(nghttp2_nv) * nheader);
925 if(!nva) {
926 result = CURLE_OUT_OF_MEMORY;
927 goto out;
928 }
929
930 for(i = 0; i < nheader; ++i) {
931 struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
932 nva[i].name = (unsigned char *)e->name;
933 nva[i].namelen = e->namelen;
934 nva[i].value = (unsigned char *)e->value;
935 nva[i].valuelen = e->valuelen;
936 nva[i].flags = NGHTTP2_NV_FLAG_NONE;
937 }
938
939 if(read_callback) {
940 nghttp2_data_provider data_prd;
941
942 data_prd.read_callback = read_callback;
943 data_prd.source.ptr = read_ctx;
944 stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
945 &data_prd, stream_user_data);
946 }
947 else {
948 stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
949 NULL, stream_user_data);
950 }
951
952 if(stream_id < 0) {
953 failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
954 nghttp2_strerror(stream_id), stream_id);
955 result = CURLE_SEND_ERROR;
956 goto out;
957 }
958 result = CURLE_OK;
959
960out:
961 free(nva);
962 Curl_dynhds_free(&h2_headers);
963 *pstream_id = stream_id;
964 return result;
965}
966
967static CURLcode submit_CONNECT(struct Curl_cfilter *cf,
968 struct Curl_easy *data,
969 struct tunnel_stream *ts)
970{
971 struct cf_h2_proxy_ctx *ctx = cf->ctx;
972 CURLcode result;
973 struct httpreq *req = NULL;
974
975 result = Curl_http_proxy_create_CONNECT(&req, cf, data, 2);
976 if(result)
977 goto out;
978
979 infof(data, "Establish HTTP/2 proxy tunnel to %s", req->authority);
980
981 result = proxy_h2_submit(&ts->stream_id, cf, data, ctx->h2, req,
982 NULL, ts, tunnel_send_callback, cf);
983 if(result) {
984 CURL_TRC_CF(data, cf, "[%d] send, nghttp2_submit_request error: %s",
985 ts->stream_id, nghttp2_strerror(ts->stream_id));
986 }
987
988out:
989 if(req)
990 Curl_http_req_free(req);
991 if(result)
992 failf(data, "Failed sending CONNECT to proxy");
993 return result;
994}
995
996static CURLcode inspect_response(struct Curl_cfilter *cf,
997 struct Curl_easy *data,
998 struct tunnel_stream *ts)
999{
1000 CURLcode result = CURLE_OK;
1001 struct dynhds_entry *auth_reply = NULL;
1002 (void)cf;
1003
1004 DEBUGASSERT(ts->resp);
1005 if(ts->resp->status/100 == 2) {
1006 infof(data, "CONNECT tunnel established, response %d", ts->resp->status);
1007 h2_tunnel_go_state(cf, ts, H2_TUNNEL_ESTABLISHED, data);
1008 return CURLE_OK;
1009 }
1010
1011 if(ts->resp->status == 401) {
1012 auth_reply = Curl_dynhds_cget(&ts->resp->headers, "WWW-Authenticate");
1013 }
1014 else if(ts->resp->status == 407) {
1015 auth_reply = Curl_dynhds_cget(&ts->resp->headers, "Proxy-Authenticate");
1016 }
1017
1018 if(auth_reply) {
1019 CURL_TRC_CF(data, cf, "[0] CONNECT: fwd auth header '%s'",
1020 auth_reply->value);
1021 result = Curl_http_input_auth(data, ts->resp->status == 407,
1022 auth_reply->value);
1023 if(result)
1024 return result;
1025 if(data->req.newurl) {
1026 /* Indicator that we should try again */
1027 Curl_safefree(data->req.newurl);
1028 h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data);
1029 return CURLE_OK;
1030 }
1031 }
1032
1033 /* Seems to have failed */
1034 return CURLE_RECV_ERROR;
1035}
1036
1037static CURLcode H2_CONNECT(struct Curl_cfilter *cf,
1038 struct Curl_easy *data,
1039 struct tunnel_stream *ts)
1040{
1041 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1042 CURLcode result = CURLE_OK;
1043
1044 DEBUGASSERT(ts);
1045 DEBUGASSERT(ts->authority);
1046 do {
1047 switch(ts->state) {
1048 case H2_TUNNEL_INIT:
1049 /* Prepare the CONNECT request and make a first attempt to send. */
1050 CURL_TRC_CF(data, cf, "[0] CONNECT start for %s", ts->authority);
1051 result = submit_CONNECT(cf, data, ts);
1052 if(result)
1053 goto out;
1054 h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data);
1055 /* FALLTHROUGH */
1056
1057 case H2_TUNNEL_CONNECT:
1058 /* see that the request is completely sent */
1059 result = proxy_h2_progress_ingress(cf, data);
1060 if(!result)
1061 result = proxy_h2_progress_egress(cf, data);
1062 if(result && result != CURLE_AGAIN) {
1063 h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
1064 break;
1065 }
1066
1067 if(ts->has_final_response) {
1068 h2_tunnel_go_state(cf, ts, H2_TUNNEL_RESPONSE, data);
1069 }
1070 else {
1071 result = CURLE_OK;
1072 goto out;
1073 }
1074 /* FALLTHROUGH */
1075
1076 case H2_TUNNEL_RESPONSE:
1077 DEBUGASSERT(ts->has_final_response);
1078 result = inspect_response(cf, data, ts);
1079 if(result)
1080 goto out;
1081 break;
1082
1083 case H2_TUNNEL_ESTABLISHED:
1084 return CURLE_OK;
1085
1086 case H2_TUNNEL_FAILED:
1087 return CURLE_RECV_ERROR;
1088
1089 default:
1090 break;
1091 }
1092
1093 } while(ts->state == H2_TUNNEL_INIT);
1094
1095out:
1096 if(result || ctx->tunnel.closed)
1097 h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
1098 return result;
1099}
1100
1101static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf,
1102 struct Curl_easy *data,
1103 bool blocking, bool *done)
1104{
1105 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1106 CURLcode result = CURLE_OK;
1107 struct cf_call_data save;
1108 timediff_t check;
1109 struct tunnel_stream *ts = &ctx->tunnel;
1110
1111 if(cf->connected) {
1112 *done = TRUE;
1113 return CURLE_OK;
1114 }
1115
1116 /* Connect the lower filters first */
1117 if(!cf->next->connected) {
1118 result = Curl_conn_cf_connect(cf->next, data, blocking, done);
1119 if(result || !*done)
1120 return result;
1121 }
1122
1123 *done = FALSE;
1124
1125 CF_DATA_SAVE(save, cf, data);
1126 if(!ctx->h2) {
1127 result = cf_h2_proxy_ctx_init(cf, data);
1128 if(result)
1129 goto out;
1130 }
1131 DEBUGASSERT(ts->authority);
1132
1133 check = Curl_timeleft(data, NULL, TRUE);
1134 if(check <= 0) {
1135 failf(data, "Proxy CONNECT aborted due to timeout");
1136 result = CURLE_OPERATION_TIMEDOUT;
1137 goto out;
1138 }
1139
1140 /* for the secondary socket (FTP), use the "connect to host"
1141 * but ignore the "connect to port" (use the secondary port)
1142 */
1143 result = H2_CONNECT(cf, data, ts);
1144
1145out:
1146 *done = (result == CURLE_OK) && (ts->state == H2_TUNNEL_ESTABLISHED);
1147 cf->connected = *done;
1148 CF_DATA_RESTORE(cf, save);
1149 return result;
1150}
1151
1152static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data)
1153{
1154 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1155
1156 if(ctx) {
1157 struct cf_call_data save;
1158
1159 CF_DATA_SAVE(save, cf, data);
1160 cf_h2_proxy_ctx_clear(ctx);
1161 CF_DATA_RESTORE(cf, save);
1162 }
1163 if(cf->next)
1164 cf->next->cft->do_close(cf->next, data);
1165}
1166
1167static void cf_h2_proxy_destroy(struct Curl_cfilter *cf,
1168 struct Curl_easy *data)
1169{
1170 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1171
1172 (void)data;
1173 if(ctx) {
1174 cf_h2_proxy_ctx_free(ctx);
1175 cf->ctx = NULL;
1176 }
1177}
1178
1179static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf,
1180 const struct Curl_easy *data)
1181{
1182 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1183 if((ctx && !Curl_bufq_is_empty(&ctx->inbufq)) ||
1184 (ctx && ctx->tunnel.state == H2_TUNNEL_ESTABLISHED &&
1185 !Curl_bufq_is_empty(&ctx->tunnel.recvbuf)))
1186 return TRUE;
1187 return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
1188}
1189
1190static int cf_h2_proxy_get_select_socks(struct Curl_cfilter *cf,
1191 struct Curl_easy *data,
1192 curl_socket_t *sock)
1193{
1194 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1195 int bitmap = GETSOCK_BLANK;
1196 struct cf_call_data save;
1197
1198 CF_DATA_SAVE(save, cf, data);
1199 sock[0] = Curl_conn_cf_get_socket(cf, data);
1200 bitmap |= GETSOCK_READSOCK(0);
1201
1202 /* HTTP/2 layer wants to send data) AND there's a window to send data in */
1203 if(nghttp2_session_want_write(ctx->h2) &&
1204 nghttp2_session_get_remote_window_size(ctx->h2))
1205 bitmap |= GETSOCK_WRITESOCK(0);
1206
1207 CF_DATA_RESTORE(cf, save);
1208 return bitmap;
1209}
1210
1211static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf,
1212 struct Curl_easy *data,
1213 CURLcode *err)
1214{
1215 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1216 ssize_t rv = 0;
1217
1218 if(ctx->tunnel.error == NGHTTP2_REFUSED_STREAM) {
1219 CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new "
1220 "connection", ctx->tunnel.stream_id);
1221 connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */
1222 *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
1223 return -1;
1224 }
1225 else if(ctx->tunnel.error != NGHTTP2_NO_ERROR) {
1226 failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)",
1227 ctx->tunnel.stream_id, nghttp2_http2_strerror(ctx->tunnel.error),
1228 ctx->tunnel.error);
1229 *err = CURLE_HTTP2_STREAM;
1230 return -1;
1231 }
1232 else if(ctx->tunnel.reset) {
1233 failf(data, "HTTP/2 stream %u was reset", ctx->tunnel.stream_id);
1234 *err = CURLE_RECV_ERROR;
1235 return -1;
1236 }
1237
1238 *err = CURLE_OK;
1239 rv = 0;
1240 CURL_TRC_CF(data, cf, "[%d] handle_tunnel_close -> %zd, %d",
1241 ctx->tunnel.stream_id, rv, *err);
1242 return rv;
1243}
1244
1245static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
1246 char *buf, size_t len, CURLcode *err)
1247{
1248 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1249 ssize_t nread = -1;
1250
1251 *err = CURLE_AGAIN;
1252 if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
1253 nread = Curl_bufq_read(&ctx->tunnel.recvbuf,
1254 (unsigned char *)buf, len, err);
1255 if(nread < 0)
1256 goto out;
1257 DEBUGASSERT(nread > 0);
1258 }
1259
1260 if(nread < 0) {
1261 if(ctx->tunnel.closed) {
1262 nread = h2_handle_tunnel_close(cf, data, err);
1263 }
1264 else if(ctx->tunnel.reset ||
1265 (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
1266 (ctx->goaway && ctx->last_stream_id < ctx->tunnel.stream_id)) {
1267 *err = CURLE_RECV_ERROR;
1268 nread = -1;
1269 }
1270 }
1271 else if(nread == 0) {
1272 *err = CURLE_AGAIN;
1273 nread = -1;
1274 }
1275
1276out:
1277 CURL_TRC_CF(data, cf, "[%d] tunnel_recv(len=%zu) -> %zd, %d",
1278 ctx->tunnel.stream_id, len, nread, *err);
1279 return nread;
1280}
1281
1282static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf,
1283 struct Curl_easy *data,
1284 char *buf, size_t len, CURLcode *err)
1285{
1286 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1287 ssize_t nread = -1;
1288 struct cf_call_data save;
1289 CURLcode result;
1290
1291 if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
1292 *err = CURLE_RECV_ERROR;
1293 return -1;
1294 }
1295 CF_DATA_SAVE(save, cf, data);
1296
1297 if(Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
1298 *err = proxy_h2_progress_ingress(cf, data);
1299 if(*err)
1300 goto out;
1301 }
1302
1303 nread = tunnel_recv(cf, data, buf, len, err);
1304
1305 if(nread > 0) {
1306 CURL_TRC_CF(data, cf, "[%d] increase window by %zd",
1307 ctx->tunnel.stream_id, nread);
1308 nghttp2_session_consume(ctx->h2, ctx->tunnel.stream_id, (size_t)nread);
1309 }
1310
1311 result = proxy_h2_progress_egress(cf, data);
1312 if(result == CURLE_AGAIN) {
1313 /* pending data to send, need to be called again. Ideally, we'd
1314 * monitor the socket for POLLOUT, but we might not be in SENDING
1315 * transfer state any longer and are unable to make this happen.
1316 */
1317 CURL_TRC_CF(data, cf, "[%d] egress blocked, DRAIN",
1318 ctx->tunnel.stream_id);
1319 drain_tunnel(cf, data, &ctx->tunnel);
1320 }
1321 else if(result) {
1322 *err = result;
1323 nread = -1;
1324 }
1325
1326out:
1327 if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) &&
1328 (nread >= 0 || *err == CURLE_AGAIN)) {
1329 /* data pending and no fatal error to report. Need to trigger
1330 * draining to avoid stalling when no socket events happen. */
1331 drain_tunnel(cf, data, &ctx->tunnel);
1332 }
1333 CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d",
1334 ctx->tunnel.stream_id, len, nread, *err);
1335 CF_DATA_RESTORE(cf, save);
1336 return nread;
1337}
1338
1339static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf,
1340 struct Curl_easy *data,
1341 const void *buf, size_t len, CURLcode *err)
1342{
1343 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1344 struct cf_call_data save;
1345 int rv;
1346 ssize_t nwritten;
1347 CURLcode result;
1348 int blocked = 0;
1349
1350 if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
1351 *err = CURLE_SEND_ERROR;
1352 return -1;
1353 }
1354 CF_DATA_SAVE(save, cf, data);
1355
1356 if(ctx->tunnel.closed) {
1357 nwritten = -1;
1358 *err = CURLE_SEND_ERROR;
1359 goto out;
1360 }
1361 else if(ctx->tunnel.upload_blocked_len) {
1362 /* the data in `buf` has already been submitted or added to the
1363 * buffers, but have been EAGAINed on the last invocation. */
1364 DEBUGASSERT(len >= ctx->tunnel.upload_blocked_len);
1365 if(len < ctx->tunnel.upload_blocked_len) {
1366 /* Did we get called again with a smaller `len`? This should not
1367 * happen. We are not prepared to handle that. */
1368 failf(data, "HTTP/2 proxy, send again with decreased length");
1369 *err = CURLE_HTTP2;
1370 nwritten = -1;
1371 goto out;
1372 }
1373 nwritten = (ssize_t)ctx->tunnel.upload_blocked_len;
1374 ctx->tunnel.upload_blocked_len = 0;
1375 *err = CURLE_OK;
1376 }
1377 else {
1378 nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err);
1379 if(nwritten < 0) {
1380 if(*err != CURLE_AGAIN)
1381 goto out;
1382 nwritten = 0;
1383 }
1384 }
1385
1386 if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
1387 /* req body data is buffered, resume the potentially suspended stream */
1388 rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
1389 if(nghttp2_is_fatal(rv)) {
1390 *err = CURLE_SEND_ERROR;
1391 nwritten = -1;
1392 goto out;
1393 }
1394 }
1395
1396 result = proxy_h2_progress_ingress(cf, data);
1397 if(result) {
1398 *err = result;
1399 nwritten = -1;
1400 goto out;
1401 }
1402
1403 /* Call the nghttp2 send loop and flush to write ALL buffered data,
1404 * headers and/or request body completely out to the network */
1405 result = proxy_h2_progress_egress(cf, data);
1406 if(result == CURLE_AGAIN) {
1407 blocked = 1;
1408 }
1409 else if(result) {
1410 *err = result;
1411 nwritten = -1;
1412 goto out;
1413 }
1414 else if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
1415 /* although we wrote everything that nghttp2 wants to send now,
1416 * there is data left in our stream send buffer unwritten. This may
1417 * be due to the stream's HTTP/2 flow window being exhausted. */
1418 blocked = 1;
1419 }
1420
1421 if(blocked) {
1422 /* Unable to send all data, due to connection blocked or H2 window
1423 * exhaustion. Data is left in our stream buffer, or nghttp2's internal
1424 * frame buffer or our network out buffer. */
1425 size_t rwin = nghttp2_session_get_stream_remote_window_size(
1426 ctx->h2, ctx->tunnel.stream_id);
1427 if(rwin == 0) {
1428 /* H2 flow window exhaustion.
1429 * FIXME: there is no way to HOLD all transfers that use this
1430 * proxy connection AND to UNHOLD all of them again when the
1431 * window increases.
1432 * We *could* iterate over all data on this conn maybe? */
1433 CURL_TRC_CF(data, cf, "[%d] remote flow "
1434 "window is exhausted", ctx->tunnel.stream_id);
1435 }
1436
1437 /* Whatever the cause, we need to return CURL_EAGAIN for this call.
1438 * We have unwritten state that needs us being invoked again and EAGAIN
1439 * is the only way to ensure that. */
1440 ctx->tunnel.upload_blocked_len = nwritten;
1441 CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) BLOCK: win %u/%zu "
1442 "blocked_len=%zu",
1443 ctx->tunnel.stream_id, len,
1444 nghttp2_session_get_remote_window_size(ctx->h2), rwin,
1445 nwritten);
1446 drain_tunnel(cf, data, &ctx->tunnel);
1447 *err = CURLE_AGAIN;
1448 nwritten = -1;
1449 goto out;
1450 }
1451 else 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
1465out:
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
1484static bool proxy_h2_connisalive(struct Curl_cfilter *cf,
1485 struct Curl_easy *data,
1486 bool *input_pending)
1487{
1488 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1489 bool alive = TRUE;
1490
1491 *input_pending = FALSE;
1492 if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
1493 return FALSE;
1494
1495 if(*input_pending) {
1496 /* This happens before we've sent off a request and the connection is
1497 not in use by any other transfer, there shouldn't be any data here,
1498 only "protocol frames" */
1499 CURLcode result;
1500 ssize_t nread = -1;
1501
1502 *input_pending = FALSE;
1503 nread = Curl_bufq_slurp(&ctx->inbufq, proxy_nw_in_reader, cf, &result);
1504 if(nread != -1) {
1505 if(proxy_h2_process_pending_input(cf, data, &result) < 0)
1506 /* immediate error, considered dead */
1507 alive = FALSE;
1508 else {
1509 alive = !proxy_h2_should_close_session(ctx);
1510 }
1511 }
1512 else if(result != CURLE_AGAIN) {
1513 /* the read failed so let's say this is dead anyway */
1514 alive = FALSE;
1515 }
1516 }
1517
1518 return alive;
1519}
1520
1521static bool cf_h2_proxy_is_alive(struct Curl_cfilter *cf,
1522 struct Curl_easy *data,
1523 bool *input_pending)
1524{
1525 struct cf_h2_proxy_ctx *ctx = cf->ctx;
1526 CURLcode result;
1527 struct cf_call_data save;
1528
1529 CF_DATA_SAVE(save, cf, data);
1530 result = (ctx && ctx->h2 && proxy_h2_connisalive(cf, data, input_pending));
1531 CURL_TRC_CF(data, cf, "[0] conn alive -> %d, input_pending=%d",
1532 result, *input_pending);
1533 CF_DATA_RESTORE(cf, save);
1534 return result;
1535}
1536
1537struct Curl_cftype Curl_cft_h2_proxy = {
1538 "H2-PROXY",
1539 CF_TYPE_IP_CONNECT,
1540 CURL_LOG_LVL_NONE,
1541 cf_h2_proxy_destroy,
1542 cf_h2_proxy_connect,
1543 cf_h2_proxy_close,
1544 Curl_cf_http_proxy_get_host,
1545 cf_h2_proxy_get_select_socks,
1546 cf_h2_proxy_data_pending,
1547 cf_h2_proxy_send,
1548 cf_h2_proxy_recv,
1549 Curl_cf_def_cntrl,
1550 cf_h2_proxy_is_alive,
1551 Curl_cf_def_conn_keep_alive,
1552 Curl_cf_def_query,
1553};
1554
1555CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf,
1556 struct Curl_easy *data)
1557{
1558 struct Curl_cfilter *cf_h2_proxy = NULL;
1559 struct cf_h2_proxy_ctx *ctx;
1560 CURLcode result = CURLE_OUT_OF_MEMORY;
1561
1562 (void)data;
1563 ctx = calloc(sizeof(*ctx), 1);
1564 if(!ctx)
1565 goto out;
1566
1567 result = Curl_cf_create(&cf_h2_proxy, &Curl_cft_h2_proxy, ctx);
1568 if(result)
1569 goto out;
1570
1571 Curl_conn_cf_insert_after(cf, cf_h2_proxy);
1572 result = CURLE_OK;
1573
1574out:
1575 if(result)
1576 cf_h2_proxy_ctx_free(ctx);
1577 return result;
1578}
1579
1580#endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */
1581