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