Coverage Report

Created: 2026-03-11 07:11

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