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