1use crate::args::*;
28use crate::common::*;
29
30use std::io::prelude::*;
31
32use std::rc::Rc;
33
34use std::cell::RefCell;
35
36use ring::rand::*;
37
38const MAX_DATAGRAM_SIZE: usize = 1350;
39
40#[derive(Debug)]
41pub enum ClientError {
42 HandshakeFail,
43 HttpFail,
44 Other(String),
45}
46
47pub fn connect(
48 args: ClientArgs, conn_args: CommonArgs,
49 output_sink: impl FnMut(String) + 'static,
50) -> Result<(), ClientError> {
51 let mut buf = [0; 65535];
52 let mut out = [0; MAX_DATAGRAM_SIZE];
53
54 let output_sink =
55 Rc::new(RefCell::new(output_sink)) as Rc<RefCell<dyn FnMut(_)>>;
56
57 let mut poll = mio::Poll::new().unwrap();
59 let mut events = mio::Events::with_capacity(1024);
60
61 let connect_url = &args.urls[0];
63
64 let peer_addr = if let Some(addr) = &args.connect_to {
66 addr.parse().expect("--connect-to is expected to be a string containing an IPv4 or IPv6 address with a port. E.g. 192.0.2.0:443")
67 } else {
68 *connect_url.socket_addrs(|| None).unwrap().first().unwrap()
69 };
70
71 let bind_addr = match peer_addr {
75 std::net::SocketAddr::V4(_) => format!("0.0.0.0:{}", args.source_port),
76 std::net::SocketAddr::V6(_) => format!("[::]:{}", args.source_port),
77 };
78
79 let mut socket =
82 mio::net::UdpSocket::bind(bind_addr.parse().unwrap()).unwrap();
83 poll.registry()
84 .register(&mut socket, mio::Token(0), mio::Interest::READABLE)
85 .unwrap();
86
87 let migrate_socket = if args.perform_migration {
88 let mut socket =
89 mio::net::UdpSocket::bind(bind_addr.parse().unwrap()).unwrap();
90 poll.registry()
91 .register(&mut socket, mio::Token(1), mio::Interest::READABLE)
92 .unwrap();
93
94 Some(socket)
95 } else {
96 None
97 };
98
99 let mut config = quiche::Config::new(args.version).unwrap();
101
102 if let Some(ref trust_origin_ca_pem) = args.trust_origin_ca_pem {
103 config
104 .load_verify_locations_from_file(trust_origin_ca_pem)
105 .map_err(|e| {
106 ClientError::Other(format!(
107 "error loading origin CA file : {}",
108 e
109 ))
110 })?;
111 } else {
112 config.verify_peer(!args.no_verify);
113 }
114
115 config.set_application_protos(&conn_args.alpns).unwrap();
116
117 config.set_max_idle_timeout(conn_args.idle_timeout);
118 config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);
119 config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);
120 config.set_initial_max_data(conn_args.max_data);
121 config.set_initial_max_stream_data_bidi_local(conn_args.max_stream_data);
122 config.set_initial_max_stream_data_bidi_remote(conn_args.max_stream_data);
123 config.set_initial_max_stream_data_uni(conn_args.max_stream_data);
124 config.set_initial_max_streams_bidi(conn_args.max_streams_bidi);
125 config.set_initial_max_streams_uni(conn_args.max_streams_uni);
126 config.set_disable_active_migration(!conn_args.enable_active_migration);
127 config.set_active_connection_id_limit(conn_args.max_active_cids);
128
129 config.set_max_connection_window(conn_args.max_window);
130 config.set_max_stream_window(conn_args.max_stream_window);
131
132 let mut keylog = None;
133
134 if let Some(keylog_path) = std::env::var_os("SSLKEYLOGFILE") {
135 let file = std::fs::OpenOptions::new()
136 .create(true)
137 .append(true)
138 .open(keylog_path)
139 .unwrap();
140
141 keylog = Some(file);
142
143 config.log_keys();
144 }
145
146 if conn_args.no_grease {
147 config.grease(false);
148 }
149
150 if conn_args.early_data {
151 config.enable_early_data();
152 }
153
154 config
155 .set_cc_algorithm_name(&conn_args.cc_algorithm)
156 .unwrap();
157
158 if conn_args.disable_hystart {
159 config.enable_hystart(false);
160 }
161
162 if conn_args.dgrams_enabled {
163 config.enable_dgram(true, 1000, 1000);
164 }
165
166 let mut http_conn: Option<Box<dyn HttpConn>> = None;
167
168 let mut app_proto_selected = false;
169
170 let rng = SystemRandom::new();
172
173 let scid = if !cfg!(feature = "fuzzing") {
174 let mut conn_id = [0; quiche::MAX_CONN_ID_LEN];
175 rng.fill(&mut conn_id[..]).unwrap();
176
177 conn_id.to_vec()
178 } else {
179 [0; quiche::MAX_CONN_ID_LEN].to_vec()
181 };
182
183 let scid = quiche::ConnectionId::from_ref(&scid);
184
185 let local_addr = socket.local_addr().unwrap();
186
187 let mut conn = quiche::connect(
189 connect_url.domain(),
190 &scid,
191 local_addr,
192 peer_addr,
193 &mut config,
194 )
195 .unwrap();
196
197 if let Some(keylog) = &mut keylog {
198 if let Ok(keylog) = keylog.try_clone() {
199 conn.set_keylog(Box::new(keylog));
200 }
201 }
202
203 #[cfg(feature = "qlog")]
205 {
206 if let Some(dir) = std::env::var_os("QLOGDIR") {
207 let id = format!("{scid:?}");
208 let writer = make_qlog_writer(&dir, "client", &id);
209
210 conn.set_qlog(
211 std::boxed::Box::new(writer),
212 "quiche-client qlog".to_string(),
213 format!("{} id={}", "quiche-client qlog", id),
214 );
215 }
216 }
217
218 if let Some(session_file) = &args.session_file {
219 if let Ok(session) = std::fs::read(session_file) {
220 conn.set_session(&session).ok();
221 }
222 }
223
224 info!(
225 "connecting to {:} from {:} with scid {:?}",
226 peer_addr,
227 socket.local_addr().unwrap(),
228 scid,
229 );
230
231 let (write, send_info) = conn.send(&mut out).expect("initial send failed");
232
233 while let Err(e) = socket.send_to(&out[..write], send_info.to) {
234 if e.kind() == std::io::ErrorKind::WouldBlock {
235 trace!(
236 "{} -> {}: send() would block",
237 socket.local_addr().unwrap(),
238 send_info.to
239 );
240 continue;
241 }
242
243 return Err(ClientError::Other(format!("send() failed: {e:?}")));
244 }
245
246 trace!("written {}", write);
247
248 let app_data_start = std::time::Instant::now();
249
250 let mut pkt_count = 0;
251
252 let mut scid_sent = false;
253 let mut new_path_probed = false;
254 let mut migrated = false;
255
256 loop {
257 if !conn.is_in_early_data() || app_proto_selected {
258 poll.poll(&mut events, conn.timeout()).unwrap();
259 }
260
261 if events.is_empty() {
265 trace!("timed out");
266
267 conn.on_timeout();
268 }
269
270 for event in &events {
273 let socket = match event.token() {
274 mio::Token(0) => &socket,
275
276 mio::Token(1) => migrate_socket.as_ref().unwrap(),
277
278 _ => unreachable!(),
279 };
280
281 let local_addr = socket.local_addr().unwrap();
282 'read: loop {
283 let (len, from) = match socket.recv_from(&mut buf) {
284 Ok(v) => v,
285
286 Err(e) => {
287 if e.kind() == std::io::ErrorKind::WouldBlock {
290 trace!("{}: recv() would block", local_addr);
291 break 'read;
292 }
293
294 return Err(ClientError::Other(format!(
295 "{local_addr}: recv() failed: {e:?}"
296 )));
297 },
298 };
299
300 trace!("got {len} bytes from {from} to {local_addr}");
301
302 if let Some(target_path) = conn_args.dump_packet_path.as_ref() {
303 let path = format!("{target_path}/{pkt_count}.pkt");
304
305 if let Ok(f) = std::fs::File::create(path) {
306 let mut f = std::io::BufWriter::new(f);
307 f.write_all(&buf[..len]).ok();
308 }
309 }
310
311 pkt_count += 1;
312
313 let recv_info = quiche::RecvInfo {
314 to: local_addr,
315 from,
316 };
317
318 let read = match conn.recv(&mut buf[..len], recv_info) {
320 Ok(v) => v,
321
322 Err(e) => {
323 error!("{}: recv failed: {:?}", local_addr, e);
324 continue 'read;
325 },
326 };
327
328 trace!("{}: processed {} bytes", local_addr, read);
329 }
330 }
331
332 trace!("done reading");
333
334 if conn.is_closed() {
335 info!(
336 "connection closed, {:?} {:?}",
337 conn.stats(),
338 conn.path_stats().collect::<Vec<quiche::PathStats>>()
339 );
340
341 if !conn.is_established() {
342 error!(
343 "connection timed out after {:?}",
344 app_data_start.elapsed(),
345 );
346
347 return Err(ClientError::HandshakeFail);
348 }
349
350 if let Some(session_file) = &args.session_file {
351 if let Some(session) = conn.session() {
352 std::fs::write(session_file, session).ok();
353 }
354 }
355
356 if let Some(h_conn) = http_conn {
357 if h_conn.report_incomplete(&app_data_start) {
358 return Err(ClientError::HttpFail);
359 }
360 }
361
362 break;
363 }
364
365 if (conn.is_established() || conn.is_in_early_data()) &&
368 (!args.perform_migration || migrated) &&
369 !app_proto_selected
370 {
371 let app_proto = conn.application_proto();
380
381 if alpns::HTTP_09.contains(&app_proto) {
382 http_conn = Some(Http09Conn::with_urls(
383 &args.urls,
384 args.reqs_cardinal,
385 Rc::clone(&output_sink),
386 ));
387
388 app_proto_selected = true;
389 } else if alpns::HTTP_3.contains(&app_proto) {
390 let dgram_sender = if conn_args.dgrams_enabled {
391 Some(Http3DgramSender::new(
392 conn_args.dgram_count,
393 conn_args.dgram_data.clone(),
394 0,
395 ))
396 } else {
397 None
398 };
399
400 http_conn = Some(Http3Conn::with_urls(
401 &mut conn,
402 &args.urls,
403 args.reqs_cardinal,
404 &args.req_headers,
405 &args.body,
406 &args.method,
407 args.send_priority_update,
408 conn_args.max_field_section_size,
409 conn_args.qpack_max_table_capacity,
410 conn_args.qpack_blocked_streams,
411 args.dump_json,
412 dgram_sender,
413 Rc::clone(&output_sink),
414 ));
415
416 app_proto_selected = true;
417 }
418 }
419
420 if let Some(h_conn) = http_conn.as_mut() {
423 h_conn.send_requests(&mut conn, &args.dump_response_path);
424 h_conn.handle_responses(&mut conn, &mut buf, &app_data_start);
425 }
426
427 while let Some(qe) = conn.path_event_next() {
429 match qe {
430 quiche::PathEvent::New(..) => unreachable!(),
431
432 quiche::PathEvent::Validated(local_addr, peer_addr) => {
433 info!(
434 "Path ({}, {}) is now validated",
435 local_addr, peer_addr
436 );
437 conn.migrate(local_addr, peer_addr).unwrap();
438 migrated = true;
439 },
440
441 quiche::PathEvent::FailedValidation(local_addr, peer_addr) => {
442 info!(
443 "Path ({}, {}) failed validation",
444 local_addr, peer_addr
445 );
446 },
447
448 quiche::PathEvent::Closed(local_addr, peer_addr) => {
449 info!(
450 "Path ({}, {}) is now closed and unusable",
451 local_addr, peer_addr
452 );
453 },
454
455 quiche::PathEvent::ReusedSourceConnectionId(
456 cid_seq,
457 old,
458 new,
459 ) => {
460 info!(
461 "Peer reused cid seq {} (initially {:?}) on {:?}",
462 cid_seq, old, new
463 );
464 },
465
466 quiche::PathEvent::PeerMigrated(..) => unreachable!(),
467 }
468 }
469
470 while let Some(retired_scid) = conn.retired_scid_next() {
472 info!("Retiring source CID {:?}", retired_scid);
473 }
474
475 while conn.scids_left() > 0 {
477 let (scid, reset_token) = generate_cid_and_reset_token(&rng);
478
479 if conn.new_scid(&scid, reset_token, false).is_err() {
480 break;
481 }
482
483 scid_sent = true;
484 }
485
486 if args.perform_migration &&
487 !new_path_probed &&
488 scid_sent &&
489 conn.available_dcids() > 0
490 {
491 let additional_local_addr =
492 migrate_socket.as_ref().unwrap().local_addr().unwrap();
493 conn.probe_path(additional_local_addr, peer_addr).unwrap();
494
495 new_path_probed = true;
496 }
497
498 let mut sockets = vec![&socket];
501 if let Some(migrate_socket) = migrate_socket.as_ref() {
502 sockets.push(migrate_socket);
503 }
504
505 for socket in sockets {
506 let local_addr = socket.local_addr().unwrap();
507
508 for peer_addr in conn.paths_iter(local_addr) {
509 loop {
510 let (write, send_info) = match conn.send_on_path(
511 &mut out,
512 Some(local_addr),
513 Some(peer_addr),
514 ) {
515 Ok(v) => v,
516
517 Err(quiche::Error::Done) => {
518 trace!(
519 "{} -> {}: done writing",
520 local_addr,
521 peer_addr
522 );
523 break;
524 },
525
526 Err(e) => {
527 error!(
528 "{} -> {}: send failed: {:?}",
529 local_addr, peer_addr, e
530 );
531
532 conn.close(false, 0x1, b"fail").ok();
533 break;
534 },
535 };
536
537 if let Err(e) = socket.send_to(&out[..write], send_info.to) {
538 if e.kind() == std::io::ErrorKind::WouldBlock {
539 trace!(
540 "{} -> {}: send() would block",
541 local_addr,
542 send_info.to
543 );
544 break;
545 }
546
547 return Err(ClientError::Other(format!(
548 "{} -> {}: send() failed: {:?}",
549 local_addr, send_info.to, e
550 )));
551 }
552
553 trace!(
554 "written {write} bytes from {local_addr} to {}",
555 send_info.to
556 );
557 }
558 }
559 }
560
561 if conn.is_closed() {
562 info!(
563 "connection closed, {:?} {:?}",
564 conn.stats(),
565 conn.path_stats().collect::<Vec<quiche::PathStats>>()
566 );
567
568 if !conn.is_established() {
569 error!(
570 "connection timed out after {:?}",
571 app_data_start.elapsed(),
572 );
573
574 return Err(ClientError::HandshakeFail);
575 }
576
577 if let Some(session_file) = &args.session_file {
578 if let Some(session) = conn.session() {
579 std::fs::write(session_file, session).ok();
580 }
581 }
582
583 if let Some(h_conn) = http_conn {
584 if h_conn.report_incomplete(&app_data_start) {
585 return Err(ClientError::HttpFail);
586 }
587 }
588
589 break;
590 }
591 }
592
593 Ok(())
594}