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