Coverage Report

Created: 2026-10-22 06:51

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