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