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