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