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