1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25 #include "curl_setup.h"
26
27 #ifdef USE_MSH3
28
29 #include "urldata.h"
30 #include "hash.h"
31 #include "timeval.h"
32 #include "multiif.h"
33 #include "sendf.h"
34 #include "curl_trc.h"
35 #include "cfilters.h"
36 #include "cf-socket.h"
37 #include "connect.h"
38 #include "progress.h"
39 #include "http1.h"
40 #include "curl_msh3.h"
41 #include "socketpair.h"
42 #include "vtls/vtls.h"
43 #include "vquic/vquic.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 #ifdef CURL_DISABLE_SOCKETPAIR
51 #error "MSH3 cannot be build with CURL_DISABLE_SOCKETPAIR set"
52 #endif
53
54 #define H3_STREAM_WINDOW_SIZE (128 * 1024)
55 #define H3_STREAM_CHUNK_SIZE (16 * 1024)
56 #define H3_STREAM_RECV_CHUNKS \
57 (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE)
58
59 #ifdef _WIN32
60 #define msh3_lock CRITICAL_SECTION
61 #define msh3_lock_initialize(lock) InitializeCriticalSection(lock)
62 #define msh3_lock_uninitialize(lock) DeleteCriticalSection(lock)
63 #define msh3_lock_acquire(lock) EnterCriticalSection(lock)
64 #define msh3_lock_release(lock) LeaveCriticalSection(lock)
65 #else /* !_WIN32 */
66 #include <pthread.h>
67 #define msh3_lock pthread_mutex_t
68 #define msh3_lock_initialize(lock) do { \
69 pthread_mutexattr_t attr; \
70 pthread_mutexattr_init(&attr); \
71 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \
72 pthread_mutex_init(lock, &attr); \
73 pthread_mutexattr_destroy(&attr); \
74 }while(0)
75 #define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock)
76 #define msh3_lock_acquire(lock) pthread_mutex_lock(lock)
77 #define msh3_lock_release(lock) pthread_mutex_unlock(lock)
78 #endif /* _WIN32 */
79
80
81 static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection,
82 void *IfContext);
83 static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection,
84 void *IfContext);
85 static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection,
86 void *IfContext,
87 MSH3_REQUEST *Request);
88 static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
89 void *IfContext,
90 const MSH3_HEADER *Header);
91 static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
92 void *IfContext, uint32_t *Length,
93 const uint8_t *Data);
94 static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
95 bool Aborted, uint64_t AbortError);
96 static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request,
97 void *IfContext);
98 static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request,
99 void *IfContext, void *SendContext);
100
101
Curl_msh3_ver(char * p,size_t len)102 void Curl_msh3_ver(char *p, size_t len)
103 {
104 uint32_t v[4];
105 MsH3Version(v);
106 (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]);
107 }
108
109 #define SP_LOCAL 0
110 #define SP_REMOTE 1
111
112 struct cf_msh3_ctx {
113 MSH3_API *api;
114 MSH3_CONNECTION *qconn;
115 struct Curl_sockaddr_ex addr;
116 curl_socket_t sock[2]; /* fake socket pair until we get support in msh3 */
117 char l_ip[MAX_IPADR_LEN]; /* local IP as string */
118 int l_port; /* local port number */
119 struct cf_call_data call_data;
120 struct curltime connect_started; /* time the current attempt started */
121 struct curltime handshake_at; /* time connect handshake finished */
122 struct Curl_hash streams; /* hash `data->id` to `stream_ctx` */
123 /* Flags written by msh3/msquic thread */
124 bool handshake_complete;
125 bool handshake_succeeded;
126 bool connected;
127 /* Flags written by curl thread */
128 BIT(verbose);
129 BIT(active);
130 };
131
132 static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data);
133
134 /* How to access `call_data` from a cf_msh3 filter */
135 #undef CF_CTX_CALL_DATA
136 #define CF_CTX_CALL_DATA(cf) \
137 ((struct cf_msh3_ctx *)(cf)->ctx)->call_data
138
139 /**
140 * All about the H3 internals of a stream
141 */
142 struct stream_ctx {
143 struct MSH3_REQUEST *req;
144 struct bufq recvbuf; /* h3 response */
145 #ifdef _WIN32
146 CRITICAL_SECTION recv_lock;
147 #else /* !_WIN32 */
148 pthread_mutex_t recv_lock;
149 #endif /* _WIN32 */
150 uint64_t error3; /* HTTP/3 stream error code */
151 int status_code; /* HTTP status code */
152 CURLcode recv_error;
153 bool closed;
154 bool reset;
155 bool upload_done;
156 bool firstheader; /* FALSE until headers arrive */
157 bool recv_header_complete;
158 };
159
160 #define H3_STREAM_CTX(ctx,data) ((struct stream_ctx *)((data && ctx)? \
161 Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL))
162
h3_stream_ctx_free(struct stream_ctx * stream)163 static void h3_stream_ctx_free(struct stream_ctx *stream)
164 {
165 Curl_bufq_free(&stream->recvbuf);
166 free(stream);
167 }
168
h3_stream_hash_free(void * stream)169 static void h3_stream_hash_free(void *stream)
170 {
171 DEBUGASSERT(stream);
172 h3_stream_ctx_free((struct stream_ctx *)stream);
173 }
174
h3_data_setup(struct Curl_cfilter * cf,struct Curl_easy * data)175 static CURLcode h3_data_setup(struct Curl_cfilter *cf,
176 struct Curl_easy *data)
177 {
178 struct cf_msh3_ctx *ctx = cf->ctx;
179 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
180
181 if(stream)
182 return CURLE_OK;
183
184 stream = calloc(1, sizeof(*stream));
185 if(!stream)
186 return CURLE_OUT_OF_MEMORY;
187
188 stream->req = ZERO_NULL;
189 msh3_lock_initialize(&stream->recv_lock);
190 Curl_bufq_init2(&stream->recvbuf, H3_STREAM_CHUNK_SIZE,
191 H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
192 CURL_TRC_CF(data, cf, "data setup");
193
194 if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) {
195 h3_stream_ctx_free(stream);
196 return CURLE_OUT_OF_MEMORY;
197 }
198
199 return CURLE_OK;
200 }
201
h3_data_done(struct Curl_cfilter * cf,struct Curl_easy * data)202 static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
203 {
204 struct cf_msh3_ctx *ctx = cf->ctx;
205 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
206
207 (void)cf;
208 if(stream) {
209 CURL_TRC_CF(data, cf, "easy handle is done");
210 Curl_hash_offt_remove(&ctx->streams, data->id);
211 }
212 }
213
drain_stream_from_other_thread(struct Curl_easy * data,struct stream_ctx * stream)214 static void drain_stream_from_other_thread(struct Curl_easy *data,
215 struct stream_ctx *stream)
216 {
217 unsigned char bits;
218
219 /* risky */
220 bits = CURL_CSELECT_IN;
221 if(stream && !stream->upload_done)
222 bits |= CURL_CSELECT_OUT;
223 if(data->state.select_bits != bits) {
224 data->state.select_bits = bits;
225 /* cannot expire from other thread */
226 }
227 }
228
drain_stream(struct Curl_cfilter * cf,struct Curl_easy * data)229 static void drain_stream(struct Curl_cfilter *cf,
230 struct Curl_easy *data)
231 {
232 struct cf_msh3_ctx *ctx = cf->ctx;
233 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
234 unsigned char bits;
235
236 (void)cf;
237 bits = CURL_CSELECT_IN;
238 if(stream && !stream->upload_done)
239 bits |= CURL_CSELECT_OUT;
240 if(data->state.select_bits != bits) {
241 data->state.select_bits = bits;
242 Curl_expire(data, 0, EXPIRE_RUN_NOW);
243 }
244 }
245
246 static const MSH3_CONNECTION_IF msh3_conn_if = {
247 msh3_conn_connected,
248 msh3_conn_shutdown_complete,
249 msh3_conn_new_request
250 };
251
msh3_conn_connected(MSH3_CONNECTION * Connection,void * IfContext)252 static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection,
253 void *IfContext)
254 {
255 struct Curl_cfilter *cf = IfContext;
256 struct cf_msh3_ctx *ctx = cf->ctx;
257 struct Curl_easy *data = CF_DATA_CURRENT(cf);
258 (void)Connection;
259
260 CURL_TRC_CF(data, cf, "[MSH3] connected");
261 ctx->handshake_succeeded = true;
262 ctx->connected = true;
263 ctx->handshake_complete = true;
264 }
265
msh3_conn_shutdown_complete(MSH3_CONNECTION * Connection,void * IfContext)266 static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection,
267 void *IfContext)
268 {
269 struct Curl_cfilter *cf = IfContext;
270 struct cf_msh3_ctx *ctx = cf->ctx;
271 struct Curl_easy *data = CF_DATA_CURRENT(cf);
272
273 (void)Connection;
274 CURL_TRC_CF(data, cf, "[MSH3] shutdown complete");
275 ctx->connected = false;
276 ctx->handshake_complete = true;
277 }
278
msh3_conn_new_request(MSH3_CONNECTION * Connection,void * IfContext,MSH3_REQUEST * Request)279 static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection,
280 void *IfContext,
281 MSH3_REQUEST *Request)
282 {
283 (void)Connection;
284 (void)IfContext;
285 (void)Request;
286 }
287
288 static const MSH3_REQUEST_IF msh3_request_if = {
289 msh3_header_received,
290 msh3_data_received,
291 msh3_complete,
292 msh3_shutdown_complete,
293 msh3_data_sent
294 };
295
296 /* Decode HTTP status code. Returns -1 if no valid status code was
297 decoded. (duplicate from http2.c) */
decode_status_code(const char * value,size_t len)298 static int decode_status_code(const char *value, size_t len)
299 {
300 int i;
301 int res;
302
303 if(len != 3) {
304 return -1;
305 }
306
307 res = 0;
308
309 for(i = 0; i < 3; ++i) {
310 char c = value[i];
311
312 if(c < '0' || c > '9') {
313 return -1;
314 }
315
316 res *= 10;
317 res += c - '0';
318 }
319
320 return res;
321 }
322
323 /*
324 * write_resp_raw() copies response data in raw format to the `data`'s
325 * receive buffer. If not enough space is available, it appends to the
326 * `data`'s overflow buffer.
327 */
write_resp_raw(struct Curl_easy * data,const void * mem,size_t memlen)328 static CURLcode write_resp_raw(struct Curl_easy *data,
329 const void *mem, size_t memlen)
330 {
331 struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
332 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
333 CURLcode result = CURLE_OK;
334 ssize_t nwritten;
335
336 if(!stream)
337 return CURLE_RECV_ERROR;
338
339 nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result);
340 if(nwritten < 0) {
341 return result;
342 }
343
344 if((size_t)nwritten < memlen) {
345 /* This MUST not happen. Our recbuf is dimensioned to hold the
346 * full max_stream_window and then some for this very reason. */
347 DEBUGASSERT(0);
348 return CURLE_RECV_ERROR;
349 }
350 return result;
351 }
352
msh3_header_received(MSH3_REQUEST * Request,void * userp,const MSH3_HEADER * hd)353 static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
354 void *userp,
355 const MSH3_HEADER *hd)
356 {
357 struct Curl_easy *data = userp;
358 struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
359 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
360 CURLcode result;
361 (void)Request;
362
363 DEBUGF(infof(data, "[MSH3] header received, stream=%d", !!stream));
364 if(!stream || stream->recv_header_complete) {
365 return;
366 }
367
368 msh3_lock_acquire(&stream->recv_lock);
369
370 if((hd->NameLength == 7) &&
371 !strncmp(HTTP_PSEUDO_STATUS, (char *)hd->Name, 7)) {
372 char line[14]; /* status line is always 13 characters long */
373 size_t ncopy;
374
375 DEBUGASSERT(!stream->firstheader);
376 stream->status_code = decode_status_code(hd->Value, hd->ValueLength);
377 DEBUGASSERT(stream->status_code != -1);
378 ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n",
379 stream->status_code);
380 result = write_resp_raw(data, line, ncopy);
381 if(result)
382 stream->recv_error = result;
383 stream->firstheader = TRUE;
384 }
385 else {
386 /* store as an HTTP1-style header */
387 DEBUGASSERT(stream->firstheader);
388 result = write_resp_raw(data, hd->Name, hd->NameLength);
389 if(!result)
390 result = write_resp_raw(data, ": ", 2);
391 if(!result)
392 result = write_resp_raw(data, hd->Value, hd->ValueLength);
393 if(!result)
394 result = write_resp_raw(data, "\r\n", 2);
395 if(result) {
396 stream->recv_error = result;
397 }
398 }
399
400 drain_stream_from_other_thread(data, stream);
401 msh3_lock_release(&stream->recv_lock);
402 }
403
msh3_data_received(MSH3_REQUEST * Request,void * IfContext,uint32_t * buflen,const uint8_t * buf)404 static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
405 void *IfContext, uint32_t *buflen,
406 const uint8_t *buf)
407 {
408 struct Curl_easy *data = IfContext;
409 struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
410 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
411 CURLcode result;
412 bool rv = FALSE;
413
414 /* TODO: we would like to limit the amount of data we are buffer here.
415 * There seems to be no mechanism in msh3 to adjust flow control and
416 * it is undocumented what happens if we return FALSE here or less
417 * length (buflen is an inout parameter).
418 */
419 (void)Request;
420 if(!stream)
421 return FALSE;
422
423 msh3_lock_acquire(&stream->recv_lock);
424
425 if(!stream->recv_header_complete) {
426 result = write_resp_raw(data, "\r\n", 2);
427 if(result) {
428 stream->recv_error = result;
429 goto out;
430 }
431 stream->recv_header_complete = true;
432 }
433
434 result = write_resp_raw(data, buf, *buflen);
435 if(result) {
436 stream->recv_error = result;
437 }
438 rv = TRUE;
439
440 out:
441 msh3_lock_release(&stream->recv_lock);
442 return rv;
443 }
444
msh3_complete(MSH3_REQUEST * Request,void * IfContext,bool aborted,uint64_t error)445 static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
446 bool aborted, uint64_t error)
447 {
448 struct Curl_easy *data = IfContext;
449 struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
450 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
451
452 (void)Request;
453 if(!stream)
454 return;
455 msh3_lock_acquire(&stream->recv_lock);
456 stream->closed = TRUE;
457 stream->recv_header_complete = true;
458 if(error)
459 stream->error3 = error;
460 if(aborted)
461 stream->reset = TRUE;
462 msh3_lock_release(&stream->recv_lock);
463 }
464
msh3_shutdown_complete(MSH3_REQUEST * Request,void * IfContext)465 static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request,
466 void *IfContext)
467 {
468 struct Curl_easy *data = IfContext;
469 struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
470 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
471
472 if(!stream)
473 return;
474 (void)Request;
475 (void)stream;
476 }
477
msh3_data_sent(MSH3_REQUEST * Request,void * IfContext,void * SendContext)478 static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request,
479 void *IfContext, void *SendContext)
480 {
481 struct Curl_easy *data = IfContext;
482 struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data);
483 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
484 if(!stream)
485 return;
486 (void)Request;
487 (void)stream;
488 (void)SendContext;
489 }
490
recv_closed_stream(struct Curl_cfilter * cf,struct Curl_easy * data,CURLcode * err)491 static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
492 struct Curl_easy *data,
493 CURLcode *err)
494 {
495 struct cf_msh3_ctx *ctx = cf->ctx;
496 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
497 ssize_t nread = -1;
498
499 if(!stream) {
500 *err = CURLE_RECV_ERROR;
501 return -1;
502 }
503 (void)cf;
504 if(stream->reset) {
505 failf(data, "HTTP/3 stream reset by server");
506 *err = CURLE_PARTIAL_FILE;
507 CURL_TRC_CF(data, cf, "cf_recv, was reset -> %d", *err);
508 goto out;
509 }
510 else if(stream->error3) {
511 failf(data, "HTTP/3 stream was not closed cleanly: (error %zd)",
512 (ssize_t)stream->error3);
513 *err = CURLE_HTTP3;
514 CURL_TRC_CF(data, cf, "cf_recv, closed uncleanly -> %d", *err);
515 goto out;
516 }
517 else {
518 CURL_TRC_CF(data, cf, "cf_recv, closed ok -> %d", *err);
519 }
520 *err = CURLE_OK;
521 nread = 0;
522
523 out:
524 return nread;
525 }
526
set_quic_expire(struct Curl_cfilter * cf,struct Curl_easy * data)527 static void set_quic_expire(struct Curl_cfilter *cf, struct Curl_easy *data)
528 {
529 struct cf_msh3_ctx *ctx = cf->ctx;
530 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
531
532 /* we have no indication from msh3 when it would be a good time
533 * to juggle the connection again. So, we compromise by calling
534 * us again every some milliseconds. */
535 (void)cf;
536 if(stream && stream->req && !stream->closed) {
537 Curl_expire(data, 10, EXPIRE_QUIC);
538 }
539 else {
540 Curl_expire(data, 50, EXPIRE_QUIC);
541 }
542 }
543
cf_msh3_recv(struct Curl_cfilter * cf,struct Curl_easy * data,char * buf,size_t len,CURLcode * err)544 static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
545 char *buf, size_t len, CURLcode *err)
546 {
547 struct cf_msh3_ctx *ctx = cf->ctx;
548 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
549 ssize_t nread = -1;
550 struct cf_call_data save;
551
552 CURL_TRC_CF(data, cf, "cf_recv(len=%zu), stream=%d", len, !!stream);
553 if(!stream) {
554 *err = CURLE_RECV_ERROR;
555 return -1;
556 }
557 CF_DATA_SAVE(save, cf, data);
558
559 msh3_lock_acquire(&stream->recv_lock);
560
561 if(stream->recv_error) {
562 failf(data, "request aborted");
563 *err = stream->recv_error;
564 goto out;
565 }
566
567 *err = CURLE_OK;
568
569 if(!Curl_bufq_is_empty(&stream->recvbuf)) {
570 nread = Curl_bufq_read(&stream->recvbuf,
571 (unsigned char *)buf, len, err);
572 CURL_TRC_CF(data, cf, "read recvbuf(len=%zu) -> %zd, %d",
573 len, nread, *err);
574 if(nread < 0)
575 goto out;
576 if(stream->closed)
577 drain_stream(cf, data);
578 }
579 else if(stream->closed) {
580 nread = recv_closed_stream(cf, data, err);
581 goto out;
582 }
583 else {
584 CURL_TRC_CF(data, cf, "req: nothing here, call again");
585 *err = CURLE_AGAIN;
586 }
587
588 out:
589 msh3_lock_release(&stream->recv_lock);
590 set_quic_expire(cf, data);
591 CF_DATA_RESTORE(cf, save);
592 return nread;
593 }
594
cf_msh3_send(struct Curl_cfilter * cf,struct Curl_easy * data,const void * buf,size_t len,CURLcode * err)595 static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data,
596 const void *buf, size_t len, CURLcode *err)
597 {
598 struct cf_msh3_ctx *ctx = cf->ctx;
599 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
600 struct h1_req_parser h1;
601 struct dynhds h2_headers;
602 MSH3_HEADER *nva = NULL;
603 size_t nheader, i;
604 ssize_t nwritten = -1;
605 struct cf_call_data save;
606 bool eos;
607
608 CF_DATA_SAVE(save, cf, data);
609
610 Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
611 Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
612
613 /* Sizes must match for cast below to work" */
614 DEBUGASSERT(stream);
615 CURL_TRC_CF(data, cf, "req: send %zu bytes", len);
616
617 if(!stream->req) {
618 /* The first send on the request contains the headers and possibly some
619 data. Parse out the headers and create the request, then if there is
620 any data left over go ahead and send it too. */
621 nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err);
622 if(nwritten < 0)
623 goto out;
624 DEBUGASSERT(h1.done);
625 DEBUGASSERT(h1.req);
626
627 *err = Curl_http_req_to_h2(&h2_headers, h1.req, data);
628 if(*err) {
629 nwritten = -1;
630 goto out;
631 }
632
633 nheader = Curl_dynhds_count(&h2_headers);
634 nva = malloc(sizeof(MSH3_HEADER) * nheader);
635 if(!nva) {
636 *err = CURLE_OUT_OF_MEMORY;
637 nwritten = -1;
638 goto out;
639 }
640
641 for(i = 0; i < nheader; ++i) {
642 struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
643 nva[i].Name = e->name;
644 nva[i].NameLength = e->namelen;
645 nva[i].Value = e->value;
646 nva[i].ValueLength = e->valuelen;
647 }
648
649 switch(data->state.httpreq) {
650 case HTTPREQ_POST:
651 case HTTPREQ_POST_FORM:
652 case HTTPREQ_POST_MIME:
653 case HTTPREQ_PUT:
654 /* known request body size or -1 */
655 eos = FALSE;
656 break;
657 default:
658 /* there is not request body */
659 eos = TRUE;
660 stream->upload_done = TRUE;
661 break;
662 }
663
664 CURL_TRC_CF(data, cf, "req: send %zu headers", nheader);
665 stream->req = MsH3RequestOpen(ctx->qconn, &msh3_request_if, data,
666 nva, nheader,
667 eos ? MSH3_REQUEST_FLAG_FIN :
668 MSH3_REQUEST_FLAG_NONE);
669 if(!stream->req) {
670 failf(data, "request open failed");
671 *err = CURLE_SEND_ERROR;
672 goto out;
673 }
674 *err = CURLE_OK;
675 nwritten = len;
676 goto out;
677 }
678 else {
679 /* request is open */
680 CURL_TRC_CF(data, cf, "req: send %zu body bytes", len);
681 if(len > 0xFFFFFFFF) {
682 len = 0xFFFFFFFF;
683 }
684
685 if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_NONE, buf,
686 (uint32_t)len, stream)) {
687 *err = CURLE_SEND_ERROR;
688 goto out;
689 }
690
691 /* TODO - msh3/msquic will hold onto this memory until the send complete
692 event. How do we make sure curl doesn't free it until then? */
693 *err = CURLE_OK;
694 nwritten = len;
695 }
696
697 out:
698 set_quic_expire(cf, data);
699 free(nva);
700 Curl_h1_req_parse_free(&h1);
701 Curl_dynhds_free(&h2_headers);
702 CF_DATA_RESTORE(cf, save);
703 return nwritten;
704 }
705
cf_msh3_adjust_pollset(struct Curl_cfilter * cf,struct Curl_easy * data,struct easy_pollset * ps)706 static void cf_msh3_adjust_pollset(struct Curl_cfilter *cf,
707 struct Curl_easy *data,
708 struct easy_pollset *ps)
709 {
710 struct cf_msh3_ctx *ctx = cf->ctx;
711 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
712 struct cf_call_data save;
713
714 CF_DATA_SAVE(save, cf, data);
715 if(stream && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) {
716 if(stream->recv_error) {
717 Curl_pollset_add_in(data, ps, ctx->sock[SP_LOCAL]);
718 drain_stream(cf, data);
719 }
720 else if(stream->req) {
721 Curl_pollset_add_out(data, ps, ctx->sock[SP_LOCAL]);
722 drain_stream(cf, data);
723 }
724 }
725 }
726
cf_msh3_data_pending(struct Curl_cfilter * cf,const struct Curl_easy * data)727 static bool cf_msh3_data_pending(struct Curl_cfilter *cf,
728 const struct Curl_easy *data)
729 {
730 struct cf_msh3_ctx *ctx = cf->ctx;
731 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
732 struct cf_call_data save;
733 bool pending = FALSE;
734
735 CF_DATA_SAVE(save, cf, data);
736
737 (void)cf;
738 if(stream && stream->req) {
739 msh3_lock_acquire(&stream->recv_lock);
740 CURL_TRC_CF((struct Curl_easy *)data, cf, "data pending = %zu",
741 Curl_bufq_len(&stream->recvbuf));
742 pending = !Curl_bufq_is_empty(&stream->recvbuf);
743 msh3_lock_release(&stream->recv_lock);
744 if(pending)
745 drain_stream(cf, (struct Curl_easy *)data);
746 }
747
748 CF_DATA_RESTORE(cf, save);
749 return pending;
750 }
751
h3_data_pause(struct Curl_cfilter * cf,struct Curl_easy * data,bool pause)752 static CURLcode h3_data_pause(struct Curl_cfilter *cf,
753 struct Curl_easy *data,
754 bool pause)
755 {
756 if(!pause) {
757 drain_stream(cf, data);
758 Curl_expire(data, 0, EXPIRE_RUN_NOW);
759 }
760 return CURLE_OK;
761 }
762
cf_msh3_data_event(struct Curl_cfilter * cf,struct Curl_easy * data,int event,int arg1,void * arg2)763 static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf,
764 struct Curl_easy *data,
765 int event, int arg1, void *arg2)
766 {
767 struct cf_msh3_ctx *ctx = cf->ctx;
768 struct stream_ctx *stream = H3_STREAM_CTX(ctx, data);
769 struct cf_call_data save;
770 CURLcode result = CURLE_OK;
771
772 CF_DATA_SAVE(save, cf, data);
773
774 (void)arg1;
775 (void)arg2;
776 switch(event) {
777 case CF_CTRL_DATA_SETUP:
778 result = h3_data_setup(cf, data);
779 break;
780 case CF_CTRL_DATA_PAUSE:
781 result = h3_data_pause(cf, data, (arg1 != 0));
782 break;
783 case CF_CTRL_DATA_DONE:
784 h3_data_done(cf, data);
785 break;
786 case CF_CTRL_DATA_DONE_SEND:
787 CURL_TRC_CF(data, cf, "req: send done");
788 if(stream) {
789 stream->upload_done = TRUE;
790 if(stream->req) {
791 char buf[1];
792 if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_FIN,
793 buf, 0, data)) {
794 result = CURLE_SEND_ERROR;
795 }
796 }
797 }
798 break;
799 default:
800 break;
801 }
802
803 CF_DATA_RESTORE(cf, save);
804 return result;
805 }
806
cf_connect_start(struct Curl_cfilter * cf,struct Curl_easy * data)807 static CURLcode cf_connect_start(struct Curl_cfilter *cf,
808 struct Curl_easy *data)
809 {
810 struct cf_msh3_ctx *ctx = cf->ctx;
811 struct ssl_primary_config *conn_config;
812 MSH3_ADDR addr = {0};
813 CURLcode result;
814 bool verify;
815
816 Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
817 conn_config = Curl_ssl_cf_get_primary_config(cf);
818 if(!conn_config)
819 return CURLE_FAILED_INIT;
820 verify = !!conn_config->verifypeer;
821
822 memcpy(&addr, &ctx->addr.sa_addr, ctx->addr.addrlen);
823 MSH3_SET_PORT(&addr, (uint16_t)cf->conn->remote_port);
824
825 if(verify && (conn_config->CAfile || conn_config->CApath)) {
826 /* TODO: need a way to provide trust anchors to MSH3 */
827 #ifdef DEBUGBUILD
828 /* we need this for our test cases to run */
829 CURL_TRC_CF(data, cf, "non-standard CA not supported, "
830 "switching off verifypeer in DEBUG mode");
831 verify = 0;
832 #else
833 CURL_TRC_CF(data, cf, "non-standard CA not supported, "
834 "attempting with built-in verification");
835 #endif
836 }
837
838 CURL_TRC_CF(data, cf, "connecting to %s:%d (verify=%d)",
839 cf->conn->host.name, (int)cf->conn->remote_port, verify);
840
841 ctx->api = MsH3ApiOpen();
842 if(!ctx->api) {
843 failf(data, "can't create msh3 api");
844 return CURLE_FAILED_INIT;
845 }
846
847 ctx->qconn = MsH3ConnectionOpen(ctx->api,
848 &msh3_conn_if,
849 cf,
850 cf->conn->host.name,
851 &addr,
852 !verify);
853 if(!ctx->qconn) {
854 failf(data, "can't create msh3 connection");
855 if(ctx->api) {
856 MsH3ApiClose(ctx->api);
857 ctx->api = NULL;
858 }
859 return CURLE_FAILED_INIT;
860 }
861
862 result = h3_data_setup(cf, data);
863 if(result)
864 return result;
865
866 return CURLE_OK;
867 }
868
cf_msh3_connect(struct Curl_cfilter * cf,struct Curl_easy * data,bool blocking,bool * done)869 static CURLcode cf_msh3_connect(struct Curl_cfilter *cf,
870 struct Curl_easy *data,
871 bool blocking, bool *done)
872 {
873 struct cf_msh3_ctx *ctx = cf->ctx;
874 struct cf_call_data save;
875 CURLcode result = CURLE_OK;
876
877 (void)blocking;
878 if(cf->connected) {
879 *done = TRUE;
880 return CURLE_OK;
881 }
882
883 CF_DATA_SAVE(save, cf, data);
884
885 if(ctx->sock[SP_LOCAL] == CURL_SOCKET_BAD) {
886 if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx->sock[0]) < 0) {
887 ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
888 ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
889 return CURLE_COULDNT_CONNECT;
890 }
891 }
892
893 *done = FALSE;
894 if(!ctx->qconn) {
895 ctx->connect_started = Curl_now();
896 result = cf_connect_start(cf, data);
897 if(result)
898 goto out;
899 }
900
901 if(ctx->handshake_complete) {
902 ctx->handshake_at = Curl_now();
903 if(ctx->handshake_succeeded) {
904 CURL_TRC_CF(data, cf, "handshake succeeded");
905 cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
906 cf->conn->httpversion = 30;
907 cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
908 cf->connected = TRUE;
909 cf->conn->alpn = CURL_HTTP_VERSION_3;
910 *done = TRUE;
911 connkeep(cf->conn, "HTTP/3 default");
912 Curl_pgrsTime(data, TIMER_APPCONNECT);
913 }
914 else {
915 failf(data, "failed to connect, handshake failed");
916 result = CURLE_COULDNT_CONNECT;
917 }
918 }
919
920 out:
921 CF_DATA_RESTORE(cf, save);
922 return result;
923 }
924
cf_msh3_close(struct Curl_cfilter * cf,struct Curl_easy * data)925 static void cf_msh3_close(struct Curl_cfilter *cf, struct Curl_easy *data)
926 {
927 struct cf_msh3_ctx *ctx = cf->ctx;
928 struct cf_call_data save;
929
930 (void)data;
931 CF_DATA_SAVE(save, cf, data);
932
933 if(ctx) {
934 CURL_TRC_CF(data, cf, "destroying");
935 if(ctx->qconn) {
936 MsH3ConnectionClose(ctx->qconn);
937 ctx->qconn = NULL;
938 }
939 if(ctx->api) {
940 MsH3ApiClose(ctx->api);
941 ctx->api = NULL;
942 }
943 Curl_hash_destroy(&ctx->streams);
944
945 if(ctx->active) {
946 /* We share our socket at cf->conn->sock[cf->sockindex] when active.
947 * If it is no longer there, someone has stolen (and hopefully
948 * closed it) and we just forget about it.
949 */
950 ctx->active = FALSE;
951 if(ctx->sock[SP_LOCAL] == cf->conn->sock[cf->sockindex]) {
952 CURL_TRC_CF(data, cf, "cf_msh3_close(%d) active",
953 (int)ctx->sock[SP_LOCAL]);
954 cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD;
955 }
956 else {
957 CURL_TRC_CF(data, cf, "cf_socket_close(%d) no longer at "
958 "conn->sock[], discarding", (int)ctx->sock[SP_LOCAL]);
959 ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
960 }
961 if(cf->sockindex == FIRSTSOCKET)
962 cf->conn->remote_addr = NULL;
963 }
964 if(ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) {
965 sclose(ctx->sock[SP_LOCAL]);
966 }
967 if(ctx->sock[SP_REMOTE] != CURL_SOCKET_BAD) {
968 sclose(ctx->sock[SP_REMOTE]);
969 }
970 ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
971 ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
972 }
973 CF_DATA_RESTORE(cf, save);
974 }
975
cf_msh3_destroy(struct Curl_cfilter * cf,struct Curl_easy * data)976 static void cf_msh3_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
977 {
978 struct cf_call_data save;
979
980 CF_DATA_SAVE(save, cf, data);
981 cf_msh3_close(cf, data);
982 free(cf->ctx);
983 cf->ctx = NULL;
984 /* no CF_DATA_RESTORE(cf, save); its gone */
985
986 }
987
cf_msh3_query(struct Curl_cfilter * cf,struct Curl_easy * data,int query,int * pres1,void * pres2)988 static CURLcode cf_msh3_query(struct Curl_cfilter *cf,
989 struct Curl_easy *data,
990 int query, int *pres1, void *pres2)
991 {
992 struct cf_msh3_ctx *ctx = cf->ctx;
993
994 switch(query) {
995 case CF_QUERY_MAX_CONCURRENT: {
996 /* TODO: we do not have access to this so far, fake it */
997 (void)ctx;
998 *pres1 = 100;
999 return CURLE_OK;
1000 }
1001 case CF_QUERY_TIMER_CONNECT: {
1002 struct curltime *when = pres2;
1003 /* we do not know when the first byte arrived */
1004 if(cf->connected)
1005 *when = ctx->handshake_at;
1006 return CURLE_OK;
1007 }
1008 case CF_QUERY_TIMER_APPCONNECT: {
1009 struct curltime *when = pres2;
1010 if(cf->connected)
1011 *when = ctx->handshake_at;
1012 return CURLE_OK;
1013 }
1014 default:
1015 break;
1016 }
1017 return cf->next?
1018 cf->next->cft->query(cf->next, data, query, pres1, pres2) :
1019 CURLE_UNKNOWN_OPTION;
1020 }
1021
cf_msh3_conn_is_alive(struct Curl_cfilter * cf,struct Curl_easy * data,bool * input_pending)1022 static bool cf_msh3_conn_is_alive(struct Curl_cfilter *cf,
1023 struct Curl_easy *data,
1024 bool *input_pending)
1025 {
1026 struct cf_msh3_ctx *ctx = cf->ctx;
1027
1028 (void)data;
1029 *input_pending = FALSE;
1030 return ctx && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD && ctx->qconn &&
1031 ctx->connected;
1032 }
1033
1034 struct Curl_cftype Curl_cft_http3 = {
1035 "HTTP/3",
1036 CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
1037 0,
1038 cf_msh3_destroy,
1039 cf_msh3_connect,
1040 cf_msh3_close,
1041 Curl_cf_def_get_host,
1042 cf_msh3_adjust_pollset,
1043 cf_msh3_data_pending,
1044 cf_msh3_send,
1045 cf_msh3_recv,
1046 cf_msh3_data_event,
1047 cf_msh3_conn_is_alive,
1048 Curl_cf_def_conn_keep_alive,
1049 cf_msh3_query,
1050 };
1051
h3_get_msh3_ctx(struct Curl_easy * data)1052 static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data)
1053 {
1054 if(data && data->conn) {
1055 struct Curl_cfilter *cf = data->conn->cfilter[FIRSTSOCKET];
1056 while(cf) {
1057 if(cf->cft == &Curl_cft_http3)
1058 return cf->ctx;
1059 cf = cf->next;
1060 }
1061 }
1062 DEBUGF(infof(data, "no filter context found"));
1063 return NULL;
1064 }
1065
Curl_cf_msh3_create(struct Curl_cfilter ** pcf,struct Curl_easy * data,struct connectdata * conn,const struct Curl_addrinfo * ai)1066 CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf,
1067 struct Curl_easy *data,
1068 struct connectdata *conn,
1069 const struct Curl_addrinfo *ai)
1070 {
1071 struct cf_msh3_ctx *ctx = NULL;
1072 struct Curl_cfilter *cf = NULL;
1073 CURLcode result;
1074
1075 (void)data;
1076 (void)conn;
1077 (void)ai; /* TODO: msh3 resolves itself? */
1078 ctx = calloc(1, sizeof(*ctx));
1079 if(!ctx) {
1080 result = CURLE_OUT_OF_MEMORY;
1081 goto out;
1082 }
1083 Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC);
1084 ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
1085 ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
1086
1087 result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
1088
1089 out:
1090 *pcf = (!result)? cf : NULL;
1091 if(result) {
1092 Curl_safefree(cf);
1093 Curl_safefree(ctx);
1094 }
1095
1096 return result;
1097 }
1098
Curl_conn_is_msh3(const struct Curl_easy * data,const struct connectdata * conn,int sockindex)1099 bool Curl_conn_is_msh3(const struct Curl_easy *data,
1100 const struct connectdata *conn,
1101 int sockindex)
1102 {
1103 struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
1104
1105 (void)data;
1106 for(; cf; cf = cf->next) {
1107 if(cf->cft == &Curl_cft_http3)
1108 return TRUE;
1109 if(cf->cft->flags & CF_TYPE_IP_CONNECT)
1110 return FALSE;
1111 }
1112 return FALSE;
1113 }
1114
1115 #endif /* USE_MSH3 */
1116