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