Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/external/curl/lib/cf-h2-proxy.c
2065 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(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 "curlx/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
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
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
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
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
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
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
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
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
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
*/
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
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
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
*/
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
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
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
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
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 = CURL_ARRAYSIZE(scratch);
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
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
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
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
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
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
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
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
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
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
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
1091
static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf,
1092
struct Curl_easy *data,
1093
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, 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
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
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
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
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
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
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
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
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
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;
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
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
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
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
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
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
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
1665