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