Coverage Report

Created: 2026-09-14 07:07

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