Coverage Report

Created: 2025-07-11 07:03

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