Coverage Report

Created: 2026-07-16 06:10

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