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