VirtualBox

source: vbox/trunk/src/libs/curl-8.7.1/lib/cf-h2-proxy.c@ 106165

最後變更 在這個檔案從106165是 104083,由 vboxsync 提交於 8 月 前

curl-8.7.1: Applied and adjusted our curl changes to 8.4.0. bugref:10639

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette