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!("error loading origin CA file : {e}"))
107 })?;
108 } else {
109 config.verify_peer(!args.no_verify);
110 }
111
112 config.set_application_protos(&conn_args.alpns).unwrap();
113
114 config.set_initial_rtt(conn_args.initial_rtt);
115 config.set_max_idle_timeout(conn_args.idle_timeout);
116 config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);
117 config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);
118 config.set_initial_max_data(conn_args.max_data);
119 config.set_initial_max_stream_data_bidi_local(conn_args.max_stream_data);
120 config.set_initial_max_stream_data_bidi_remote(conn_args.max_stream_data);
121 config.set_initial_max_stream_data_uni(conn_args.max_stream_data);
122 config.set_initial_max_streams_bidi(conn_args.max_streams_bidi);
123 config.set_initial_max_streams_uni(conn_args.max_streams_uni);
124 config.set_disable_active_migration(!conn_args.enable_active_migration);
125 config.set_active_connection_id_limit(conn_args.max_active_cids);
126 config.set_initial_congestion_window_packets(
127 usize::try_from(conn_args.initial_cwnd_packets).unwrap(),
128 );
129
130 config.set_max_connection_window(conn_args.max_window);
131 config.set_max_stream_window(conn_args.max_stream_window);
132
133 let mut keylog = None;
134
135 if let Some(keylog_path) = std::env::var_os("SSLKEYLOGFILE") {
136 let file = std::fs::OpenOptions::new()
137 .create(true)
138 .append(true)
139 .open(keylog_path)
140 .unwrap();
141
142 keylog = Some(file);
143
144 config.log_keys();
145 }
146
147 if conn_args.no_grease {
148 config.grease(false);
149 }
150
151 if conn_args.early_data {
152 config.enable_early_data();
153 }
154
155 config
156 .set_cc_algorithm_name(&conn_args.cc_algorithm)
157 .unwrap();
158
159 if conn_args.disable_hystart {
160 config.enable_hystart(false);
161 }
162
163 if conn_args.dgrams_enabled {
164 config.enable_dgram(true, 1000, 1000);
165 }
166
167 let mut http_conn: Option<Box<dyn HttpConn>> = None;
168
169 let mut app_proto_selected = false;
170
171 let rng = SystemRandom::new();
173
174 let scid = if !cfg!(feature = "fuzzing") {
175 let mut conn_id = [0; quiche::MAX_CONN_ID_LEN];
176 rng.fill(&mut conn_id[..]).unwrap();
177
178 conn_id.to_vec()
179 } else {
180 [0; quiche::MAX_CONN_ID_LEN].to_vec()
182 };
183
184 let scid = quiche::ConnectionId::from_ref(&scid);
185
186 let local_addr = socket.local_addr().unwrap();
187
188 let mut conn = quiche::connect(
190 connect_url.domain(),
191 &scid,
192 local_addr,
193 peer_addr,
194 &mut config,
195 )
196 .unwrap();
197
198 if let Some(keylog) = &mut keylog {
199 if let Ok(keylog) = keylog.try_clone() {
200 conn.set_keylog(Box::new(keylog));
201 }
202 }
203
204 #[cfg(feature = "qlog")]
206 {
207 if let Some(dir) = std::env::var_os("QLOGDIR") {
208 let id = format!("{scid:?}");
209 let writer = make_qlog_writer(&dir, "client", &id);
210
211 conn.set_qlog(
212 std::boxed::Box::new(writer),
213 "quiche-client qlog".to_string(),
214 format!("{} id={}", "quiche-client qlog", id),
215 );
216 }
217 }
218
219 if let Some(session_file) = &args.session_file {
220 if let Ok(session) = std::fs::read(session_file) {
221 conn.set_session(&session).ok();
222 }
223 }
224
225 info!(
226 "connecting to {:} from {:} with scid {:?}",
227 peer_addr,
228 socket.local_addr().unwrap(),
229 scid,
230 );
231
232 let (write, send_info) = conn.send(&mut out).expect("initial send failed");
233
234 while let Err(e) = socket.send_to(&out[..write], send_info.to) {
235 if e.kind() == std::io::ErrorKind::WouldBlock {
236 trace!(
237 "{} -> {}: send() would block",
238 socket.local_addr().unwrap(),
239 send_info.to
240 );
241 continue;
242 }
243
244 return Err(ClientError::Other(format!("send() failed: {e:?}")));
245 }
246
247 trace!("written {write}");
248
249 let app_data_start = std::time::Instant::now();
250
251 let mut pkt_count = 0;
252
253 let mut scid_sent = false;
254 let mut new_path_probed = false;
255 let mut migrated = false;
256
257 loop {
258 if !conn.is_in_early_data() || app_proto_selected {
259 poll.poll(&mut events, conn.timeout()).unwrap();
260 }
261
262 if events.is_empty() {
266 trace!("timed out");
267
268 conn.on_timeout();
269 }
270
271 for event in &events {
274 let socket = match event.token() {
275 mio::Token(0) => &socket,
276
277 mio::Token(1) => migrate_socket.as_ref().unwrap(),
278
279 _ => unreachable!(),
280 };
281
282 let local_addr = socket.local_addr().unwrap();
283 'read: loop {
284 let (len, from) = match socket.recv_from(&mut buf) {
285 Ok(v) => v,
286
287 Err(e) => {
288 if e.kind() == std::io::ErrorKind::WouldBlock {
291 trace!("{local_addr}: recv() would block");
292 break 'read;
293 }
294
295 return Err(ClientError::Other(format!(
296 "{local_addr}: recv() failed: {e:?}"
297 )));
298 },
299 };
300
301 trace!("got {len} bytes from {from} to {local_addr}");
302
303 if let Some(target_path) = conn_args.dump_packet_path.as_ref() {
304 let path = format!("{target_path}/{pkt_count}.pkt");
305
306 if let Ok(f) = std::fs::File::create(path) {
307 let mut f = std::io::BufWriter::new(f);
308 f.write_all(&buf[..len]).ok();
309 }
310 }
311
312 pkt_count += 1;
313
314 let recv_info = quiche::RecvInfo {
315 to: local_addr,
316 from,
317 };
318
319 let read = match conn.recv(&mut buf[..len], recv_info) {
321 Ok(v) => v,
322
323 Err(e) => {
324 error!("{local_addr}: recv failed: {e:?}");
325 continue 'read;
326 },
327 };
328
329 trace!("{local_addr}: processed {read} bytes");
330 }
331 }
332
333 trace!("done reading");
334
335 if conn.is_closed() {
336 info!(
337 "connection closed, {:?} {:?}",
338 conn.stats(),
339 conn.path_stats().collect::<Vec<quiche::PathStats>>()
340 );
341
342 if !conn.is_established() {
343 error!(
344 "connection timed out after {:?}",
345 app_data_start.elapsed(),
346 );
347
348 return Err(ClientError::HandshakeFail);
349 }
350
351 if let Some(session_file) = &args.session_file {
352 if let Some(session) = conn.session() {
353 std::fs::write(session_file, session).ok();
354 }
355 }
356
357 if let Some(h_conn) = http_conn {
358 if h_conn.report_incomplete(&app_data_start) {
359 return Err(ClientError::HttpFail);
360 }
361 }
362
363 break;
364 }
365
366 if (conn.is_established() || conn.is_in_early_data()) &&
369 (!args.perform_migration || migrated) &&
370 !app_proto_selected
371 {
372 let app_proto = conn.application_proto();
381
382 if alpns::HTTP_09.contains(&app_proto) {
383 http_conn = Some(Http09Conn::with_urls(
384 &args.urls,
385 args.reqs_cardinal,
386 Rc::clone(&output_sink),
387 ));
388
389 app_proto_selected = true;
390 } else if alpns::HTTP_3.contains(&app_proto) {
391 let dgram_sender = if conn_args.dgrams_enabled {
392 Some(Http3DgramSender::new(
393 conn_args.dgram_count,
394 conn_args.dgram_data.clone(),
395 0,
396 ))
397 } else {
398 None
399 };
400
401 http_conn = Some(Http3Conn::with_urls(
402 &mut conn,
403 &args.urls,
404 args.reqs_cardinal,
405 &args.req_headers,
406 &args.body,
407 &args.method,
408 args.send_priority_update,
409 conn_args.max_field_section_size,
410 conn_args.qpack_max_table_capacity,
411 conn_args.qpack_blocked_streams,
412 args.dump_json,
413 dgram_sender,
414 Rc::clone(&output_sink),
415 ));
416
417 app_proto_selected = true;
418 }
419 }
420
421 if let Some(h_conn) = http_conn.as_mut() {
424 h_conn.send_requests(&mut conn, &args.dump_response_path);
425 h_conn.handle_responses(&mut conn, &mut buf, &app_data_start);
426 }
427
428 while let Some(qe) = conn.path_event_next() {
430 match qe {
431 quiche::PathEvent::New(..) => unreachable!(),
432
433 quiche::PathEvent::Validated(local_addr, peer_addr) => {
434 info!("Path ({local_addr}, {peer_addr}) is now validated");
435 conn.migrate(local_addr, peer_addr).unwrap();
436 migrated = true;
437 },
438
439 quiche::PathEvent::FailedValidation(local_addr, peer_addr) => {
440 info!("Path ({local_addr}, {peer_addr}) failed validation");
441 },
442
443 quiche::PathEvent::Closed(local_addr, peer_addr) => {
444 info!(
445 "Path ({local_addr}, {peer_addr}) is now closed and unusable"
446 );
447 },
448
449 quiche::PathEvent::ReusedSourceConnectionId(
450 cid_seq,
451 old,
452 new,
453 ) => {
454 info!(
455 "Peer reused cid seq {cid_seq} (initially {old:?}) on {new:?}"
456 );
457 },
458
459 quiche::PathEvent::PeerMigrated(..) => unreachable!(),
460 }
461 }
462
463 while let Some(retired_scid) = conn.retired_scid_next() {
465 info!("Retiring source CID {retired_scid:?}");
466 }
467
468 while conn.scids_left() > 0 {
470 let (scid, reset_token) = generate_cid_and_reset_token(&rng);
471
472 if conn.new_scid(&scid, reset_token, false).is_err() {
473 break;
474 }
475
476 scid_sent = true;
477 }
478
479 if args.perform_migration &&
480 !new_path_probed &&
481 scid_sent &&
482 conn.available_dcids() > 0
483 {
484 let additional_local_addr =
485 migrate_socket.as_ref().unwrap().local_addr().unwrap();
486 conn.probe_path(additional_local_addr, peer_addr).unwrap();
487
488 new_path_probed = true;
489 }
490
491 let mut sockets = vec![&socket];
494 if let Some(migrate_socket) = migrate_socket.as_ref() {
495 sockets.push(migrate_socket);
496 }
497
498 for socket in sockets {
499 let local_addr = socket.local_addr().unwrap();
500
501 for peer_addr in conn.paths_iter(local_addr) {
502 loop {
503 let (write, send_info) = match conn.send_on_path(
504 &mut out,
505 Some(local_addr),
506 Some(peer_addr),
507 ) {
508 Ok(v) => v,
509
510 Err(quiche::Error::Done) => {
511 trace!("{local_addr} -> {peer_addr}: done writing");
512 break;
513 },
514
515 Err(e) => {
516 error!(
517 "{local_addr} -> {peer_addr}: send failed: {e:?}"
518 );
519
520 conn.close(false, 0x1, b"fail").ok();
521 break;
522 },
523 };
524
525 if let Err(e) = socket.send_to(&out[..write], send_info.to) {
526 if e.kind() == std::io::ErrorKind::WouldBlock {
527 trace!(
528 "{} -> {}: send() would block",
529 local_addr,
530 send_info.to
531 );
532 break;
533 }
534
535 return Err(ClientError::Other(format!(
536 "{} -> {}: send() failed: {:?}",
537 local_addr, send_info.to, e
538 )));
539 }
540
541 trace!(
542 "written {write} bytes from {local_addr} to {}",
543 send_info.to
544 );
545 }
546 }
547 }
548
549 if conn.is_closed() {
550 info!(
551 "connection closed, {:?} {:?}",
552 conn.stats(),
553 conn.path_stats().collect::<Vec<quiche::PathStats>>()
554 );
555
556 if !conn.is_established() {
557 error!(
558 "connection timed out after {:?}",
559 app_data_start.elapsed(),
560 );
561
562 return Err(ClientError::HandshakeFail);
563 }
564
565 if let Some(session_file) = &args.session_file {
566 if let Some(session) = conn.session() {
567 std::fs::write(session_file, session).ok();
568 }
569 }
570
571 if let Some(h_conn) = http_conn {
572 if h_conn.report_incomplete(&app_data_start) {
573 return Err(ClientError::HttpFail);
574 }
575 }
576
577 break;
578 }
579 }
580
581 Ok(())
582}