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