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
Curl_quic_ver(char * p,size_t len)54 void Curl_quic_ver(char *p, size_t len)
55 {
56 #ifdef USE_NGTCP2
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
vquic_ctx_init(struct cf_quic_ctx * qctx,size_t pktbuflen)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
vquic_ctx_free(struct cf_quic_ctx * qctx)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
do_sendmsg(struct Curl_cfilter * cf,struct Curl_easy * data,struct cf_quic_ctx * qctx,const uint8_t * pkt,size_t pktlen,size_t gsolen,size_t * psent)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
send_packet_no_gso(struct Curl_cfilter * cf,struct Curl_easy * data,struct cf_quic_ctx * qctx,const uint8_t * pkt,size_t pktlen,size_t gsolen,size_t * psent)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
vquic_send_packet(struct Curl_cfilter * cf,struct Curl_easy * data,struct cf_quic_ctx * qctx,const uint8_t * pkt,size_t pktlen,size_t gsolen,size_t * psent)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
vquic_push_blocked_pkt(struct Curl_cfilter * cf,struct cf_quic_ctx * qctx,const uint8_t * pkt,size_t pktlen,size_t gsolen)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
vquic_send_blocked_pkt(struct Curl_cfilter * cf,struct Curl_easy * data,struct cf_quic_ctx * qctx)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 */
Curl_qlogdir(struct Curl_easy * data,unsigned char * scid,size_t scidlen,int * qlogfdp)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
Curl_cf_quic_create(struct Curl_cfilter ** pcf,struct Curl_easy * data,struct connectdata * conn,const struct Curl_addrinfo * ai,int transport)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 #ifdef USE_NGTCP2
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
Curl_conn_is_http3(const struct Curl_easy * data,const struct connectdata * conn,int sockindex)348 bool Curl_conn_is_http3(const struct Curl_easy *data,
349 const struct connectdata *conn,
350 int sockindex)
351 {
352 #ifdef USE_NGTCP2
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
Curl_conn_may_http3(struct Curl_easy * data,const struct connectdata * conn)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
Curl_conn_may_http3(struct Curl_easy * data,const struct connectdata * conn)391 CURLcode Curl_conn_may_http3(struct Curl_easy *data,
392 const struct connectdata *conn)
393 {
394 (void)conn;
395 (void)data;
396 DEBUGF(infof(data, "QUIC is not supported in this build"));
397 return CURLE_NOT_BUILT_IN;
398 }
399
400 #endif /* !ENABLE_QUIC */
401