Coverage Report

Created: 2023-12-08 06:48

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