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