/src/curl/lib/vquic/vquic.c
Line | Count | Source (jump to first uncovered line) |
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 HAVE_FCNTL_H |
28 | | #include <fcntl.h> |
29 | | #endif |
30 | | #include "urldata.h" |
31 | | #include "dynbuf.h" |
32 | | #include "cfilters.h" |
33 | | #include "curl_log.h" |
34 | | #include "curl_msh3.h" |
35 | | #include "curl_ngtcp2.h" |
36 | | #include "curl_quiche.h" |
37 | | #include "vquic.h" |
38 | | #include "vquic_int.h" |
39 | | |
40 | | /* The last 3 #include files should be in this order */ |
41 | | #include "curl_printf.h" |
42 | | #include "curl_memory.h" |
43 | | #include "memdebug.h" |
44 | | |
45 | | |
46 | | #ifdef ENABLE_QUIC |
47 | | |
48 | | #ifdef O_BINARY |
49 | | #define QLOGMODE O_WRONLY|O_CREAT|O_BINARY |
50 | | #else |
51 | | #define QLOGMODE O_WRONLY|O_CREAT |
52 | | #endif |
53 | | |
54 | | void Curl_quic_ver(char *p, size_t len) |
55 | | { |
56 | | #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) |
57 | | Curl_ngtcp2_ver(p, len); |
58 | | #elif defined(USE_QUICHE) |
59 | | Curl_quiche_ver(p, len); |
60 | | #elif defined(USE_MSH3) |
61 | | Curl_msh3_ver(p, len); |
62 | | #endif |
63 | | } |
64 | | |
65 | | CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx, size_t pktbuflen) |
66 | | { |
67 | | qctx->num_blocked_pkt = 0; |
68 | | qctx->num_blocked_pkt_sent = 0; |
69 | | memset(&qctx->blocked_pkt, 0, sizeof(qctx->blocked_pkt)); |
70 | | |
71 | | qctx->pktbuflen = pktbuflen; |
72 | | qctx->pktbuf = malloc(qctx->pktbuflen); |
73 | | if(!qctx->pktbuf) |
74 | | return CURLE_OUT_OF_MEMORY; |
75 | | |
76 | | #if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG) |
77 | | qctx->no_gso = FALSE; |
78 | | #else |
79 | | qctx->no_gso = TRUE; |
80 | | #endif |
81 | | |
82 | | return CURLE_OK; |
83 | | } |
84 | | |
85 | | void vquic_ctx_free(struct cf_quic_ctx *qctx) |
86 | | { |
87 | | free(qctx->pktbuf); |
88 | | qctx->pktbuf = NULL; |
89 | | } |
90 | | |
91 | | static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, |
92 | | struct Curl_easy *data, |
93 | | struct cf_quic_ctx *qctx, |
94 | | const uint8_t *pkt, size_t pktlen, |
95 | | size_t gsolen, size_t *psent); |
96 | | |
97 | | static CURLcode do_sendmsg(struct Curl_cfilter *cf, |
98 | | struct Curl_easy *data, |
99 | | struct cf_quic_ctx *qctx, |
100 | | const uint8_t *pkt, size_t pktlen, size_t gsolen, |
101 | | size_t *psent) |
102 | | { |
103 | | #ifdef HAVE_SENDMSG |
104 | | struct iovec msg_iov; |
105 | | struct msghdr msg = {0}; |
106 | | ssize_t sent; |
107 | | #if defined(__linux__) && defined(UDP_SEGMENT) |
108 | | uint8_t msg_ctrl[32]; |
109 | | struct cmsghdr *cm; |
110 | | #endif |
111 | | |
112 | | *psent = 0; |
113 | | msg_iov.iov_base = (uint8_t *)pkt; |
114 | | msg_iov.iov_len = pktlen; |
115 | | msg.msg_iov = &msg_iov; |
116 | | msg.msg_iovlen = 1; |
117 | | |
118 | | #if defined(__linux__) && defined(UDP_SEGMENT) |
119 | | if(pktlen > gsolen) { |
120 | | /* Only set this, when we need it. macOS, for example, |
121 | | * does not seem to like a msg_control of length 0. */ |
122 | | msg.msg_control = msg_ctrl; |
123 | | assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t))); |
124 | | msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); |
125 | | cm = CMSG_FIRSTHDR(&msg); |
126 | | cm->cmsg_level = SOL_UDP; |
127 | | cm->cmsg_type = UDP_SEGMENT; |
128 | | cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); |
129 | | *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff; |
130 | | } |
131 | | #endif |
132 | | |
133 | | |
134 | | while((sent = sendmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR) |
135 | | ; |
136 | | |
137 | | if(sent == -1) { |
138 | | switch(SOCKERRNO) { |
139 | | case EAGAIN: |
140 | | #if EAGAIN != EWOULDBLOCK |
141 | | case EWOULDBLOCK: |
142 | | #endif |
143 | | return CURLE_AGAIN; |
144 | | case EMSGSIZE: |
145 | | /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */ |
146 | | break; |
147 | | case EIO: |
148 | | if(pktlen > gsolen) { |
149 | | /* GSO failure */ |
150 | | failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent, |
151 | | SOCKERRNO); |
152 | | qctx->no_gso = TRUE; |
153 | | return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); |
154 | | } |
155 | | /* FALLTHROUGH */ |
156 | | default: |
157 | | failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO); |
158 | | return CURLE_SEND_ERROR; |
159 | | } |
160 | | } |
161 | | else { |
162 | | assert(pktlen == (size_t)sent); |
163 | | } |
164 | | #else |
165 | | ssize_t sent; |
166 | | (void)gsolen; |
167 | | |
168 | | *psent = 0; |
169 | | |
170 | | while((sent = send(qctx->sockfd, |
171 | | (const char *)pkt, (SEND_TYPE_ARG3)pktlen, 0)) == -1 && |
172 | | SOCKERRNO == EINTR) |
173 | | ; |
174 | | |
175 | | if(sent == -1) { |
176 | | if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { |
177 | | return CURLE_AGAIN; |
178 | | } |
179 | | else { |
180 | | failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO); |
181 | | if(SOCKERRNO != EMSGSIZE) { |
182 | | return CURLE_SEND_ERROR; |
183 | | } |
184 | | /* UDP datagram is too large; caused by PMTUD. Just let it be |
185 | | lost. */ |
186 | | } |
187 | | } |
188 | | #endif |
189 | | (void)cf; |
190 | | *psent = pktlen; |
191 | | |
192 | | return CURLE_OK; |
193 | | } |
194 | | |
195 | | static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, |
196 | | struct Curl_easy *data, |
197 | | struct cf_quic_ctx *qctx, |
198 | | const uint8_t *pkt, size_t pktlen, |
199 | | size_t gsolen, size_t *psent) |
200 | | { |
201 | | const uint8_t *p, *end = pkt + pktlen; |
202 | | size_t sent; |
203 | | |
204 | | *psent = 0; |
205 | | |
206 | | for(p = pkt; p < end; p += gsolen) { |
207 | | size_t len = CURLMIN(gsolen, (size_t)(end - p)); |
208 | | CURLcode curlcode = do_sendmsg(cf, data, qctx, p, len, len, &sent); |
209 | | if(curlcode != CURLE_OK) { |
210 | | return curlcode; |
211 | | } |
212 | | *psent += sent; |
213 | | } |
214 | | |
215 | | return CURLE_OK; |
216 | | } |
217 | | |
218 | | CURLcode vquic_send_packet(struct Curl_cfilter *cf, |
219 | | struct Curl_easy *data, |
220 | | struct cf_quic_ctx *qctx, |
221 | | const uint8_t *pkt, size_t pktlen, size_t gsolen, |
222 | | size_t *psent) |
223 | | { |
224 | | if(qctx->no_gso && pktlen > gsolen) { |
225 | | return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); |
226 | | } |
227 | | |
228 | | return do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent); |
229 | | } |
230 | | |
231 | | |
232 | | |
233 | | void vquic_push_blocked_pkt(struct Curl_cfilter *cf, |
234 | | struct cf_quic_ctx *qctx, |
235 | | const uint8_t *pkt, size_t pktlen, size_t gsolen) |
236 | | { |
237 | | struct vquic_blocked_pkt *blkpkt; |
238 | | |
239 | | (void)cf; |
240 | | assert(qctx->num_blocked_pkt < |
241 | | sizeof(qctx->blocked_pkt) / sizeof(qctx->blocked_pkt[0])); |
242 | | |
243 | | blkpkt = &qctx->blocked_pkt[qctx->num_blocked_pkt++]; |
244 | | |
245 | | blkpkt->pkt = pkt; |
246 | | blkpkt->pktlen = pktlen; |
247 | | blkpkt->gsolen = gsolen; |
248 | | } |
249 | | |
250 | | CURLcode vquic_send_blocked_pkt(struct Curl_cfilter *cf, |
251 | | struct Curl_easy *data, |
252 | | struct cf_quic_ctx *qctx) |
253 | | { |
254 | | size_t sent; |
255 | | CURLcode curlcode; |
256 | | struct vquic_blocked_pkt *blkpkt; |
257 | | |
258 | | (void)cf; |
259 | | for(; qctx->num_blocked_pkt_sent < qctx->num_blocked_pkt; |
260 | | ++qctx->num_blocked_pkt_sent) { |
261 | | blkpkt = &qctx->blocked_pkt[qctx->num_blocked_pkt_sent]; |
262 | | curlcode = vquic_send_packet(cf, data, qctx, blkpkt->pkt, |
263 | | blkpkt->pktlen, blkpkt->gsolen, &sent); |
264 | | |
265 | | if(curlcode) { |
266 | | if(curlcode == CURLE_AGAIN) { |
267 | | blkpkt->pkt += sent; |
268 | | blkpkt->pktlen -= sent; |
269 | | } |
270 | | return curlcode; |
271 | | } |
272 | | } |
273 | | |
274 | | qctx->num_blocked_pkt = 0; |
275 | | qctx->num_blocked_pkt_sent = 0; |
276 | | |
277 | | return CURLE_OK; |
278 | | } |
279 | | |
280 | | /* |
281 | | * If the QLOGDIR environment variable is set, open and return a file |
282 | | * descriptor to write the log to. |
283 | | * |
284 | | * This function returns error if something failed outside of failing to |
285 | | * create the file. Open file success is deemed by seeing if the returned fd |
286 | | * is != -1. |
287 | | */ |
288 | | CURLcode Curl_qlogdir(struct Curl_easy *data, |
289 | | unsigned char *scid, |
290 | | size_t scidlen, |
291 | | int *qlogfdp) |
292 | | { |
293 | | const char *qlog_dir = getenv("QLOGDIR"); |
294 | | *qlogfdp = -1; |
295 | | if(qlog_dir) { |
296 | | struct dynbuf fname; |
297 | | CURLcode result; |
298 | | unsigned int i; |
299 | | Curl_dyn_init(&fname, DYN_QLOG_NAME); |
300 | | result = Curl_dyn_add(&fname, qlog_dir); |
301 | | if(!result) |
302 | | result = Curl_dyn_add(&fname, "/"); |
303 | | for(i = 0; (i < scidlen) && !result; i++) { |
304 | | char hex[3]; |
305 | | msnprintf(hex, 3, "%02x", scid[i]); |
306 | | result = Curl_dyn_add(&fname, hex); |
307 | | } |
308 | | if(!result) |
309 | | result = Curl_dyn_add(&fname, ".sqlog"); |
310 | | |
311 | | if(!result) { |
312 | | int qlogfd = open(Curl_dyn_ptr(&fname), QLOGMODE, |
313 | | data->set.new_file_perms); |
314 | | if(qlogfd != -1) |
315 | | *qlogfdp = qlogfd; |
316 | | } |
317 | | Curl_dyn_free(&fname); |
318 | | if(result) |
319 | | return result; |
320 | | } |
321 | | |
322 | | return CURLE_OK; |
323 | | } |
324 | | |
325 | | CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, |
326 | | struct Curl_easy *data, |
327 | | struct connectdata *conn, |
328 | | const struct Curl_addrinfo *ai, |
329 | | int transport) |
330 | | { |
331 | | (void)transport; |
332 | | DEBUGASSERT(transport == TRNSPRT_QUIC); |
333 | | #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) |
334 | | return Curl_cf_ngtcp2_create(pcf, data, conn, ai); |
335 | | #elif defined(USE_QUICHE) |
336 | | return Curl_cf_quiche_create(pcf, data, conn, ai); |
337 | | #elif defined(USE_MSH3) |
338 | | return Curl_cf_msh3_create(pcf, data, conn, ai); |
339 | | #else |
340 | | *pcf = NULL; |
341 | | (void)data; |
342 | | (void)conn; |
343 | | (void)ai; |
344 | | return CURLE_NOT_BUILT_IN; |
345 | | #endif |
346 | | } |
347 | | |
348 | | bool Curl_conn_is_http3(const struct Curl_easy *data, |
349 | | const struct connectdata *conn, |
350 | | int sockindex) |
351 | | { |
352 | | #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) |
353 | | return Curl_conn_is_ngtcp2(data, conn, sockindex); |
354 | | #elif defined(USE_QUICHE) |
355 | | return Curl_conn_is_quiche(data, conn, sockindex); |
356 | | #elif defined(USE_MSH3) |
357 | | return Curl_conn_is_msh3(data, conn, sockindex); |
358 | | #else |
359 | | return ((conn->handler->protocol & PROTO_FAMILY_HTTP) && |
360 | | (conn->httpversion == 30)); |
361 | | #endif |
362 | | } |
363 | | |
364 | | CURLcode Curl_conn_may_http3(struct Curl_easy *data, |
365 | | const struct connectdata *conn) |
366 | | { |
367 | | if(conn->transport == TRNSPRT_UNIX) { |
368 | | /* cannot do QUIC over a unix domain socket */ |
369 | | return CURLE_QUIC_CONNECT_ERROR; |
370 | | } |
371 | | if(!(conn->handler->flags & PROTOPT_SSL)) { |
372 | | failf(data, "HTTP/3 requested for non-HTTPS URL"); |
373 | | return CURLE_URL_MALFORMAT; |
374 | | } |
375 | | #ifndef CURL_DISABLE_PROXY |
376 | | if(conn->bits.socksproxy) { |
377 | | failf(data, "HTTP/3 is not supported over a SOCKS proxy"); |
378 | | return CURLE_URL_MALFORMAT; |
379 | | } |
380 | | if(conn->bits.httpproxy && conn->bits.tunnel_proxy) { |
381 | | failf(data, "HTTP/3 is not supported over a HTTP proxy"); |
382 | | return CURLE_URL_MALFORMAT; |
383 | | } |
384 | | #endif |
385 | | |
386 | | return CURLE_OK; |
387 | | } |
388 | | |
389 | | #else /* ENABLE_QUIC */ |
390 | | |
391 | | CURLcode Curl_conn_may_http3(struct Curl_easy *data, |
392 | | const struct connectdata *conn) |
393 | 0 | { |
394 | 0 | (void)conn; |
395 | 0 | (void)data; |
396 | 0 | DEBUGF(infof(data, "QUIC is not supported in this build")); |
397 | 0 | return CURLE_NOT_BUILT_IN; |
398 | 0 | } |
399 | | |
400 | | #endif /* !ENABLE_QUIC */ |