• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.dselect_bits != bits) {
225     CURL_TRC_CF(data, cf, "[%d] DRAIN dselect_bits=%x",
226                 tunnel->stream_id, bits);
227     data->state.dselect_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((data->req.keepon & KEEP_SEND_HOLD) &&
692          (data->req.keepon & KEEP_SEND)) {
693         data->req.keepon &= ~KEEP_SEND_HOLD;
694         drain_tunnel(cf, data, &ctx->tunnel);
695         CURL_TRC_CF(data, cf, "[%d] un-holding after SETTINGS",
696                     stream_id);
697       }
698       break;
699     case NGHTTP2_GOAWAY:
700       ctx->goaway = TRUE;
701       break;
702     default:
703       break;
704     }
705     return 0;
706   }
707 
708   if(stream_id != ctx->tunnel.stream_id) {
709     CURL_TRC_CF(data, cf, "[%d] rcvd FRAME not for tunnel", stream_id);
710     return NGHTTP2_ERR_CALLBACK_FAILURE;
711   }
712 
713   switch(frame->hd.type) {
714   case NGHTTP2_HEADERS:
715     /* nghttp2 guarantees that :status is received, and we store it to
716        stream->status_code. Fuzzing has proven this can still be reached
717        without status code having been set. */
718     if(!ctx->tunnel.resp)
719       return NGHTTP2_ERR_CALLBACK_FAILURE;
720     /* Only final status code signals the end of header */
721     CURL_TRC_CF(data, cf, "[%d] got http status: %d",
722                 stream_id, ctx->tunnel.resp->status);
723     if(!ctx->tunnel.has_final_response) {
724       if(ctx->tunnel.resp->status / 100 != 1) {
725         ctx->tunnel.has_final_response = TRUE;
726       }
727     }
728     break;
729   case NGHTTP2_WINDOW_UPDATE:
730     if((data->req.keepon & KEEP_SEND_HOLD) &&
731        (data->req.keepon & KEEP_SEND)) {
732       data->req.keepon &= ~KEEP_SEND_HOLD;
733       Curl_expire(data, 0, EXPIRE_RUN_NOW);
734       CURL_TRC_CF(data, cf, "[%d] unpausing after win update",
735                   stream_id);
736     }
737     break;
738   default:
739     break;
740   }
741   return 0;
742 }
743 
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)744 static int proxy_h2_on_header(nghttp2_session *session,
745                               const nghttp2_frame *frame,
746                               const uint8_t *name, size_t namelen,
747                               const uint8_t *value, size_t valuelen,
748                               uint8_t flags,
749                               void *userp)
750 {
751   struct Curl_cfilter *cf = userp;
752   struct cf_h2_proxy_ctx *ctx = cf->ctx;
753   struct Curl_easy *data = CF_DATA_CURRENT(cf);
754   int32_t stream_id = frame->hd.stream_id;
755   CURLcode result;
756 
757   (void)flags;
758   (void)data;
759   (void)session;
760   DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
761   if(stream_id != ctx->tunnel.stream_id) {
762     CURL_TRC_CF(data, cf, "[%d] header for non-tunnel stream: "
763                 "%.*s: %.*s", stream_id,
764                 (int)namelen, name, (int)valuelen, value);
765     return NGHTTP2_ERR_CALLBACK_FAILURE;
766   }
767 
768   if(frame->hd.type == NGHTTP2_PUSH_PROMISE)
769     return NGHTTP2_ERR_CALLBACK_FAILURE;
770 
771   if(ctx->tunnel.has_final_response) {
772     /* we do not do anything with trailers for tunnel streams */
773     return 0;
774   }
775 
776   if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 &&
777      memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) {
778     int http_status;
779     struct http_resp *resp;
780 
781     /* status: always comes first, we might get more than one response,
782      * link the previous ones for keepers */
783     result = Curl_http_decode_status(&http_status,
784                                     (const char *)value, valuelen);
785     if(result)
786       return NGHTTP2_ERR_CALLBACK_FAILURE;
787     result = Curl_http_resp_make(&resp, http_status, NULL);
788     if(result)
789       return NGHTTP2_ERR_CALLBACK_FAILURE;
790     resp->prev = ctx->tunnel.resp;
791     ctx->tunnel.resp = resp;
792     CURL_TRC_CF(data, cf, "[%d] status: HTTP/2 %03d",
793                 stream_id, ctx->tunnel.resp->status);
794     return 0;
795   }
796 
797   if(!ctx->tunnel.resp)
798     return NGHTTP2_ERR_CALLBACK_FAILURE;
799 
800   result = Curl_dynhds_add(&ctx->tunnel.resp->headers,
801                            (const char *)name, namelen,
802                            (const char *)value, valuelen);
803   if(result)
804     return NGHTTP2_ERR_CALLBACK_FAILURE;
805 
806   CURL_TRC_CF(data, cf, "[%d] header: %.*s: %.*s",
807               stream_id, (int)namelen, name, (int)valuelen, value);
808 
809   return 0; /* 0 is successful */
810 }
811 
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)812 static ssize_t tunnel_send_callback(nghttp2_session *session,
813                                     int32_t stream_id,
814                                     uint8_t *buf, size_t length,
815                                     uint32_t *data_flags,
816                                     nghttp2_data_source *source,
817                                     void *userp)
818 {
819   struct Curl_cfilter *cf = userp;
820   struct cf_h2_proxy_ctx *ctx = cf->ctx;
821   struct Curl_easy *data = CF_DATA_CURRENT(cf);
822   struct tunnel_stream *ts;
823   CURLcode result;
824   ssize_t nread;
825 
826   (void)source;
827   (void)data;
828   (void)ctx;
829 
830   if(!stream_id)
831     return NGHTTP2_ERR_INVALID_ARGUMENT;
832 
833   ts = nghttp2_session_get_stream_user_data(session, stream_id);
834   if(!ts)
835     return NGHTTP2_ERR_CALLBACK_FAILURE;
836   DEBUGASSERT(ts == &ctx->tunnel);
837 
838   nread = Curl_bufq_read(&ts->sendbuf, buf, length, &result);
839   if(nread < 0) {
840     if(result != CURLE_AGAIN)
841       return NGHTTP2_ERR_CALLBACK_FAILURE;
842     return NGHTTP2_ERR_DEFERRED;
843   }
844   if(ts->closed && Curl_bufq_is_empty(&ts->sendbuf))
845     *data_flags = NGHTTP2_DATA_FLAG_EOF;
846 
847   CURL_TRC_CF(data, cf, "[%d] tunnel_send_callback -> %zd",
848               ts->stream_id, nread);
849   return nread;
850 }
851 
tunnel_recv_callback(nghttp2_session * session,uint8_t flags,int32_t stream_id,const uint8_t * mem,size_t len,void * userp)852 static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags,
853                                 int32_t stream_id,
854                                 const uint8_t *mem, size_t len, void *userp)
855 {
856   struct Curl_cfilter *cf = userp;
857   struct cf_h2_proxy_ctx *ctx = cf->ctx;
858   ssize_t nwritten;
859   CURLcode result;
860 
861   (void)flags;
862   (void)session;
863   DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
864 
865   if(stream_id != ctx->tunnel.stream_id)
866     return NGHTTP2_ERR_CALLBACK_FAILURE;
867 
868   nwritten = Curl_bufq_write(&ctx->tunnel.recvbuf, mem, len, &result);
869   if(nwritten < 0) {
870     if(result != CURLE_AGAIN)
871       return NGHTTP2_ERR_CALLBACK_FAILURE;
872     nwritten = 0;
873   }
874   DEBUGASSERT((size_t)nwritten == len);
875   return 0;
876 }
877 
proxy_h2_on_stream_close(nghttp2_session * session,int32_t stream_id,uint32_t error_code,void * userp)878 static int proxy_h2_on_stream_close(nghttp2_session *session,
879                                     int32_t stream_id,
880                                     uint32_t error_code, void *userp)
881 {
882   struct Curl_cfilter *cf = userp;
883   struct cf_h2_proxy_ctx *ctx = cf->ctx;
884   struct Curl_easy *data = CF_DATA_CURRENT(cf);
885 
886   (void)session;
887   (void)data;
888 
889   if(stream_id != ctx->tunnel.stream_id)
890     return 0;
891 
892   CURL_TRC_CF(data, cf, "[%d] proxy_h2_on_stream_close, %s (err %d)",
893               stream_id, nghttp2_http2_strerror(error_code), error_code);
894   ctx->tunnel.closed = TRUE;
895   ctx->tunnel.error = error_code;
896 
897   return 0;
898 }
899 
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)900 static CURLcode proxy_h2_submit(int32_t *pstream_id,
901                                 struct Curl_cfilter *cf,
902                                 struct Curl_easy *data,
903                                 nghttp2_session *h2,
904                                 struct httpreq *req,
905                                 const nghttp2_priority_spec *pri_spec,
906                                 void *stream_user_data,
907                                nghttp2_data_source_read_callback read_callback,
908                                 void *read_ctx)
909 {
910   struct dynhds h2_headers;
911   nghttp2_nv *nva = NULL;
912   unsigned int i;
913   int32_t stream_id = -1;
914   size_t nheader;
915   CURLcode result;
916 
917   (void)cf;
918   Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
919   result = Curl_http_req_to_h2(&h2_headers, req, data);
920   if(result)
921     goto out;
922 
923   nheader = Curl_dynhds_count(&h2_headers);
924   nva = malloc(sizeof(nghttp2_nv) * nheader);
925   if(!nva) {
926     result = CURLE_OUT_OF_MEMORY;
927     goto out;
928   }
929 
930   for(i = 0; i < nheader; ++i) {
931     struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
932     nva[i].name = (unsigned char *)e->name;
933     nva[i].namelen = e->namelen;
934     nva[i].value = (unsigned char *)e->value;
935     nva[i].valuelen = e->valuelen;
936     nva[i].flags = NGHTTP2_NV_FLAG_NONE;
937   }
938 
939   if(read_callback) {
940     nghttp2_data_provider data_prd;
941 
942     data_prd.read_callback = read_callback;
943     data_prd.source.ptr = read_ctx;
944     stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
945                                        &data_prd, stream_user_data);
946   }
947   else {
948     stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
949                                        NULL, stream_user_data);
950   }
951 
952   if(stream_id < 0) {
953     failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
954           nghttp2_strerror(stream_id), stream_id);
955     result = CURLE_SEND_ERROR;
956     goto out;
957   }
958   result = CURLE_OK;
959 
960 out:
961   free(nva);
962   Curl_dynhds_free(&h2_headers);
963   *pstream_id = stream_id;
964   return result;
965 }
966 
submit_CONNECT(struct Curl_cfilter * cf,struct Curl_easy * data,struct tunnel_stream * ts)967 static CURLcode submit_CONNECT(struct Curl_cfilter *cf,
968                                struct Curl_easy *data,
969                                struct tunnel_stream *ts)
970 {
971   struct cf_h2_proxy_ctx *ctx = cf->ctx;
972   CURLcode result;
973   struct httpreq *req = NULL;
974 
975   result = Curl_http_proxy_create_CONNECT(&req, cf, data, 2);
976   if(result)
977     goto out;
978 
979   infof(data, "Establish HTTP/2 proxy tunnel to %s", req->authority);
980 
981   result = proxy_h2_submit(&ts->stream_id, cf, data, ctx->h2, req,
982                            NULL, ts, tunnel_send_callback, cf);
983   if(result) {
984     CURL_TRC_CF(data, cf, "[%d] send, nghttp2_submit_request error: %s",
985                 ts->stream_id, nghttp2_strerror(ts->stream_id));
986   }
987 
988 out:
989   if(req)
990     Curl_http_req_free(req);
991   if(result)
992     failf(data, "Failed sending CONNECT to proxy");
993   return result;
994 }
995 
inspect_response(struct Curl_cfilter * cf,struct Curl_easy * data,struct tunnel_stream * ts)996 static CURLcode inspect_response(struct Curl_cfilter *cf,
997                                  struct Curl_easy *data,
998                                  struct tunnel_stream *ts)
999 {
1000   CURLcode result = CURLE_OK;
1001   struct dynhds_entry *auth_reply = NULL;
1002   (void)cf;
1003 
1004   DEBUGASSERT(ts->resp);
1005   if(ts->resp->status/100 == 2) {
1006     infof(data, "CONNECT tunnel established, response %d", ts->resp->status);
1007     h2_tunnel_go_state(cf, ts, H2_TUNNEL_ESTABLISHED, data);
1008     return CURLE_OK;
1009   }
1010 
1011   if(ts->resp->status == 401) {
1012     auth_reply = Curl_dynhds_cget(&ts->resp->headers, "WWW-Authenticate");
1013   }
1014   else if(ts->resp->status == 407) {
1015     auth_reply = Curl_dynhds_cget(&ts->resp->headers, "Proxy-Authenticate");
1016   }
1017 
1018   if(auth_reply) {
1019     CURL_TRC_CF(data, cf, "[0] CONNECT: fwd auth header '%s'",
1020                 auth_reply->value);
1021     result = Curl_http_input_auth(data, ts->resp->status == 407,
1022                                   auth_reply->value);
1023     if(result)
1024       return result;
1025     if(data->req.newurl) {
1026       /* Indicator that we should try again */
1027       Curl_safefree(data->req.newurl);
1028       h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data);
1029       return CURLE_OK;
1030     }
1031   }
1032 
1033   /* Seems to have failed */
1034   return CURLE_RECV_ERROR;
1035 }
1036 
H2_CONNECT(struct Curl_cfilter * cf,struct Curl_easy * data,struct tunnel_stream * ts)1037 static CURLcode H2_CONNECT(struct Curl_cfilter *cf,
1038                            struct Curl_easy *data,
1039                            struct tunnel_stream *ts)
1040 {
1041   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1042   CURLcode result = CURLE_OK;
1043 
1044   DEBUGASSERT(ts);
1045   DEBUGASSERT(ts->authority);
1046   do {
1047     switch(ts->state) {
1048     case H2_TUNNEL_INIT:
1049       /* Prepare the CONNECT request and make a first attempt to send. */
1050       CURL_TRC_CF(data, cf, "[0] CONNECT start for %s", ts->authority);
1051       result = submit_CONNECT(cf, data, ts);
1052       if(result)
1053         goto out;
1054       h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data);
1055       /* FALLTHROUGH */
1056 
1057     case H2_TUNNEL_CONNECT:
1058       /* see that the request is completely sent */
1059       result = proxy_h2_progress_ingress(cf, data);
1060       if(!result)
1061         result = proxy_h2_progress_egress(cf, data);
1062       if(result && result != CURLE_AGAIN) {
1063         h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
1064         break;
1065       }
1066 
1067       if(ts->has_final_response) {
1068         h2_tunnel_go_state(cf, ts, H2_TUNNEL_RESPONSE, data);
1069       }
1070       else {
1071         result = CURLE_OK;
1072         goto out;
1073       }
1074       /* FALLTHROUGH */
1075 
1076     case H2_TUNNEL_RESPONSE:
1077       DEBUGASSERT(ts->has_final_response);
1078       result = inspect_response(cf, data, ts);
1079       if(result)
1080         goto out;
1081       break;
1082 
1083     case H2_TUNNEL_ESTABLISHED:
1084       return CURLE_OK;
1085 
1086     case H2_TUNNEL_FAILED:
1087       return CURLE_RECV_ERROR;
1088 
1089     default:
1090       break;
1091     }
1092 
1093   } while(ts->state == H2_TUNNEL_INIT);
1094 
1095 out:
1096   if(result || ctx->tunnel.closed)
1097     h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
1098   return result;
1099 }
1100 
cf_h2_proxy_connect(struct Curl_cfilter * cf,struct Curl_easy * data,bool blocking,bool * done)1101 static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf,
1102                                     struct Curl_easy *data,
1103                                     bool blocking, bool *done)
1104 {
1105   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1106   CURLcode result = CURLE_OK;
1107   struct cf_call_data save;
1108   timediff_t check;
1109   struct tunnel_stream *ts = &ctx->tunnel;
1110 
1111   if(cf->connected) {
1112     *done = TRUE;
1113     return CURLE_OK;
1114   }
1115 
1116   /* Connect the lower filters first */
1117   if(!cf->next->connected) {
1118     result = Curl_conn_cf_connect(cf->next, data, blocking, done);
1119     if(result || !*done)
1120       return result;
1121   }
1122 
1123   *done = FALSE;
1124 
1125   CF_DATA_SAVE(save, cf, data);
1126   if(!ctx->h2) {
1127     result = cf_h2_proxy_ctx_init(cf, data);
1128     if(result)
1129       goto out;
1130   }
1131   DEBUGASSERT(ts->authority);
1132 
1133   check = Curl_timeleft(data, NULL, TRUE);
1134   if(check <= 0) {
1135     failf(data, "Proxy CONNECT aborted due to timeout");
1136     result = CURLE_OPERATION_TIMEDOUT;
1137     goto out;
1138   }
1139 
1140   /* for the secondary socket (FTP), use the "connect to host"
1141    * but ignore the "connect to port" (use the secondary port)
1142    */
1143   result = H2_CONNECT(cf, data, ts);
1144 
1145 out:
1146   *done = (result == CURLE_OK) && (ts->state == H2_TUNNEL_ESTABLISHED);
1147   cf->connected = *done;
1148   CF_DATA_RESTORE(cf, save);
1149   return result;
1150 }
1151 
cf_h2_proxy_close(struct Curl_cfilter * cf,struct Curl_easy * data)1152 static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data)
1153 {
1154   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1155 
1156   if(ctx) {
1157     struct cf_call_data save;
1158 
1159     CF_DATA_SAVE(save, cf, data);
1160     cf_h2_proxy_ctx_clear(ctx);
1161     CF_DATA_RESTORE(cf, save);
1162   }
1163   if(cf->next)
1164     cf->next->cft->do_close(cf->next, data);
1165 }
1166 
cf_h2_proxy_destroy(struct Curl_cfilter * cf,struct Curl_easy * data)1167 static void cf_h2_proxy_destroy(struct Curl_cfilter *cf,
1168                                 struct Curl_easy *data)
1169 {
1170   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1171 
1172   (void)data;
1173   if(ctx) {
1174     cf_h2_proxy_ctx_free(ctx);
1175     cf->ctx = NULL;
1176   }
1177 }
1178 
cf_h2_proxy_data_pending(struct Curl_cfilter * cf,const struct Curl_easy * data)1179 static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf,
1180                                      const struct Curl_easy *data)
1181 {
1182   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1183   if((ctx && !Curl_bufq_is_empty(&ctx->inbufq)) ||
1184      (ctx && ctx->tunnel.state == H2_TUNNEL_ESTABLISHED &&
1185       !Curl_bufq_is_empty(&ctx->tunnel.recvbuf)))
1186     return TRUE;
1187   return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
1188 }
1189 
cf_h2_proxy_get_select_socks(struct Curl_cfilter * cf,struct Curl_easy * data,curl_socket_t * sock)1190 static int cf_h2_proxy_get_select_socks(struct Curl_cfilter *cf,
1191                                         struct Curl_easy *data,
1192                                         curl_socket_t *sock)
1193 {
1194   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1195   int bitmap = GETSOCK_BLANK;
1196   struct cf_call_data save;
1197 
1198   CF_DATA_SAVE(save, cf, data);
1199   sock[0] = Curl_conn_cf_get_socket(cf, data);
1200   bitmap |= GETSOCK_READSOCK(0);
1201 
1202   /* HTTP/2 layer wants to send data) AND there's a window to send data in */
1203   if(nghttp2_session_want_write(ctx->h2) &&
1204      nghttp2_session_get_remote_window_size(ctx->h2))
1205     bitmap |= GETSOCK_WRITESOCK(0);
1206 
1207   CF_DATA_RESTORE(cf, save);
1208   return bitmap;
1209 }
1210 
h2_handle_tunnel_close(struct Curl_cfilter * cf,struct Curl_easy * data,CURLcode * err)1211 static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf,
1212                                       struct Curl_easy *data,
1213                                       CURLcode *err)
1214 {
1215   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1216   ssize_t rv = 0;
1217 
1218   if(ctx->tunnel.error == NGHTTP2_REFUSED_STREAM) {
1219     CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new "
1220                 "connection", ctx->tunnel.stream_id);
1221     connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */
1222     *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
1223     return -1;
1224   }
1225   else if(ctx->tunnel.error != NGHTTP2_NO_ERROR) {
1226     failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)",
1227           ctx->tunnel.stream_id, nghttp2_http2_strerror(ctx->tunnel.error),
1228           ctx->tunnel.error);
1229     *err = CURLE_HTTP2_STREAM;
1230     return -1;
1231   }
1232   else if(ctx->tunnel.reset) {
1233     failf(data, "HTTP/2 stream %u was reset", ctx->tunnel.stream_id);
1234     *err = CURLE_RECV_ERROR;
1235     return -1;
1236   }
1237 
1238   *err = CURLE_OK;
1239   rv = 0;
1240   CURL_TRC_CF(data, cf, "[%d] handle_tunnel_close -> %zd, %d",
1241               ctx->tunnel.stream_id, rv, *err);
1242   return rv;
1243 }
1244 
tunnel_recv(struct Curl_cfilter * cf,struct Curl_easy * data,char * buf,size_t len,CURLcode * err)1245 static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
1246                            char *buf, size_t len, CURLcode *err)
1247 {
1248   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1249   ssize_t nread = -1;
1250 
1251   *err = CURLE_AGAIN;
1252   if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
1253     nread = Curl_bufq_read(&ctx->tunnel.recvbuf,
1254                            (unsigned char *)buf, len, err);
1255     if(nread < 0)
1256       goto out;
1257     DEBUGASSERT(nread > 0);
1258   }
1259 
1260   if(nread < 0) {
1261     if(ctx->tunnel.closed) {
1262       nread = h2_handle_tunnel_close(cf, data, err);
1263     }
1264     else if(ctx->tunnel.reset ||
1265             (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
1266             (ctx->goaway && ctx->last_stream_id < ctx->tunnel.stream_id)) {
1267       *err = CURLE_RECV_ERROR;
1268       nread = -1;
1269     }
1270   }
1271   else if(nread == 0) {
1272     *err = CURLE_AGAIN;
1273     nread = -1;
1274   }
1275 
1276 out:
1277   CURL_TRC_CF(data, cf, "[%d] tunnel_recv(len=%zu) -> %zd, %d",
1278               ctx->tunnel.stream_id, len, nread, *err);
1279   return nread;
1280 }
1281 
cf_h2_proxy_recv(struct Curl_cfilter * cf,struct Curl_easy * data,char * buf,size_t len,CURLcode * err)1282 static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf,
1283                                 struct Curl_easy *data,
1284                                 char *buf, size_t len, CURLcode *err)
1285 {
1286   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1287   ssize_t nread = -1;
1288   struct cf_call_data save;
1289   CURLcode result;
1290 
1291   if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
1292     *err = CURLE_RECV_ERROR;
1293     return -1;
1294   }
1295   CF_DATA_SAVE(save, cf, data);
1296 
1297   if(Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
1298     *err = proxy_h2_progress_ingress(cf, data);
1299     if(*err)
1300       goto out;
1301   }
1302 
1303   nread = tunnel_recv(cf, data, buf, len, err);
1304 
1305   if(nread > 0) {
1306     CURL_TRC_CF(data, cf, "[%d] increase window by %zd",
1307                 ctx->tunnel.stream_id, nread);
1308     nghttp2_session_consume(ctx->h2, ctx->tunnel.stream_id, (size_t)nread);
1309   }
1310 
1311   result = proxy_h2_progress_egress(cf, data);
1312   if(result == CURLE_AGAIN) {
1313     /* pending data to send, need to be called again. Ideally, we'd
1314      * monitor the socket for POLLOUT, but we might not be in SENDING
1315      * transfer state any longer and are unable to make this happen.
1316      */
1317     CURL_TRC_CF(data, cf, "[%d] egress blocked, DRAIN",
1318                 ctx->tunnel.stream_id);
1319     drain_tunnel(cf, data, &ctx->tunnel);
1320   }
1321   else if(result) {
1322     *err = result;
1323     nread = -1;
1324   }
1325 
1326 out:
1327   if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) &&
1328      (nread >= 0 || *err == CURLE_AGAIN)) {
1329     /* data pending and no fatal error to report. Need to trigger
1330      * draining to avoid stalling when no socket events happen. */
1331     drain_tunnel(cf, data, &ctx->tunnel);
1332   }
1333   CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d",
1334               ctx->tunnel.stream_id, len, nread, *err);
1335   CF_DATA_RESTORE(cf, save);
1336   return nread;
1337 }
1338 
cf_h2_proxy_send(struct Curl_cfilter * cf,struct Curl_easy * data,const void * buf,size_t len,CURLcode * err)1339 static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf,
1340                                 struct Curl_easy *data,
1341                                 const void *buf, size_t len, CURLcode *err)
1342 {
1343   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1344   struct cf_call_data save;
1345   int rv;
1346   ssize_t nwritten;
1347   CURLcode result;
1348   int blocked = 0;
1349 
1350   if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
1351     *err = CURLE_SEND_ERROR;
1352     return -1;
1353   }
1354   CF_DATA_SAVE(save, cf, data);
1355 
1356   if(ctx->tunnel.closed) {
1357     nwritten = -1;
1358     *err = CURLE_SEND_ERROR;
1359     goto out;
1360   }
1361   else if(ctx->tunnel.upload_blocked_len) {
1362     /* the data in `buf` has already been submitted or added to the
1363      * buffers, but have been EAGAINed on the last invocation. */
1364     DEBUGASSERT(len >= ctx->tunnel.upload_blocked_len);
1365     if(len < ctx->tunnel.upload_blocked_len) {
1366       /* Did we get called again with a smaller `len`? This should not
1367        * happen. We are not prepared to handle that. */
1368       failf(data, "HTTP/2 proxy, send again with decreased length");
1369       *err = CURLE_HTTP2;
1370       nwritten = -1;
1371       goto out;
1372     }
1373     nwritten = (ssize_t)ctx->tunnel.upload_blocked_len;
1374     ctx->tunnel.upload_blocked_len = 0;
1375     *err = CURLE_OK;
1376   }
1377   else {
1378     nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err);
1379     if(nwritten < 0) {
1380       if(*err != CURLE_AGAIN)
1381         goto out;
1382       nwritten = 0;
1383     }
1384   }
1385 
1386   if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
1387     /* req body data is buffered, resume the potentially suspended stream */
1388     rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
1389     if(nghttp2_is_fatal(rv)) {
1390       *err = CURLE_SEND_ERROR;
1391       nwritten = -1;
1392       goto out;
1393     }
1394   }
1395 
1396   result = proxy_h2_progress_ingress(cf, data);
1397   if(result) {
1398     *err = result;
1399     nwritten = -1;
1400     goto out;
1401   }
1402 
1403   /* Call the nghttp2 send loop and flush to write ALL buffered data,
1404    * headers and/or request body completely out to the network */
1405   result = proxy_h2_progress_egress(cf, data);
1406   if(result == CURLE_AGAIN) {
1407     blocked = 1;
1408   }
1409   else if(result) {
1410     *err = result;
1411     nwritten = -1;
1412     goto out;
1413   }
1414   else if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
1415     /* although we wrote everything that nghttp2 wants to send now,
1416      * there is data left in our stream send buffer unwritten. This may
1417      * be due to the stream's HTTP/2 flow window being exhausted. */
1418     blocked = 1;
1419   }
1420 
1421   if(blocked) {
1422     /* Unable to send all data, due to connection blocked or H2 window
1423      * exhaustion. Data is left in our stream buffer, or nghttp2's internal
1424      * frame buffer or our network out buffer. */
1425     size_t rwin = nghttp2_session_get_stream_remote_window_size(
1426                     ctx->h2, ctx->tunnel.stream_id);
1427     if(rwin == 0) {
1428       /* H2 flow window exhaustion.
1429        * FIXME: there is no way to HOLD all transfers that use this
1430        * proxy connection AND to UNHOLD all of them again when the
1431        * window increases.
1432        * We *could* iterate over all data on this conn maybe? */
1433       CURL_TRC_CF(data, cf, "[%d] remote flow "
1434                   "window is exhausted", ctx->tunnel.stream_id);
1435     }
1436 
1437     /* Whatever the cause, we need to return CURL_EAGAIN for this call.
1438      * We have unwritten state that needs us being invoked again and EAGAIN
1439      * is the only way to ensure that. */
1440     ctx->tunnel.upload_blocked_len = nwritten;
1441     CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) BLOCK: win %u/%zu "
1442                 "blocked_len=%zu",
1443                 ctx->tunnel.stream_id, len,
1444                 nghttp2_session_get_remote_window_size(ctx->h2), rwin,
1445                 nwritten);
1446     drain_tunnel(cf, data, &ctx->tunnel);
1447     *err = CURLE_AGAIN;
1448     nwritten = -1;
1449     goto out;
1450   }
1451   else 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 
proxy_h2_connisalive(struct Curl_cfilter * cf,struct Curl_easy * data,bool * input_pending)1484 static bool proxy_h2_connisalive(struct Curl_cfilter *cf,
1485                                  struct Curl_easy *data,
1486                                  bool *input_pending)
1487 {
1488   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1489   bool alive = TRUE;
1490 
1491   *input_pending = FALSE;
1492   if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
1493     return FALSE;
1494 
1495   if(*input_pending) {
1496     /* This happens before we've sent off a request and the connection is
1497        not in use by any other transfer, there shouldn't be any data here,
1498        only "protocol frames" */
1499     CURLcode result;
1500     ssize_t nread = -1;
1501 
1502     *input_pending = FALSE;
1503     nread = Curl_bufq_slurp(&ctx->inbufq, proxy_nw_in_reader, cf, &result);
1504     if(nread != -1) {
1505       if(proxy_h2_process_pending_input(cf, data, &result) < 0)
1506         /* immediate error, considered dead */
1507         alive = FALSE;
1508       else {
1509         alive = !proxy_h2_should_close_session(ctx);
1510       }
1511     }
1512     else if(result != CURLE_AGAIN) {
1513       /* the read failed so let's say this is dead anyway */
1514       alive = FALSE;
1515     }
1516   }
1517 
1518   return alive;
1519 }
1520 
cf_h2_proxy_is_alive(struct Curl_cfilter * cf,struct Curl_easy * data,bool * input_pending)1521 static bool cf_h2_proxy_is_alive(struct Curl_cfilter *cf,
1522                                  struct Curl_easy *data,
1523                                  bool *input_pending)
1524 {
1525   struct cf_h2_proxy_ctx *ctx = cf->ctx;
1526   CURLcode result;
1527   struct cf_call_data save;
1528 
1529   CF_DATA_SAVE(save, cf, data);
1530   result = (ctx && ctx->h2 && proxy_h2_connisalive(cf, data, input_pending));
1531   CURL_TRC_CF(data, cf, "[0] conn alive -> %d, input_pending=%d",
1532               result, *input_pending);
1533   CF_DATA_RESTORE(cf, save);
1534   return result;
1535 }
1536 
1537 struct Curl_cftype Curl_cft_h2_proxy = {
1538   "H2-PROXY",
1539   CF_TYPE_IP_CONNECT,
1540   CURL_LOG_LVL_NONE,
1541   cf_h2_proxy_destroy,
1542   cf_h2_proxy_connect,
1543   cf_h2_proxy_close,
1544   Curl_cf_http_proxy_get_host,
1545   cf_h2_proxy_get_select_socks,
1546   cf_h2_proxy_data_pending,
1547   cf_h2_proxy_send,
1548   cf_h2_proxy_recv,
1549   Curl_cf_def_cntrl,
1550   cf_h2_proxy_is_alive,
1551   Curl_cf_def_conn_keep_alive,
1552   Curl_cf_def_query,
1553 };
1554 
Curl_cf_h2_proxy_insert_after(struct Curl_cfilter * cf,struct Curl_easy * data)1555 CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf,
1556                                        struct Curl_easy *data)
1557 {
1558   struct Curl_cfilter *cf_h2_proxy = NULL;
1559   struct cf_h2_proxy_ctx *ctx;
1560   CURLcode result = CURLE_OUT_OF_MEMORY;
1561 
1562   (void)data;
1563   ctx = calloc(sizeof(*ctx), 1);
1564   if(!ctx)
1565     goto out;
1566 
1567   result = Curl_cf_create(&cf_h2_proxy, &Curl_cft_h2_proxy, ctx);
1568   if(result)
1569     goto out;
1570 
1571   Curl_conn_cf_insert_after(cf, cf_h2_proxy);
1572   result = CURLE_OK;
1573 
1574 out:
1575   if(result)
1576     cf_h2_proxy_ctx_free(ctx);
1577   return result;
1578 }
1579 
1580 #endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */
1581