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