diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml index 0d1ed31e50..1a6c3b631b 100644 --- a/.github/workflows/stable.yml +++ b/.github/workflows/stable.yml @@ -9,6 +9,11 @@ env: jobs: quiche: runs-on: ubuntu-latest + strategy: + matrix: + tls-feature: + - "" # default, boringssl-vendored + - "boringssl-boring-crate" # Only run on "pull_request" event for external PRs. This is to avoid # duplicate builds for PRs created from internal branches. if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository @@ -30,7 +35,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --verbose --all-targets --features=ffi,qlog + args: --verbose --all-targets --features=ffi,qlog,${{ matrix.tls-feature }} # Need to run doc tests separately. # (https://github.com/rust-lang/cargo/issues/6669) @@ -38,7 +43,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --verbose --doc --features=ffi,qlog + args: --verbose --doc --features=ffi,qlog,${{ matrix.tls-feature }} - name: Run cargo package uses: actions-rs/cargo@v1 @@ -50,13 +55,13 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --features=ffi,qlog -- -D warnings + args: --features=ffi,qlog,${{ matrix.tls-feature }} -- -D warnings - name: Run cargo clippy on examples uses: actions-rs/cargo@v1 with: command: clippy - args: --examples --features=ffi,qlog -- -D warnings + args: --examples --features=ffi,qlog,${{ matrix.tls-feature }} -- -D warnings - name: Run cargo doc uses: actions-rs/cargo@v1 diff --git a/apps/Cargo.toml b/apps/Cargo.toml index f6bc053dac..65a5cae4b0 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -22,15 +22,17 @@ default = ["qlog", "sfv"] [dependencies] docopt = "1" -env_logger = "0.6" +env_logger = "0.10" mio = { version = "0.8", features = ["net", "os-poll"] } url = "1" log = "0.4" octets = { version = "0.2", path = "../octets" } -ring = "0.16" +ring = "0.17" quiche = { path = "../quiche" } libc = "0.2" nix = { version = "0.27", features = ["net", "socket", "uio"] } +slab = "0.4" +itertools = "0.10" [lib] crate-type = ["lib"] diff --git a/apps/src/args.rs b/apps/src/args.rs index 9caa994781..19b5ebbc6d 100644 --- a/apps/src/args.rs +++ b/apps/src/args.rs @@ -24,6 +24,9 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::net::SocketAddr; +use std::str::FromStr; + use super::common::alpns; pub trait Args { @@ -54,6 +57,7 @@ pub struct CommonArgs { pub qpack_max_table_capacity: Option, pub qpack_blocked_streams: Option, pub initial_cwnd_packets: u64, + pub multipath: bool, } /// Creates a new `CommonArgs` structure using the provided [`Docopt`]. @@ -80,6 +84,7 @@ pub struct CommonArgs { /// --qpack-max-table-capacity BYTES Max capacity of dynamic QPACK decoding. /// --qpack-blocked-streams STREAMS Limit of blocked streams while decoding. /// --initial-cwnd-packets Size of initial congestion window, in packets. +/// --multipath Enable multipath support. /// /// [`Docopt`]: https://docs.rs/docopt/1.1.0/docopt/ impl Args for CommonArgs { @@ -191,6 +196,8 @@ impl Args for CommonArgs { .parse::() .unwrap(); + let multipath = args.get_bool("--multipath"); + CommonArgs { alpns, max_data, @@ -214,6 +221,7 @@ impl Args for CommonArgs { qpack_max_table_capacity, qpack_blocked_streams, initial_cwnd_packets, + multipath, } } } @@ -243,6 +251,7 @@ impl Default for CommonArgs { qpack_max_table_capacity: None, qpack_blocked_streams: None, initial_cwnd_packets: 10, + multipath: false, } } } @@ -271,7 +280,7 @@ Options: --dump-responses PATH Dump response payload as files in the given directory. --dump-json Dump response headers and payload to stdout in JSON format. --max-json-payload BYTES Per-response payload limit when dumping JSON [default: 10000]. - --connect-to ADDRESS Override ther server's address. + --connect-to ADDRESS Override the server's address. --no-verify Don't verify server's certificate. --trust-origin-ca-pem Path to the pem file of the origin's CA, if not publicly trusted. --no-grease Don't send GREASE. @@ -280,6 +289,10 @@ Options: --max-active-cids NUM The maximum number of active Connection IDs we can support [default: 2]. --enable-active-migration Enable active connection migration. --perform-migration Perform connection migration on another source port. + --multipath Enable multipath support. + -A --address ADDR ... Specify addresses to be used instead of the unspecified address. Non-routable addresses will lead to connectivity issues. + -R --rm-addr TIMEADDR ... Specify addresses to stop using after the provided time (format time,addr). + -S --status TIMEADDRSTAT ... Specify availability status to advertise to the peer after the provided time (format time,addr,available). -H --header HEADER ... Add a request header. -n --requests REQUESTS Send the given number of identical requests [default: 1]. --send-priority-update Send HTTP/3 priority updates if the query string params 'u' or 'i' are present in URLs @@ -309,6 +322,9 @@ pub struct ClientArgs { pub source_port: u16, pub perform_migration: bool, pub send_priority_update: bool, + pub addrs: Vec, + pub rm_addrs: Vec<(std::time::Duration, SocketAddr)>, + pub status: Vec<(std::time::Duration, SocketAddr, bool)>, } impl Args for ClientArgs { @@ -386,6 +402,57 @@ impl Args for ClientArgs { let send_priority_update = args.get_bool("--send-priority-update"); + let addrs = args + .get_vec("--address") + .into_iter() + .filter_map(|a| a.parse().ok()) + .collect(); + + let rm_addrs = args + .get_vec("--rm-addr") + .into_iter() + .filter_map(|ta| { + let s = ta.split(',').collect::>(); + if s.len() != 2 { + return None; + } + let secs = match s[0].parse::() { + Ok(s) => s, + Err(_) => return None, + }; + let addr = match SocketAddr::from_str(s[1]) { + Ok(a) => a, + Err(_) => return None, + }; + Some((std::time::Duration::from_secs(secs), addr)) + }) + .collect(); + + let status = args + .get_vec("--status") + .into_iter() + .filter_map(|ta| { + let s = ta.split(',').collect::>(); + if s.len() != 3 { + return None; + } + let secs = match s[0].parse::() { + Ok(s) => s, + Err(_) => return None, + }; + let addr = match SocketAddr::from_str(s[1]) { + Ok(a) => a, + Err(_) => return None, + }; + let status = match s[2].parse::() { + Ok(0) => false, + Ok(_) => true, + Err(_) => return None, + }; + Some((std::time::Duration::from_secs(secs), addr, status)) + }) + .collect(); + ClientArgs { version, dump_response_path, @@ -402,6 +469,9 @@ impl Args for ClientArgs { source_port, perform_migration, send_priority_update, + addrs, + rm_addrs, + status, } } } @@ -424,6 +494,9 @@ impl Default for ClientArgs { source_port: 0, perform_migration: false, send_priority_update: false, + addrs: vec![], + rm_addrs: vec![], + status: vec![], } } } @@ -464,6 +537,7 @@ Options: --disable-gso Disable GSO (linux only). --disable-pacing Disable pacing (linux only). --initial-cwnd-packets PACKETS The initial congestion window size in terms of packet count [default: 10]. + --multipath Enable multipath support. -h --help Show this screen. "; diff --git a/apps/src/bin/quiche-client.rs b/apps/src/bin/quiche-client.rs index 25ce6ddeff..fad885cb86 100644 --- a/apps/src/bin/quiche-client.rs +++ b/apps/src/bin/quiche-client.rs @@ -31,9 +31,7 @@ use quiche_apps::common::*; use quiche_apps::client::*; fn main() { - env_logger::builder() - .default_format_timestamp_nanos(true) - .init(); + env_logger::builder().format_timestamp_nanos().init(); // Parse CLI parameters. let docopt = docopt::Docopt::new(CLIENT_USAGE).unwrap(); diff --git a/apps/src/bin/quiche-server.rs b/apps/src/bin/quiche-server.rs index f36d673cea..3cef6e41e8 100644 --- a/apps/src/bin/quiche-server.rs +++ b/apps/src/bin/quiche-server.rs @@ -58,9 +58,7 @@ fn main() { let mut out = [0; MAX_BUF_SIZE]; let mut pacing = false; - env_logger::builder() - .default_format_timestamp_nanos(true) - .init(); + env_logger::builder().format_timestamp_nanos().init(); // Parse CLI parameters. let docopt = docopt::Docopt::new(SERVER_USAGE).unwrap(); @@ -124,6 +122,7 @@ fn main() { config.set_initial_congestion_window_packets( usize::try_from(conn_args.initial_cwnd_packets).unwrap(), ); + config.set_multipath(conn_args.multipath); config.set_max_connection_window(conn_args.max_window); config.set_max_stream_window(conn_args.max_stream_window); @@ -512,13 +511,9 @@ fn main() { } // Provides as many CIDs as possible. - while client.conn.source_cids_left() > 0 { + while client.conn.scids_left() > 0 { let (scid, reset_token) = generate_cid_and_reset_token(&rng); - if client - .conn - .new_source_cid(&scid, reset_token, false) - .is_err() - { + if client.conn.new_scid(&scid, reset_token, false).is_err() { break; } @@ -547,16 +542,24 @@ fn main() { client.max_datagram_size * client.max_datagram_size; let mut total_write = 0; - let mut dst_info = None; + let mut dst_info: Option = None; while total_write < max_send_burst { - let (write, send_info) = match client - .conn - .send(&mut out[total_write..max_send_burst]) - { + let res = match dst_info { + Some(info) => client.conn.send_on_path( + &mut out[total_write..max_send_burst], + Some(info.from), + Some(info.to), + ), + None => + client.conn.send(&mut out[total_write..max_send_burst]), + }; + + let (write, send_info) = match res { Ok(v) => v, Err(quiche::Error::Done) => { + continue_write = dst_info.is_some(); trace!("{} done writing", client.conn.trace_id()); break; }, @@ -602,6 +605,14 @@ fn main() { trace!("{} written {} bytes", client.conn.trace_id(), total_write); + if continue_write { + trace!( + "{} pause writing and consider another path", + client.conn.trace_id() + ); + break; + } + if total_write >= max_send_burst { trace!("{} pause writing", client.conn.trace_id(),); continue_write = true; @@ -703,7 +714,8 @@ fn handle_path_events(client: &mut Client) { client .conn .probe_path(local_addr, peer_addr) - .expect("cannot probe"); + .map_err(|e| error!("cannot probe: {}", e)) + .ok(); }, quiche::PathEvent::Validated(local_addr, peer_addr) => { @@ -713,6 +725,13 @@ fn handle_path_events(client: &mut Client) { local_addr, peer_addr ); + if client.conn.is_multipath_enabled() { + client + .conn + .set_active(local_addr, peer_addr, true) + .map_err(|e| error!("cannot set path active: {}", e)) + .ok(); + } }, quiche::PathEvent::FailedValidation(local_addr, peer_addr) => { @@ -724,12 +743,14 @@ fn handle_path_events(client: &mut Client) { ); }, - quiche::PathEvent::Closed(local_addr, peer_addr) => { + quiche::PathEvent::Closed(local_addr, peer_addr, err, reason) => { info!( - "{} Path ({}, {}) is now closed and unusable", + "{} Path ({}, {}) is now closed and unusable; err = {} reason = {:?}", client.conn.trace_id(), local_addr, - peer_addr + peer_addr, + err, + reason, ); }, @@ -751,6 +772,15 @@ fn handle_path_events(client: &mut Client) { peer_addr ); }, + + quiche::PathEvent::PeerPathStatus(addr, path_status) => { + info!("Peer asks status {:?} for {:?}", path_status, addr,); + client + .conn + .set_path_status(addr.0, addr.1, path_status, false) + .map_err(|e| error!("cannot follow status request: {}", e)) + .ok(); + }, } } } diff --git a/apps/src/client.rs b/apps/src/client.rs index 13f6814ddb..2685b9ff38 100644 --- a/apps/src/client.rs +++ b/apps/src/client.rs @@ -27,6 +27,7 @@ use crate::args::*; use crate::common::*; +use std::collections::HashMap; use std::net::ToSocketAddrs; use std::io::prelude::*; @@ -37,6 +38,8 @@ use std::cell::RefCell; use ring::rand::*; +use slab::Slab; + const MAX_DATAGRAM_SIZE: usize = 1350; #[derive(Debug)] @@ -70,33 +73,30 @@ pub fn connect( connect_url.to_socket_addrs().unwrap().next().unwrap() }; - // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the - // server address. This is needed on macOS and BSD variants that don't - // support binding to IN6ADDR_ANY for both v4 and v6. - let bind_addr = match peer_addr { - std::net::SocketAddr::V4(_) => format!("0.0.0.0:{}", args.source_port), - std::net::SocketAddr::V6(_) => format!("[::]:{}", args.source_port), - }; - - // Create the UDP socket backing the QUIC connection, and register it with - // the event loop. - let mut socket = - mio::net::UdpSocket::bind(bind_addr.parse().unwrap()).unwrap(); - poll.registry() - .register(&mut socket, mio::Token(0), mio::Interest::READABLE) - .unwrap(); + let (sockets, src_addr_to_token, local_addr) = + create_sockets(&mut poll, &peer_addr, &args); + let mut addrs = Vec::with_capacity(sockets.len()); + addrs.push(local_addr); + for src in src_addr_to_token.keys() { + if *src != local_addr { + addrs.push(*src); + } + } - let migrate_socket = if args.perform_migration { - let mut socket = - mio::net::UdpSocket::bind(bind_addr.parse().unwrap()).unwrap(); - poll.registry() - .register(&mut socket, mio::Token(1), mio::Interest::READABLE) - .unwrap(); + // Warn the user if there are more usable addresses than the advertised + // `active_connection_id_limit`. + if addrs.len() as u64 > conn_args.max_active_cids { + warn!( + "{} addresses provided, but configuration restricts to at most {} \ + active CIDs; increase the --max-active-cids parameter to use all \ + the provided addresses", + addrs.len(), + conn_args.max_active_cids + ); + } - Some(socket) - } else { - None - }; + let mut rm_addrs = args.rm_addrs.clone(); + let mut status = args.status.clone(); // Create the configuration for the QUIC connection. let mut config = quiche::Config::new(args.version).unwrap(); @@ -127,6 +127,7 @@ pub fn connect( config.set_initial_max_streams_uni(conn_args.max_streams_uni); config.set_disable_active_migration(!conn_args.enable_active_migration); config.set_active_connection_id_limit(conn_args.max_active_cids); + config.set_multipath(conn_args.multipath); config.set_max_connection_window(conn_args.max_window); config.set_max_stream_window(conn_args.max_stream_window); @@ -176,8 +177,6 @@ pub fn connect( let scid = quiche::ConnectionId::from_ref(&scid); - let local_addr = socket.local_addr().unwrap(); - // Create a QUIC connection and initiate handshake. let mut conn = quiche::connect( connect_url.domain(), @@ -217,18 +216,17 @@ pub fn connect( info!( "connecting to {:} from {:} with scid {:?}", - peer_addr, - socket.local_addr().unwrap(), - scid, + peer_addr, local_addr, scid, ); let (write, send_info) = conn.send(&mut out).expect("initial send failed"); + let token = src_addr_to_token[&send_info.from]; - while let Err(e) = socket.send_to(&out[..write], send_info.to) { + while let Err(e) = sockets[token].send_to(&out[..write], send_info.to) { if e.kind() == std::io::ErrorKind::WouldBlock { trace!( "{} -> {}: send() would block", - socket.local_addr().unwrap(), + sockets[token].local_addr().unwrap(), send_info.to ); continue; @@ -241,6 +239,7 @@ pub fn connect( let app_data_start = std::time::Instant::now(); + let mut probed_paths = 0; let mut pkt_count = 0; let mut scid_sent = false; @@ -264,14 +263,8 @@ pub fn connect( // Read incoming UDP packets from the socket and feed them to quiche, // until there are no more packets to read. for event in &events { - let socket = match event.token() { - mio::Token(0) => &socket, - - mio::Token(1) => migrate_socket.as_ref().unwrap(), - - _ => unreachable!(), - }; - + let token = event.token().into(); + let socket = &sockets[token]; let local_addr = socket.local_addr().unwrap(); 'read: loop { let (len, from) = match socket.recv_from(&mut buf) { @@ -428,8 +421,12 @@ pub fn connect( "Path ({}, {}) is now validated", local_addr, peer_addr ); - conn.migrate(local_addr, peer_addr).unwrap(); - migrated = true; + if conn.is_multipath_enabled() { + conn.set_active(local_addr, peer_addr, true).ok(); + } else if args.perform_migration { + conn.migrate(local_addr, peer_addr).unwrap(); + migrated = true; + } }, quiche::PathEvent::FailedValidation(local_addr, peer_addr) => { @@ -439,10 +436,10 @@ pub fn connect( ); }, - quiche::PathEvent::Closed(local_addr, peer_addr) => { + quiche::PathEvent::Closed(local_addr, peer_addr, e, reason) => { info!( - "Path ({}, {}) is now closed and unusable", - local_addr, peer_addr + "Path ({}, {}) is now closed and unusable; err = {}, reason = {:?}", + local_addr, peer_addr, e, reason ); }, @@ -458,6 +455,8 @@ pub fn connect( }, quiche::PathEvent::PeerMigrated(..) => unreachable!(), + + quiche::PathEvent::PeerPathStatus(..) => {}, } } @@ -467,90 +466,113 @@ pub fn connect( } // Provides as many CIDs as possible. - while conn.source_cids_left() > 0 { + while conn.scids_left() > 0 { let (scid, reset_token) = generate_cid_and_reset_token(&rng); - if conn.new_source_cid(&scid, reset_token, false).is_err() { + if conn.new_scid(&scid, reset_token, false).is_err() { break; } scid_sent = true; } - if args.perform_migration && + if conn_args.multipath && + probed_paths < addrs.len() && + conn.available_dcids() > 0 && + conn.probe_path(addrs[probed_paths], peer_addr).is_ok() + { + probed_paths += 1; + } + + if !conn_args.multipath && + args.perform_migration && !new_path_probed && scid_sent && conn.available_dcids() > 0 { - let additional_local_addr = - migrate_socket.as_ref().unwrap().local_addr().unwrap(); + let additional_local_addr = sockets[1].local_addr().unwrap(); conn.probe_path(additional_local_addr, peer_addr).unwrap(); new_path_probed = true; } + if conn.is_multipath_enabled() { + rm_addrs.retain(|(d, addr)| { + if app_data_start.elapsed() >= *d { + info!("Abandoning path {:?}", addr); + conn.abandon_path( + *addr, + peer_addr, + 0, + "do not use me anymore".to_string().into_bytes(), + ) + .is_err() + } else { + true + } + }); + + status.retain(|(d, addr, available)| { + if app_data_start.elapsed() >= *d { + let status = (*available).into(); + info!("Advertising path status {status:?} to {addr:?}"); + conn.set_path_status(*addr, peer_addr, status, true) + .is_err() + } else { + true + } + }); + } + + // Determine in which order we are going to iterate over paths. + let scheduled_tuples = lowest_latency_scheduler(&conn); + // Generate outgoing QUIC packets and send them on the UDP socket, until // quiche reports that there are no more packets to be sent. - let mut sockets = vec![&socket]; - if let Some(migrate_socket) = migrate_socket.as_ref() { - sockets.push(migrate_socket); - } + for (local_addr, peer_addr) in scheduled_tuples { + let token = src_addr_to_token[&local_addr]; + let socket = &sockets[token]; + loop { + let (write, send_info) = match conn.send_on_path( + &mut out, + Some(local_addr), + Some(peer_addr), + ) { + Ok(v) => v, - for socket in sockets { - let local_addr = socket.local_addr().unwrap(); + Err(quiche::Error::Done) => { + trace!("{} -> {}: done writing", local_addr, peer_addr); + break; + }, - for peer_addr in conn.paths_iter(local_addr) { - loop { - let (write, send_info) = match conn.send_on_path( - &mut out, - Some(local_addr), - Some(peer_addr), - ) { - Ok(v) => v, - - Err(quiche::Error::Done) => { - trace!( - "{} -> {}: done writing", - local_addr, - peer_addr - ); - break; - }, - - Err(e) => { - error!( - "{} -> {}: send failed: {:?}", - local_addr, peer_addr, e - ); - - conn.close(false, 0x1, b"fail").ok(); - break; - }, - }; - - if let Err(e) = socket.send_to(&out[..write], send_info.to) { - if e.kind() == std::io::ErrorKind::WouldBlock { - trace!( - "{} -> {}: send() would block", - local_addr, - send_info.to - ); - break; - } + Err(e) => { + error!( + "{} -> {}: send failed: {:?}", + local_addr, peer_addr, e + ); - return Err(ClientError::Other(format!( - "{} -> {}: send() failed: {:?}", - local_addr, send_info.to, e - ))); + conn.close(false, 0x1, b"fail").ok(); + break; + }, + }; + + if let Err(e) = socket.send_to(&out[..write], send_info.to) { + if e.kind() == std::io::ErrorKind::WouldBlock { + trace!( + "{} -> {}: send() would block", + local_addr, + send_info.to + ); + break; } - trace!( - "{} -> {}: written {}", - local_addr, - send_info.to, - write - ); + return Err(ClientError::Other(format!( + "{} -> {}: send() failed: {:?}", + local_addr, send_info.to, e + ))); } + + trace!("{} -> {}: written {}", local_addr, send_info.to, write); } } @@ -588,3 +610,76 @@ pub fn connect( Ok(()) } + +fn create_sockets( + poll: &mut mio::Poll, peer_addr: &std::net::SocketAddr, args: &ClientArgs, +) -> ( + Slab, + HashMap, + std::net::SocketAddr, +) { + let mut sockets = Slab::with_capacity(std::cmp::max(args.addrs.len(), 1)); + let mut src_addrs = HashMap::new(); + let mut first_local_addr = None; + + // Create UDP sockets backing the QUIC connection, and register them with + // the event loop. Check first user-provided addresses and keep the ones + // compatible with the address family of the peer. + for src_addr in args.addrs.iter().filter(|sa| { + (sa.is_ipv4() && peer_addr.is_ipv4()) || + (sa.is_ipv6() && peer_addr.is_ipv6()) + }) { + let socket = mio::net::UdpSocket::bind(*src_addr).unwrap(); + let local_addr = socket.local_addr().unwrap(); + let token = sockets.insert(socket); + src_addrs.insert(local_addr, token); + poll.registry() + .register( + &mut sockets[token], + mio::Token(token), + mio::Interest::READABLE, + ) + .unwrap(); + if first_local_addr.is_none() { + first_local_addr = Some(local_addr); + } + } + + // If there is no such address, rely on the default INADDR_IN or IN6ADDR_ANY + // depending on the IP family of the server address. This is needed on macOS + // and BSD variants that don't support binding to IN6ADDR_ANY for both v4 + // and v6. + if first_local_addr.is_none() { + let bind_addr = match peer_addr { + std::net::SocketAddr::V4(_) => "0.0.0.0:0", + std::net::SocketAddr::V6(_) => "[::]:0", + }; + let bind_addr = bind_addr.parse().unwrap(); + let socket = mio::net::UdpSocket::bind(bind_addr).unwrap(); + let local_addr = socket.local_addr().unwrap(); + let token = sockets.insert(socket); + src_addrs.insert(local_addr, token); + poll.registry() + .register( + &mut sockets[token], + mio::Token(token), + mio::Interest::READABLE, + ) + .unwrap(); + first_local_addr = Some(local_addr) + } + + (sockets, src_addrs, first_local_addr.unwrap()) +} + +/// Generate a ordered list of 4-tuples on which the host should send packets, +/// following a lowest-latency scheduling. +fn lowest_latency_scheduler( + conn: &quiche::Connection, +) -> impl Iterator { + use itertools::Itertools; + conn.path_stats() + .filter(|p| !matches!(p.state, quiche::PathState::Closed(_, _))) + .sorted_by_key(|p| p.rtt) + .map(|p| (p.local_addr, p.peer_addr)) +} diff --git a/catalog-info.yaml b/catalog-info.yaml new file mode 100644 index 0000000000..e9be3dcfb9 --- /dev/null +++ b/catalog-info.yaml @@ -0,0 +1,23 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + title: "quiche" + name: "quiche" + description: "Savoury implementation of the QUIC transport protocol and HTTP/3." + annotations: + backstage.io/source-location: url:https://github.com/cloudflare/quiche + cloudflare.com/jira-project-key: "FLPROTO" + cloudflare.com/software-excellence-opt-in: "true" + links: + - title: Documentation + url: https://docs.quic.tech/quiche/ + icon: docs + tags: + - external + - edge +spec: + type: "library" + lifecycle: "Active" + owner: "teams/fl-protocols" + dependsOn: + - component:default/boringssl diff --git a/fuzz/src/packet_recv_client.rs b/fuzz/src/packet_recv_client.rs index b5ab38deeb..7fc25bf7e2 100644 --- a/fuzz/src/packet_recv_client.rs +++ b/fuzz/src/packet_recv_client.rs @@ -40,8 +40,8 @@ fuzz_target!(|data: &[u8]| { let mut conn = quiche::connect( Some("quic.tech"), &SCID, - to.clone(), - from.clone(), + to, + from, &mut CONFIG.lock().unwrap(), ) .unwrap(); diff --git a/fuzz/src/qpack_decode.rs b/fuzz/src/qpack_decode.rs index 69fdf9c290..4098178d57 100644 --- a/fuzz/src/qpack_decode.rs +++ b/fuzz/src/qpack_decode.rs @@ -16,7 +16,7 @@ fuzz_target!(|data: &[u8]| { let mut decoder = quiche::h3::qpack::Decoder::new(); let mut encoder = quiche::h3::qpack::Encoder::new(); - let hdrs = match decoder.decode(&mut data.to_vec(), u64::MAX) { + let hdrs = match decoder.decode(data, u64::MAX) { Err(_) => return, Ok(hdrs) => hdrs, }; diff --git a/qlog/Cargo.toml b/qlog/Cargo.toml index 8ac8ace785..a3cbcabef5 100644 --- a/qlog/Cargo.toml +++ b/qlog/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qlog" -version = "0.9.0" +version = "0.11.0" authors = ["Lucas Pardue "] edition = "2018" description = "qlog data model for QUIC and HTTP/3" diff --git a/qlog/src/events/connectivity.rs b/qlog/src/events/connectivity.rs index 19a442cc72..1b579232b1 100644 --- a/qlog/src/events/connectivity.rs +++ b/qlog/src/events/connectivity.rs @@ -61,6 +61,7 @@ pub enum ConnectivityEventType { ConnectionIdUpdated, SpinBitUpdated, ConnectionStateUpdated, + MtuUpdated, } #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)] @@ -136,3 +137,11 @@ pub struct ConnectionStateUpdated { pub old: Option, pub new: ConnectionState, } + +#[serde_with::skip_serializing_none] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] +pub struct MtuUpdated { + pub old: Option, + pub new: u16, + pub done: Option, +} diff --git a/qlog/src/events/mod.rs b/qlog/src/events/mod.rs index 026869c0fd..ac18276fd0 100644 --- a/qlog/src/events/mod.rs +++ b/qlog/src/events/mod.rs @@ -35,6 +35,10 @@ use connectivity::ConnectivityEventType; use serde::Deserialize; use serde::Serialize; +use std::collections::BTreeMap; + +pub type ExData = BTreeMap; + #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Default)] #[serde(untagged)] pub enum EventType { @@ -87,6 +91,9 @@ pub struct Event { #[serde(flatten)] pub data: EventData, + #[serde(flatten)] + pub ex_data: ExData, + pub protocol_type: Option, pub group_id: Option, @@ -99,20 +106,32 @@ pub struct Event { impl Event { /// Returns a new `Event` object with the provided time and data. pub fn with_time(time: f32, data: EventData) -> Self { + Self::with_time_ex(time, data, Default::default()) + } + + /// Returns a new `Event` object with the provided time, data and ex_data. + pub fn with_time_ex(time: f32, data: EventData, ex_data: ExData) -> Self { let ty = EventType::from(&data); Event { time, data, + ex_data, protocol_type: Default::default(), group_id: Default::default(), time_format: Default::default(), ty, } } +} - pub fn importance(&self) -> EventImportance { +impl Eventable for Event { + fn importance(&self) -> EventImportance { self.ty.into() } + + fn set_time(&mut self, time: f32) { + self.time = time; + } } impl PartialEq for Event { @@ -120,14 +139,37 @@ impl PartialEq for Event { fn eq(&self, other: &Event) -> bool { self.time == other.time && self.data == other.data && + self.ex_data == other.ex_data && self.protocol_type == other.protocol_type && self.group_id == other.group_id && self.time_format == other.time_format } } -#[derive(Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct JsonEvent { + pub time: f32, + + #[serde(skip)] + pub importance: EventImportance, + + pub name: String, + pub data: serde_json::Value, +} + +impl Eventable for JsonEvent { + fn importance(&self) -> EventImportance { + self.importance + } + + fn set_time(&mut self, time: f32) { + self.time = time; + } +} + +#[derive(Clone, Copy, Debug, Default)] pub enum EventImportance { + #[default] Core, Base, Extra, @@ -169,14 +211,26 @@ impl From for EventImportance { EventType::ConnectivityEventType( ConnectivityEventType::ConnectionStateUpdated, ) => EventImportance::Base, + EventType::ConnectivityEventType( + ConnectivityEventType::MtuUpdated, + ) => EventImportance::Extra, EventType::SecurityEventType(SecurityEventType::KeyUpdated) => EventImportance::Base, EventType::SecurityEventType(SecurityEventType::KeyDiscarded) => EventImportance::Base, + EventType::TransportEventType( + TransportEventType::VersionInformation, + ) => EventImportance::Core, + EventType::TransportEventType( + TransportEventType::AlpnInformation, + ) => EventImportance::Core, EventType::TransportEventType(TransportEventType::ParametersSet) => EventImportance::Core, + EventType::TransportEventType( + TransportEventType::ParametersRestored, + ) => EventImportance::Base, EventType::TransportEventType( TransportEventType::DatagramsReceived, ) => EventImportance::Extra, @@ -193,6 +247,8 @@ impl From for EventImportance { EventImportance::Base, EventType::TransportEventType(TransportEventType::PacketBuffered) => EventImportance::Base, + EventType::TransportEventType(TransportEventType::PacketsAcked) => + EventImportance::Extra, EventType::TransportEventType( TransportEventType::StreamStateUpdated, ) => EventImportance::Base, @@ -248,6 +304,12 @@ impl From for EventImportance { } } +pub trait Eventable { + fn importance(&self) -> EventImportance; + + fn set_time(&mut self, time: f32); +} + #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "snake_case")] pub enum EventCategory { @@ -328,6 +390,9 @@ impl From<&EventData> for EventType { EventType::ConnectivityEventType( ConnectivityEventType::ConnectionStateUpdated, ), + EventData::MtuUpdated { .. } => EventType::ConnectivityEventType( + ConnectivityEventType::MtuUpdated, + ), EventData::KeyUpdated { .. } => EventType::SecurityEventType(SecurityEventType::KeyUpdated), @@ -476,6 +541,9 @@ pub enum EventData { #[serde(rename = "connectivity:connection_state_updated")] ConnectionStateUpdated(connectivity::ConnectionStateUpdated), + #[serde(rename = "connectivity:mtu_updated")] + MtuUpdated(connectivity::MtuUpdated), + // Security #[serde(rename = "security:key_updated")] KeyUpdated(security::KeyUpdated), @@ -517,7 +585,7 @@ pub enum EventData { #[serde(rename = "transport:packet_buffered")] PacketBuffered(quic::PacketBuffered), - #[serde(rename = "transport:version_information")] + #[serde(rename = "transport:packets_acked")] PacketsAcked(quic::PacketsAcked), #[serde(rename = "transport:stream_state_updated")] diff --git a/qlog/src/events/quic.rs b/qlog/src/events/quic.rs index a7c1fa3225..74ab29c0b2 100644 --- a/qlog/src/events/quic.rs +++ b/qlog/src/events/quic.rs @@ -372,6 +372,9 @@ pub enum QuicFrameTypeName { ApplicationClose, HandshakeDone, Datagram, + AckMp, + PathAbandon, + PathStatus, Unknown, } @@ -493,6 +496,35 @@ pub enum QuicFrame { raw: Option, }, + AckMp { + space_identifier: u64, + + ack_delay: Option, + acked_ranges: Option, + + ect1: Option, + + ect0: Option, + + ce: Option, + }, + + PathAbandon { + dcid_seq_num: u64, + error_code: u64, + reason: Option, + }, + + PathStandby { + dcid_seq_num: u64, + seq_num: u64, + }, + + PathAvailable { + dcid_seq_num: u64, + seq_num: u64, + }, + Unknown { raw_frame_type: u64, frame_type_value: Option, diff --git a/qlog/src/lib.rs b/qlog/src/lib.rs index 600ae9feb7..68ff278fcd 100644 --- a/qlog/src/lib.rs +++ b/qlog/src/lib.rs @@ -967,4 +967,5 @@ mod tests { } pub mod events; +pub mod reader; pub mod streamer; diff --git a/qlog/src/reader.rs b/qlog/src/reader.rs new file mode 100644 index 0000000000..89a8e12a44 --- /dev/null +++ b/qlog/src/reader.rs @@ -0,0 +1,111 @@ +// Copyright (C) 2023, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::QlogSeq; + +/// Represents the format of the read event. +#[allow(clippy::large_enum_variant)] +pub enum Event { + /// A native qlog event type. + Qlog(crate::events::Event), + + // An extended JSON event type. + Json(crate::events::JsonEvent), +} + +/// A helper object specialized for reading JSON-SEQ qlog from a [`BufRead`] +/// trait. +/// +/// [`BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html +pub struct QlogSeqReader { + pub qlog: QlogSeq, + reader: Box, +} + +impl QlogSeqReader { + pub fn new( + mut reader: Box, + ) -> Result> { + // "null record" skip it + Self::read_record(reader.as_mut()); + + let header = Self::read_record(reader.as_mut()).ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::Other, + "error reading file header bytes", + ) + })?; + + let res: Result = + serde_json::from_slice(&header); + match res { + Ok(qlog) => Ok(Self { qlog, reader }), + + Err(e) => Err(e.into()), + } + } + + fn read_record( + reader: &mut (dyn std::io::BufRead + Send + Sync), + ) -> Option> { + let mut buf = Vec::::new(); + let size = reader.read_until(b'', &mut buf).unwrap(); + if size <= 1 { + return None; + } + + buf.truncate(buf.len() - 1); + + Some(buf) + } +} + +impl Iterator for QlogSeqReader { + type Item = Event; + + #[inline] + fn next(&mut self) -> Option { + // Attempt to deserialize events but skip them if that fails for any + // reason, ensuring we always read all bytes in the reader. + while let Some(bytes) = Self::read_record(&mut self.reader) { + let r: serde_json::Result = + serde_json::from_slice(&bytes); + + if let Ok(event) = r { + return Some(Event::Qlog(event)); + } + + let r: serde_json::Result = + serde_json::from_slice(&bytes); + + if let Ok(event) = r { + return Some(Event::Json(event)); + } + } + + None + } +} diff --git a/qlog/src/streamer.rs b/qlog/src/streamer.rs index e0ef324c08..16761b3a67 100644 --- a/qlog/src/streamer.rs +++ b/qlog/src/streamer.rs @@ -27,6 +27,8 @@ use crate::events::EventData; use crate::events::EventImportance; use crate::events::EventType; +use crate::events::Eventable; +use crate::events::ExData; /// A helper object specialized for streaming JSON-serialized qlog to a /// [`Write`] trait. @@ -134,17 +136,20 @@ impl QlogStreamer { Ok(()) } - /// Writes a JSON-SEQ-serialized [Event] using [std::time::Instant::now()]. - pub fn add_event_now(&mut self, event: Event) -> Result<()> { + /// Writes a serializable to a JSON-SEQ record using + /// [std::time::Instant::now()]. + pub fn add_event_now( + &mut self, event: E, + ) -> Result<()> { let now = std::time::Instant::now(); self.add_event_with_instant(event, now) } - /// Writes a JSON-SEQ-serialized [Event] using the provided + /// Writes a serializable to a JSON-SEQ record using the provided /// [std::time::Instant]. - pub fn add_event_with_instant( - &mut self, mut event: Event, now: std::time::Instant, + pub fn add_event_with_instant( + &mut self, mut event: E, now: std::time::Instant, ) -> Result<()> { if self.state != StreamerState::Ready { return Err(Error::InvalidState); @@ -161,23 +166,40 @@ impl QlogStreamer { }; let rel_time = dur.as_secs_f32() * 1000.0; - event.time = rel_time; + event.set_time(rel_time); self.add_event(event) } - /// Writes a JSON-SEQ-serialized [Event] based on the provided [EventData] + /// Writes an [Event] based on the provided [EventData] to a JSON-SEQ record /// at time [std::time::Instant::now()]. pub fn add_event_data_now(&mut self, event_data: EventData) -> Result<()> { + self.add_event_data_ex_now(event_data, Default::default()) + } + + /// Writes an [Event] based on the provided [EventData] and [ExData] to a + /// JSON-SEQ record at time [std::time::Instant::now()]. + pub fn add_event_data_ex_now( + &mut self, event_data: EventData, ex_data: ExData, + ) -> Result<()> { let now = std::time::Instant::now(); - self.add_event_data_with_instant(event_data, now) + self.add_event_data_ex_with_instant(event_data, ex_data, now) } - /// Writes a JSON-SEQ-serialized [Event] based on the provided [EventData] - /// and [std::time::Instant]. + /// Writes an [Event] based on the provided [EventData] and + /// [std::time::Instant] to a JSON-SEQ record. pub fn add_event_data_with_instant( &mut self, event_data: EventData, now: std::time::Instant, + ) -> Result<()> { + self.add_event_data_ex_with_instant(event_data, Default::default(), now) + } + + /// Writes an [Event] based on the provided [EventData], [ExData], and + /// [std::time::Instant] to a JSON-SEQ record. + pub fn add_event_data_ex_with_instant( + &mut self, event_data: EventData, ex_data: ExData, + now: std::time::Instant, ) -> Result<()> { if self.state != StreamerState::Ready { return Err(Error::InvalidState); @@ -195,13 +217,15 @@ impl QlogStreamer { }; let rel_time = dur.as_secs_f32() * 1000.0; - let event = Event::with_time(rel_time, event_data); + let event = Event::with_time_ex(rel_time, event_data, ex_data); self.add_event(event) } /// Writes a JSON-SEQ-serialized [Event] using the provided [Event]. - pub fn add_event(&mut self, event: Event) -> Result<()> { + pub fn add_event( + &mut self, event: E, + ) -> Result<()> { if self.state != StreamerState::Ready { return Err(Error::InvalidState); } @@ -229,8 +253,16 @@ impl QlogStreamer { } } +impl Drop for QlogStreamer { + fn drop(&mut self) { + let _ = self.finish_log(); + } +} + #[cfg(test)] mod tests { + use std::collections::BTreeMap; + use super::*; use crate::events::quic; use crate::events::quic::QuicFrame; @@ -238,6 +270,8 @@ mod tests { use smallvec::smallvec; use testing::*; + use serde_json::json; + #[test] fn serialization_states() { let v: Vec = Vec::new(); @@ -368,4 +402,143 @@ mod tests { assert_eq!(log_string, written_string); } + + #[test] + fn stream_json_event() { + let data = json!({"foo": "Bar", "hello": 123}); + let ev = events::JsonEvent { + time: 0.0, + importance: events::EventImportance::Core, + name: "jsonevent:sample".into(), + data, + }; + + let v: Vec = Vec::new(); + let buff = std::io::Cursor::new(v); + let writer = Box::new(buff); + + let trace = make_trace_seq(); + + let mut s = streamer::QlogStreamer::new( + "version".to_string(), + Some("title".to_string()), + Some("description".to_string()), + None, + std::time::Instant::now(), + trace, + EventImportance::Base, + writer, + ); + + assert!(matches!(s.start_log(), Ok(()))); + assert!(matches!(s.add_event(ev), Ok(()))); + assert!(matches!(s.finish_log(), Ok(()))); + + let r = s.writer(); + #[allow(clippy::borrowed_box)] + let w: &Box>> = unsafe { std::mem::transmute(r) }; + + let log_string = r#"{"qlog_version":"version","qlog_format":"JSON-SEQ","title":"title","description":"description","trace":{"vantage_point":{"type":"server"},"title":"Quiche qlog trace","description":"Quiche qlog trace description","configuration":{"time_offset":0.0}}} +{"time":0.0,"name":"jsonevent:sample","data":{"foo":"Bar","hello":123}} +"#; + + let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap(); + + assert_eq!(log_string, written_string); + } + + #[test] + fn stream_data_ex() { + let v: Vec = Vec::new(); + let buff = std::io::Cursor::new(v); + let writer = Box::new(buff); + + let trace = make_trace_seq(); + let pkt_hdr = make_pkt_hdr(quic::PacketType::Handshake); + let raw = Some(RawInfo { + length: Some(1251), + payload_length: Some(1224), + data: None, + }); + + let frame1 = QuicFrame::Stream { + stream_id: 40, + offset: 40, + length: 400, + fin: Some(true), + raw: None, + }; + + let event_data1 = EventData::PacketSent(quic::PacketSent { + header: pkt_hdr.clone(), + frames: Some(smallvec![frame1]), + is_coalesced: None, + retry_token: None, + stateless_reset_token: None, + supported_versions: None, + raw: raw.clone(), + datagram_id: None, + send_at_time: None, + trigger: None, + }); + let j1 = json!({"foo": "Bar", "hello": 123}); + let j2 = json!({"baz": [1,2,3,4]}); + let mut ex_data = BTreeMap::new(); + ex_data.insert("first".to_string(), j1); + ex_data.insert("second".to_string(), j2); + + let ev1 = Event::with_time_ex(0.0, event_data1, ex_data); + + let frame2 = QuicFrame::Stream { + stream_id: 1, + offset: 0, + length: 100, + fin: Some(true), + raw: None, + }; + + let event_data2 = EventData::PacketSent(quic::PacketSent { + header: pkt_hdr.clone(), + frames: Some(smallvec![frame2]), + is_coalesced: None, + retry_token: None, + stateless_reset_token: None, + supported_versions: None, + raw: raw.clone(), + datagram_id: None, + send_at_time: None, + trigger: None, + }); + + let ev2 = Event::with_time(0.0, event_data2); + + let mut s = streamer::QlogStreamer::new( + "version".to_string(), + Some("title".to_string()), + Some("description".to_string()), + None, + std::time::Instant::now(), + trace, + EventImportance::Base, + writer, + ); + + assert!(matches!(s.start_log(), Ok(()))); + assert!(matches!(s.add_event(ev1), Ok(()))); + assert!(matches!(s.add_event(ev2), Ok(()))); + assert!(matches!(s.finish_log(), Ok(()))); + + let r = s.writer(); + #[allow(clippy::borrowed_box)] + let w: &Box>> = unsafe { std::mem::transmute(r) }; + + let log_string = r#"{"qlog_version":"version","qlog_format":"JSON-SEQ","title":"title","description":"description","trace":{"vantage_point":{"type":"server"},"title":"Quiche qlog trace","description":"Quiche qlog trace description","configuration":{"time_offset":0.0}}} +{"time":0.0,"name":"transport:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":40,"offset":40,"length":400,"fin":true}]},"first":{"foo":"Bar","hello":123},"second":{"baz":[1,2,3,4]}} +{"time":0.0,"name":"transport:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":1,"offset":0,"length":100,"fin":true}]}} +"#; + + let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap(); + + assert_eq!(log_string, written_string); + } } diff --git a/quiche/Cargo.toml b/quiche/Cargo.toml index 4ebf1ba44c..b242f4d81c 100644 --- a/quiche/Cargo.toml +++ b/quiche/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quiche" -version = "0.18.0" +version = "0.20.0" authors = ["Alessandro Ghedini "] edition = "2018" build = "src/build.rs" @@ -45,6 +45,9 @@ fuzzing = [] # Build and expose the FFI API. ffi = [] +# Exposes internal APIs that have no stability guarantees across versions. +internal = [] + [package.metadata.docs.rs] no-default-features = true features = ["boringssl-boring-crate", "qlog"] @@ -58,14 +61,14 @@ either = { version = "1.8", default-features = false } log = { version = "0.4", features = ["std"] } libc = "0.2" libm = "0.2" -ring = "0.16" +ring = "0.17" slab = "0.4" once_cell = "1" octets = { version = "0.2", path = "../octets" } -boring = { version = "3", optional = true } +boring = { version = "4", optional = true } foreign-types-shared = { version = "0.3.0", optional = true } intrusive-collections = "0.9.5" -qlog = { version = "0.9", path = "../qlog", optional = true } +qlog = { version = "0.11", path = "../qlog", optional = true } sfv = { version = "0.9", optional = true } smallvec = { version = "1.10", features = ["serde", "union"] } @@ -74,7 +77,7 @@ winapi = { version = "0.3", features = ["wincrypt", "ws2def", "ws2ipdef", "ws2tc [dev-dependencies] mio = { version = "0.8", features = ["net", "os-poll"] } -url = "1" +url = "2.5" [lib] crate-type = ["lib", "staticlib", "cdylib"] diff --git a/quiche/examples/client.rs b/quiche/examples/client.rs index 2f576fcb46..9a5fb3957a 100644 --- a/quiche/examples/client.rs +++ b/quiche/examples/client.rs @@ -27,8 +27,6 @@ #[macro_use] extern crate log; -use std::net::ToSocketAddrs; - use ring::rand::*; const MAX_DATAGRAM_SIZE: usize = 1350; @@ -56,7 +54,7 @@ fn main() { let mut events = mio::Events::with_capacity(1024); // Resolve server address. - let peer_addr = url.to_socket_addrs().unwrap().next().unwrap(); + let peer_addr = url.socket_addrs(|| None).unwrap()[0]; // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the // server address. This is needed on macOS and BSD variants that don't diff --git a/quiche/examples/http3-client.rs b/quiche/examples/http3-client.rs index b7631601be..92646e0dfd 100644 --- a/quiche/examples/http3-client.rs +++ b/quiche/examples/http3-client.rs @@ -27,8 +27,6 @@ #[macro_use] extern crate log; -use std::net::ToSocketAddrs; - use quiche::h3::NameValue; use ring::rand::*; @@ -56,7 +54,7 @@ fn main() { let mut events = mio::Events::with_capacity(1024); // Resolve server address. - let peer_addr = url.to_socket_addrs().unwrap().next().unwrap(); + let peer_addr = url.socket_addrs(|| None).unwrap()[0]; // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the // server address. This is needed on macOS and BSD variants that don't diff --git a/quiche/include/quiche.h b/quiche/include/quiche.h index 72ffe7eb6d..c0d1406b7c 100644 --- a/quiche/include/quiche.h +++ b/quiche/include/quiche.h @@ -251,6 +251,12 @@ void quiche_config_set_active_connection_id_limit(quiche_config *config, uint64_ // Sets the initial stateless reset token. |v| must contain 16 bytes, otherwise the behaviour is undefined. void quiche_config_set_stateless_reset_token(quiche_config *config, const uint8_t *v); +// Sets whether the QUIC connection should avoid reusing DCIDs over different paths. +void quiche_config_set_disable_dcid_reuse(quiche_config *config, bool v); + +// Configures the session ticket key material. +int quiche_config_set_ticket_key(quiche_config *config, const uint8_t *key, size_t key_len); + // Frees the config object. void quiche_config_free(quiche_config *config); @@ -268,15 +274,15 @@ typedef struct quiche_conn quiche_conn; // Creates a new server-side connection. quiche_conn *quiche_accept(const uint8_t *scid, size_t scid_len, const uint8_t *odcid, size_t odcid_len, - const struct sockaddr *local, size_t local_len, - const struct sockaddr *peer, size_t peer_len, + const struct sockaddr *local, socklen_t local_len, + const struct sockaddr *peer, socklen_t peer_len, quiche_config *config); // Creates a new client-side connection. quiche_conn *quiche_connect(const char *server_name, const uint8_t *scid, size_t scid_len, - const struct sockaddr *local, size_t local_len, - const struct sockaddr *peer, size_t peer_len, + const struct sockaddr *local, socklen_t local_len, + const struct sockaddr *peer, socklen_t peer_len, quiche_config *config); // Writes a version negotiation packet. @@ -296,8 +302,8 @@ bool quiche_version_is_supported(uint32_t version); quiche_conn *quiche_conn_new_with_tls(const uint8_t *scid, size_t scid_len, const uint8_t *odcid, size_t odcid_len, - const struct sockaddr *local, size_t local_len, - const struct sockaddr *peer, size_t peer_len, + const struct sockaddr *local, socklen_t local_len, + const struct sockaddr *peer, socklen_t peer_len, const quiche_config *config, void *ssl, bool is_server); @@ -352,6 +358,19 @@ ssize_t quiche_conn_send(quiche_conn *conn, uint8_t *out, size_t out_len, // Returns the size of the send quantum, in bytes. size_t quiche_conn_send_quantum(const quiche_conn *conn); +// Writes a single QUIC packet to be sent to the peer from the specified +// local address "from" to the destination address "to". +ssize_t quiche_conn_send_on_path(quiche_conn *conn, uint8_t *out, size_t out_len, + const struct sockaddr *from, socklen_t from_len, + const struct sockaddr *to, socklen_t to_len, + quiche_send_info *out_info); + +// Returns the size of the send quantum over the given 4-tuple, in bytes. +size_t quiche_conn_send_quantum_on_path(const quiche_conn *conn, + const struct sockaddr *local_addr, socklen_t local_len, + const struct sockaddr *peer_addr, socklen_t peer_len); + + // Reads contiguous data from a stream. ssize_t quiche_conn_stream_recv(quiche_conn *conn, uint64_t stream_id, uint8_t *out, size_t buf_len, bool *fin); @@ -426,6 +445,18 @@ void quiche_conn_trace_id(const quiche_conn *conn, const uint8_t **out, size_t * // Returns the source connection ID. void quiche_conn_source_id(const quiche_conn *conn, const uint8_t **out, size_t *out_len); +typedef struct quiche_connection_id_iter quiche_connection_id_iter; + +// Returns all active source connection IDs. +quiche_connection_id_iter *quiche_conn_source_ids(quiche_conn *conn); + +// Fetches the next id from the given iterator. Returns false if there are +// no more elements in the iterator. +bool quiche_connection_id_iter_next(quiche_connection_id_iter *iter, const uint8_t **out, size_t *out_len); + +// Frees the given path iterator object. +void quiche_connection_id_iter_free(quiche_connection_id_iter *iter); + // Returns the destination connection ID. void quiche_conn_destination_id(const quiche_conn *conn, const uint8_t **out, size_t *out_len); @@ -442,6 +473,9 @@ void quiche_conn_session(const quiche_conn *conn, const uint8_t **out, size_t *o // Returns true if the connection handshake is complete. bool quiche_conn_is_established(const quiche_conn *conn); +// Returns true if the connection is resumed. +bool quiche_conn_is_resumed(const quiche_conn *conn); + // Returns true if the connection has a pending handshake that has progressed // enough to send or receive early data. bool quiche_conn_is_in_early_data(const quiche_conn *conn); @@ -516,6 +550,18 @@ typedef struct { // The number of known paths for the connection. size_t paths_count; + + // The number of streams reset by local. + uint64_t reset_stream_count_local; + + // The number of streams stopped by local. + uint64_t stopped_stream_count_local; + + // The number of streams reset by remote. + uint64_t reset_stream_count_remote; + + // The number of streams stopped by remote. + uint64_t stopped_stream_count_remote; } quiche_stats; // Collects and returns statistics about the connection. @@ -658,13 +704,127 @@ ssize_t quiche_conn_dgram_send(quiche_conn *conn, const uint8_t *buf, void quiche_conn_dgram_purge_outgoing(quiche_conn *conn, bool (*f)(uint8_t *, size_t)); +// Returns whether or not the DATAGRAM send queue is full. +bool quiche_conn_is_dgram_send_queue_full(const quiche_conn *conn); + +// Returns whether or not the DATAGRAM recv queue is full. +bool quiche_conn_is_dgram_recv_queue_full(const quiche_conn *conn); + // Schedule an ack-eliciting packet on the active path. ssize_t quiche_conn_send_ack_eliciting(quiche_conn *conn); // Schedule an ack-eliciting packet on the specified path. ssize_t quiche_conn_send_ack_eliciting_on_path(quiche_conn *conn, - const struct sockaddr *local, size_t local_len, - const struct sockaddr *peer, size_t peer_len); + const struct sockaddr *local, socklen_t local_len, + const struct sockaddr *peer, socklen_t peer_len); + +// Returns true if there are retired source connection ids and fill the parameters +bool quiche_conn_retired_scid_next(const quiche_conn *conn, const uint8_t **out, size_t *out_len); + +// Returns the number of source Connection IDs that are retired. +size_t quiche_conn_retired_scids(const quiche_conn *conn); + +// Returns the number of spare Destination Connection IDs, i.e., +// Destination Connection IDs that are still unused. +size_t quiche_conn_available_dcids(const quiche_conn *conn); + +// Returns the number of source Connection IDs that should be provided +// to the peer without exceeding the limit it advertised. +size_t quiche_conn_scids_left(quiche_conn *conn); + +// Returns the number of source Connection IDs that are active. This is +// only meaningful if the host uses non-zero length Source Connection IDs. +size_t quiche_conn_active_scids(quiche_conn *conn); + +// Provides additional source Connection IDs that the peer can use to reach +// this host. Writes the sequence number to "scid_seq" and returns 0. +int quiche_conn_new_scid(quiche_conn *conn, + const uint8_t *scid, size_t scid_len, + const uint8_t *reset_token, bool retire_if_needed, uint64_t *scid_seq); + +// Requests the stack to perform path validation of the proposed 4-tuple. +int quiche_conn_probe_path(quiche_conn *conn, + const struct sockaddr *local, socklen_t local_len, + const struct sockaddr *peer, socklen_t peer_len, uint64_t *seq); + +// Migrates the connection to a new local address. +int quiche_conn_migrate_source(quiche_conn *conn, const struct sockaddr *local, socklen_t local_len, uint64_t *seq); + +// Migrates the connection over the given network path between "local" +// and "peer". +int quiche_conn_migrate(quiche_conn *conn, + const struct sockaddr *local, socklen_t local_len, + const struct sockaddr *peer, socklen_t peer_len, + uint64_t *seq); + +enum quiche_path_event_type { + QUICHE_PATH_EVENT_NEW, + QUICHE_PATH_EVENT_VALIDATED, + QUICHE_PATH_EVENT_FAILED_VALIDATION, + QUICHE_PATH_EVENT_CLOSED, + QUICHE_PATH_EVENT_REUSED_SOURCE_CONNECTION_ID, + QUICHE_PATH_EVENT_PEER_MIGRATED, +}; + +typedef struct quiche_path_event quiche_path_event; + +// Retrieves the next event. Returns NULL if there is no event to process. +const quiche_path_event *quiche_conn_path_event_next(quiche_conn *conn); + +// Returns the type of the event. +enum quiche_path_event_type quiche_path_event_type(quiche_path_event *ev); + +// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_NEW. +void quiche_path_event_new(quiche_path_event *ev, + struct sockaddr_storage *local, socklen_t *local_len, struct sockaddr_storage *peer, socklen_t *peer_len); + +// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_VALIDATED. +void quiche_path_event_validated(quiche_path_event *ev, + struct sockaddr_storage *local, socklen_t *local_len, struct sockaddr_storage *peer, socklen_t *peer_len); + +// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_FAILED_VALIDATION. +void quiche_path_event_failed_validation(quiche_path_event *ev, + struct sockaddr_storage *local, socklen_t *local_len, struct sockaddr_storage *peer, socklen_t *peer_len); + +// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_CLOSED. +void quiche_path_event_closed(quiche_path_event *ev, + struct sockaddr_storage *local, socklen_t *local_len, struct sockaddr_storage *peer, socklen_t *peer_len); + +// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_REUSED_SOURCE_CONNECTION_ID. +void quiche_path_event_reused_source_connection_id(quiche_path_event *ev, uint64_t *id, + struct sockaddr_storage *old_local, socklen_t *old_local_len, + struct sockaddr_storage *old_peer, socklen_t *old_peer_len, + struct sockaddr_storage *local, socklen_t *local_len, + struct sockaddr_storage *peer, socklen_t *peer_len); + +// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_PEER_MIGRATED. +void quiche_path_event_peer_migrated(quiche_path_event *ev, + struct sockaddr_storage *local, socklen_t *local_len, + struct sockaddr_storage *peer, socklen_t *peer_len); + +// Frees the path event object. +void quiche_path_event_free(quiche_path_event *ev); + +// Requests the retirement of the destination Connection ID used by the +// host to reach its peer. +int quiche_conn_retire_dcid(quiche_conn *conn, uint64_t dcid_seq); + +typedef struct quiche_socket_addr_iter quiche_socket_addr_iter; + +// Returns an iterator over destination `SockAddr`s whose association +// with "from" forms a known QUIC path on which packets can be sent to. +quiche_socket_addr_iter *quiche_conn_paths_iter(quiche_conn *conn, const struct sockaddr *from, size_t from_len); + +// Fetches the next peer from the given iterator. Returns false if there are +// no more elements in the iterator. +bool quiche_socket_addr_iter_next(quiche_socket_addr_iter *iter, struct sockaddr_storage *peer, size_t *peer_len); + +// Frees the given path iterator object. +void quiche_socket_addr_iter_free(quiche_socket_addr_iter *iter); + +// Returns whether the network path with local address "from and remote address "to" has been validated. +// If the 4-tuple does not exist over the connection, returns an InvalidState. +int quiche_conn_is_path_validated(const quiche_conn *conn, const struct sockaddr *from, size_t from_len, const struct sockaddr *to, size_t to_len); // Frees the connection object. void quiche_conn_free(quiche_conn *conn); @@ -907,23 +1067,23 @@ typedef struct { // Sends an HTTP/3 request. int64_t quiche_h3_send_request(quiche_h3_conn *conn, quiche_conn *quic_conn, - quiche_h3_header *headers, size_t headers_len, + const quiche_h3_header *headers, size_t headers_len, bool fin); // Sends an HTTP/3 response on the specified stream with default priority. int quiche_h3_send_response(quiche_h3_conn *conn, quiche_conn *quic_conn, - uint64_t stream_id, quiche_h3_header *headers, + uint64_t stream_id, const quiche_h3_header *headers, size_t headers_len, bool fin); // Sends an HTTP/3 response on the specified stream with specified priority. int quiche_h3_send_response_with_priority(quiche_h3_conn *conn, quiche_conn *quic_conn, uint64_t stream_id, - quiche_h3_header *headers, size_t headers_len, + const quiche_h3_header *headers, size_t headers_len, quiche_h3_priority *priority, bool fin); // Sends an HTTP/3 body chunk on the given stream. ssize_t quiche_h3_send_body(quiche_h3_conn *conn, quiche_conn *quic_conn, - uint64_t stream_id, uint8_t *body, size_t body_len, + uint64_t stream_id, const uint8_t *body, size_t body_len, bool fin); // Reads request or response body data into the provided buffer. diff --git a/quiche/src/cid.rs b/quiche/src/cid.rs index fdedfe3a13..c895fd5e47 100644 --- a/quiche/src/cid.rs +++ b/quiche/src/cid.rs @@ -191,27 +191,6 @@ impl BoundedNonEmptyConnectionIdVecDeque { } } -/// An iterator over QUIC Connection IDs. -pub struct ConnectionIdIter { - cids: VecDeque>, -} - -impl Iterator for ConnectionIdIter { - type Item = ConnectionId<'static>; - - #[inline] - fn next(&mut self) -> Option { - self.cids.pop_front() - } -} - -impl ExactSizeIterator for ConnectionIdIter { - #[inline] - fn len(&self) -> usize { - self.cids.len() - } -} - #[derive(Default)] pub struct ConnectionIdentifiers { /// All the Destination Connection IDs provided by our peer. @@ -593,9 +572,9 @@ impl ConnectionIdentifiers { /// Updates the Source Connection ID entry with the provided sequence number /// to indicate that it is now linked to the provided path ID. pub fn link_scid_to_path_id( - &mut self, dcid_seq: u64, path_id: usize, + &mut self, scid_seq: u64, path_id: usize, ) -> Result<()> { - let e = self.scids.get_mut(dcid_seq).ok_or(Error::InvalidState)?; + let e = self.scids.get_mut(scid_seq).ok_or(Error::InvalidState)?; e.path_id = Some(path_id); Ok(()) } @@ -698,6 +677,18 @@ impl ConnectionIdentifiers { self.dcids.get_oldest() } + /// Returns the lowest DCID sequence number still in use. + #[inline] + pub fn min_dcid_seq(&self) -> u64 { + self.dcids.iter().map(|e| e.seq).min().unwrap_or(0) + } + + /// Returns the lowest SCID sequence number still in use. + #[inline] + pub fn min_scid_seq(&self) -> u64 { + self.scids.iter().map(|e| e.seq).min().unwrap_or(0) + } + /// Adds or remove the source Connection ID sequence number from the /// source Connection ID set that need to be advertised to the peer through /// NEW_CONNECTION_ID frames. @@ -806,6 +797,12 @@ impl ConnectionIdentifiers { pub fn pop_retired_scid(&mut self) -> Option> { self.retired_scids.pop_front() } + + /// Returns the largest destination Connection ID sequence number seen. + #[inline] + pub fn largest_dcid_seq(&self) -> u64 { + self.largest_destination_seq + } } #[cfg(test)] diff --git a/quiche/src/crypto.rs b/quiche/src/crypto.rs index 598779c147..7240c2dc04 100644 --- a/quiche/src/crypto.rs +++ b/quiche/src/crypto.rs @@ -138,33 +138,34 @@ pub struct Open { impl Open { pub fn new( - alg: Algorithm, key: &[u8], iv: &[u8], hp_key: &[u8], secret: &[u8], + alg: Algorithm, key: Vec, iv: Vec, hp_key: Vec, + secret: Vec, ) -> Result { Ok(Open { alg, - secret: Vec::from(secret), - header: HeaderProtectionKey::new(alg, hp_key)?, packet: PacketKey::new(alg, key, iv)?, + + secret, }) } - pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result { + pub fn from_secret(aead: Algorithm, secret: Vec) -> Result { Ok(Open { alg: aead, - secret: Vec::from(secret), + header: HeaderProtectionKey::from_secret(aead, &secret)?, - header: HeaderProtectionKey::from_secret(aead, secret)?, + packet: PacketKey::from_secret(aead, &secret)?, - packet: PacketKey::from_secret(aead, secret)?, + secret, }) } pub fn open_with_u64_counter( - &self, counter: u64, ad: &[u8], buf: &mut [u8], + &self, path_seq: u32, counter: u64, ad: &[u8], buf: &mut [u8], ) -> Result { if cfg!(feature = "fuzzing") { return Ok(buf.len()); @@ -179,7 +180,7 @@ impl Open { let max_out_len = out_len; - let nonce = make_nonce(&self.packet.nonce, counter); + let nonce = make_nonce(&self.packet.nonce, path_seq, counter); let rc = unsafe { EVP_AEAD_CTX_open( @@ -231,7 +232,10 @@ impl Open { secret: next_secret, - header: HeaderProtectionKey::new(self.alg, &self.header.hp_key)?, + header: HeaderProtectionKey::new( + self.alg, + self.header.hp_key.clone(), + )?, packet: next_packet_key, }) @@ -250,34 +254,35 @@ pub struct Seal { impl Seal { pub fn new( - alg: Algorithm, key: &[u8], iv: &[u8], hp_key: &[u8], secret: &[u8], + alg: Algorithm, key: Vec, iv: Vec, hp_key: Vec, + secret: Vec, ) -> Result { Ok(Seal { alg, - secret: Vec::from(secret), - header: HeaderProtectionKey::new(alg, hp_key)?, packet: PacketKey::new(alg, key, iv)?, + + secret, }) } - pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result { + pub fn from_secret(aead: Algorithm, secret: Vec) -> Result { Ok(Seal { alg: aead, - secret: Vec::from(secret), + header: HeaderProtectionKey::from_secret(aead, &secret)?, - header: HeaderProtectionKey::from_secret(aead, secret)?, + packet: PacketKey::from_secret(aead, &secret)?, - packet: PacketKey::from_secret(aead, secret)?, + secret, }) } pub fn seal_with_u64_counter( - &self, counter: u64, ad: &[u8], buf: &mut [u8], in_len: usize, - extra_in: Option<&[u8]>, + &self, path_seq: u32, counter: u64, ad: &[u8], buf: &mut [u8], + in_len: usize, extra_in: Option<&[u8]>, ) -> Result { if cfg!(feature = "fuzzing") { if let Some(extra) = extra_in { @@ -303,7 +308,7 @@ impl Seal { return Err(Error::CryptoFail); } - let nonce = make_nonce(&self.packet.nonce, counter); + let nonce = make_nonce(&self.packet.nonce, path_seq, counter); let rc = unsafe { EVP_AEAD_CTX_seal_scatter( @@ -358,7 +363,10 @@ impl Seal { secret: next_secret, - header: HeaderProtectionKey::new(self.alg, &self.header.hp_key)?, + header: HeaderProtectionKey::new( + self.alg, + self.header.hp_key.clone(), + )?, packet: next_packet_key, }) @@ -372,12 +380,9 @@ pub struct HeaderProtectionKey { } impl HeaderProtectionKey { - pub fn new(alg: Algorithm, hp_key: &[u8]) -> Result { - aead::quic::HeaderProtectionKey::new(alg.get_ring_hp(), hp_key) - .map(|hpk| Self { - hpk, - hp_key: Vec::from(hp_key), - }) + pub fn new(alg: Algorithm, hp_key: Vec) -> Result { + aead::quic::HeaderProtectionKey::new(alg.get_ring_hp(), &hp_key) + .map(|hpk| Self { hpk, hp_key }) .map_err(|_| Error::CryptoFail) } @@ -388,7 +393,7 @@ impl HeaderProtectionKey { derive_hdr_key(aead, secret, &mut hp_key)?; - Self::new(aead, &hp_key) + Self::new(aead, hp_key) } } @@ -399,11 +404,11 @@ pub struct PacketKey { } impl PacketKey { - pub fn new(alg: Algorithm, key: &[u8], iv: &[u8]) -> Result { + pub fn new(alg: Algorithm, key: Vec, iv: Vec) -> Result { Ok(Self { - ctx: make_aead_ctx(alg, key)?, + ctx: make_aead_ctx(alg, &key)?, - nonce: Vec::from(iv), + nonce: iv, }) } @@ -417,7 +422,7 @@ impl PacketKey { derive_pkt_key(aead, secret, &mut key)?; derive_pkt_iv(aead, secret, &mut iv)?; - Self::new(aead, &key, &iv) + Self::new(aead, key, iv) } } @@ -458,34 +463,34 @@ pub fn derive_initial_key_material( ( Open::new( aead, - &client_key, - &client_iv, - &client_hp_key, - &client_secret, + client_key, + client_iv, + client_hp_key, + client_secret.to_vec(), )?, Seal::new( aead, - &server_key, - &server_iv, - &server_hp_key, - &server_secret, + server_key, + server_iv, + server_hp_key, + server_secret.to_vec(), )?, ) } else { ( Open::new( aead, - &server_key, - &server_iv, - &server_hp_key, - &server_secret, + server_key, + server_iv, + server_hp_key, + server_secret.to_vec(), )?, Seal::new( aead, - &client_key, - &client_iv, - &client_hp_key, - &client_secret, + client_key, + client_iv, + client_hp_key, + client_secret.to_vec(), )?, ) }; @@ -618,10 +623,16 @@ fn hkdf_expand_label( Ok(()) } -fn make_nonce(iv: &[u8], counter: u64) -> [u8; aead::NONCE_LEN] { +fn make_nonce(iv: &[u8], path_seq: u32, counter: u64) -> [u8; aead::NONCE_LEN] { let mut nonce = [0; aead::NONCE_LEN]; nonce.copy_from_slice(iv); + // XOR the four first bytes of the IV with the path_seq. This is equivalent + // to right-padding the path_seq with zero bytes. + for (a, b) in nonce[0..4].iter_mut().zip(path_seq.to_be_bytes().iter()) { + *a ^= b; + } + // XOR the last bytes of the IV with the counter. This is equivalent to // left-padding the counter with zero bytes. for (a, b) in nonce[4..].iter_mut().zip(counter.to_be_bytes().iter()) { @@ -644,7 +655,9 @@ impl hkdf::KeyType for ArbitraryOutputLen { #[allow(non_camel_case_types)] #[repr(transparent)] -struct EVP_AEAD(c_void); +struct EVP_AEAD { + _unused: c_void, +} // NOTE: This structure is copied from in order to be able to // statically allocate it. While it is not often modified upstream, it needs to @@ -805,4 +818,19 @@ mod tests { ]; assert_eq!(&hdr_key, &expected_hdr_key); } + + #[test] + fn nonce() { + let iv = [ + 0x6b, 0x26, 0x11, 0x4b, 0x9c, 0xba, 0x2b, 0x63, 0xa9, 0xe8, 0xdd, + 0x4f, + ]; + let pn = 0xaead; + let path_seq = 3; + let nonce = make_nonce(&iv, path_seq, pn); + assert_eq!(nonce, [ + 0x6b, 0x26, 0x11, 0x48, 0x9c, 0xba, 0x2b, 0x63, 0xa9, 0xe8, 0x73, + 0xe2 + ]); + } } diff --git a/quiche/src/ffi.rs b/quiche/src/ffi.rs index 16430303e9..0183730de4 100644 --- a/quiche/src/ffi.rs +++ b/quiche/src/ffi.rs @@ -393,6 +393,24 @@ pub extern fn quiche_config_set_stateless_reset_token( config.set_stateless_reset_token(Some(reset_token)); } +#[no_mangle] +pub extern fn quiche_config_set_disable_dcid_reuse(config: &mut Config, v: bool) { + config.set_disable_dcid_reuse(v); +} + +#[no_mangle] +pub extern fn quiche_config_set_ticket_key( + config: &mut Config, key: *const u8, key_len: size_t, +) -> c_int { + let key = unsafe { slice::from_raw_parts(key, key_len) }; + + match config.set_ticket_key(key) { + Ok(_) => 0, + + Err(e) => e.to_c() as c_int, + } +} + #[no_mangle] pub extern fn quiche_config_free(config: *mut Config) { drop(unsafe { Box::from_raw(config) }); @@ -764,6 +782,34 @@ pub extern fn quiche_conn_send( } } +#[no_mangle] +pub extern fn quiche_conn_send_on_path( + conn: &mut Connection, out: *mut u8, out_len: size_t, from: *const sockaddr, + from_len: socklen_t, to: *const sockaddr, to_len: socklen_t, + out_info: &mut SendInfo, +) -> ssize_t { + if out_len > ::max_value() as usize { + panic!("The provided buffer is too large"); + } + + let from = optional_std_addr_from_c(from, from_len); + let to = optional_std_addr_from_c(to, to_len); + let out = unsafe { slice::from_raw_parts_mut(out, out_len) }; + + match conn.send_on_path(out, from, to) { + Ok((v, info)) => { + out_info.from_len = std_addr_to_c(&info.from, &mut out_info.from); + out_info.to_len = std_addr_to_c(&info.to, &mut out_info.to); + + std_time_to_c(&info.at, &mut out_info.at); + + v as ssize_t + }, + + Err(e) => e.to_c(), + } +} + #[no_mangle] pub extern fn quiche_conn_stream_recv( conn: &mut Connection, stream_id: u64, out: *mut u8, out_len: size_t, @@ -941,6 +987,52 @@ pub extern fn quiche_conn_trace_id( *out_len = trace_id.len(); } +/// An iterator over connection ids. +#[derive(Default)] +pub struct ConnectionIdIter<'a> { + cids: Vec>, + index: usize, +} + +impl<'a> Iterator for ConnectionIdIter<'a> { + type Item = ConnectionId<'a>; + + #[inline] + fn next(&mut self) -> Option { + let v = self.cids.get(self.index)?; + self.index += 1; + Some(v.clone()) + } +} + +#[no_mangle] +pub extern fn quiche_conn_source_ids(conn: &Connection) -> *mut ConnectionIdIter { + let vec = conn.source_ids().cloned().collect(); + Box::into_raw(Box::new(ConnectionIdIter { + cids: vec, + index: 0, + })) +} + +#[no_mangle] +pub extern fn quiche_connection_id_iter_next( + iter: &mut ConnectionIdIter, out: &mut *const u8, out_len: &mut size_t, +) -> bool { + if let Some(conn_id) = iter.next() { + let id = conn_id.as_ref(); + *out = id.as_ptr(); + *out_len = id.len(); + return true; + } + + false +} + +#[no_mangle] +pub extern fn quiche_connection_id_iter_free(iter: *mut ConnectionIdIter) { + drop(unsafe { Box::from_raw(iter) }); +} + #[no_mangle] pub extern fn quiche_conn_source_id( conn: &Connection, out: &mut *const u8, out_len: &mut size_t, @@ -1005,6 +1097,11 @@ pub extern fn quiche_conn_is_established(conn: &Connection) -> bool { conn.is_established() } +#[no_mangle] +pub extern fn quiche_conn_is_resumed(conn: &Connection) -> bool { + conn.is_resumed() +} + #[no_mangle] pub extern fn quiche_conn_is_in_early_data(conn: &Connection) -> bool { conn.is_in_early_data() @@ -1091,6 +1188,10 @@ pub struct Stats { lost_bytes: u64, stream_retrans_bytes: u64, paths_count: usize, + reset_stream_count_local: u64, + stopped_stream_count_local: u64, + reset_stream_count_remote: u64, + stopped_stream_count_remote: u64, } pub struct TransportParams { @@ -1122,6 +1223,10 @@ pub extern fn quiche_conn_stats(conn: &Connection, out: &mut Stats) { out.lost_bytes = stats.lost_bytes; out.stream_retrans_bytes = stats.stream_retrans_bytes; out.paths_count = stats.paths_count; + out.reset_stream_count_local = stats.reset_stream_count_local; + out.stopped_stream_count_local = stats.stopped_stream_count_local; + out.reset_stream_count_remote = stats.reset_stream_count_remote; + out.stopped_stream_count_remote = stats.stopped_stream_count_remote; } #[no_mangle] @@ -1302,6 +1407,16 @@ pub extern fn quiche_conn_dgram_purge_outgoing( }); } +#[no_mangle] +pub extern fn quiche_conn_is_dgram_send_queue_full(conn: &Connection) -> bool { + conn.is_dgram_send_queue_full() +} + +#[no_mangle] +pub extern fn quiche_conn_is_dgram_recv_queue_full(conn: &Connection) -> bool { + conn.is_dgram_recv_queue_full() +} + #[no_mangle] pub extern fn quiche_conn_send_ack_eliciting(conn: &mut Connection) -> ssize_t { match conn.send_ack_eliciting() { @@ -1343,6 +1458,307 @@ pub extern fn quiche_conn_send_quantum(conn: &Connection) -> size_t { conn.send_quantum() as size_t } +#[no_mangle] +pub extern fn quiche_conn_active_scids(conn: &Connection) -> size_t { + conn.active_scids() as size_t +} + +#[no_mangle] +pub extern fn quiche_conn_scids_left(conn: &Connection) -> size_t { + conn.scids_left() as size_t +} + +#[no_mangle] +pub extern fn quiche_conn_new_scid( + conn: &mut Connection, scid: *const u8, scid_len: size_t, + reset_token: *const u8, retire_if_needed: bool, scid_seq: *mut u64, +) -> c_int { + let scid = unsafe { slice::from_raw_parts(scid, scid_len) }; + let scid = ConnectionId::from_ref(scid); + + let reset_token = unsafe { slice::from_raw_parts(reset_token, 16) }; + let reset_token = match reset_token.try_into() { + Ok(rt) => rt, + Err(_) => unreachable!(), + }; + let reset_token = u128::from_be_bytes(reset_token); + + match conn.new_scid(&scid, reset_token, retire_if_needed) { + Ok(c) => { + unsafe { *scid_seq = c } + 0 + }, + Err(e) => e.to_c() as c_int, + } +} + +#[no_mangle] +pub extern fn quiche_conn_retire_dcid( + conn: &mut Connection, dcid_seq: u64, +) -> c_int { + match conn.retire_dcid(dcid_seq) { + Ok(_) => 0, + Err(e) => e.to_c() as c_int, + } +} + +#[no_mangle] +pub extern fn quiche_conn_available_dcids(conn: &Connection) -> size_t { + conn.available_dcids() as size_t +} + +#[no_mangle] +pub extern fn quiche_conn_retired_scids(conn: &Connection) -> size_t { + conn.retired_scids() as size_t +} + +#[no_mangle] +pub extern fn quiche_conn_retired_scid_next( + conn: &mut Connection, out: &mut *const u8, out_len: &mut size_t, +) -> bool { + match conn.retired_scid_next() { + None => false, + + Some(conn_id) => { + let id = conn_id.as_ref(); + *out = id.as_ptr(); + *out_len = id.len(); + true + }, + } +} + +#[no_mangle] +pub extern fn quiche_conn_send_quantum_on_path( + conn: &Connection, local: &sockaddr, local_len: socklen_t, peer: &sockaddr, + peer_len: socklen_t, +) -> size_t { + let local = std_addr_from_c(local, local_len); + let peer = std_addr_from_c(peer, peer_len); + + conn.send_quantum_on_path(local, peer) as size_t +} + +#[no_mangle] +pub extern fn quiche_conn_paths_iter( + conn: &Connection, from: &sockaddr, from_len: socklen_t, +) -> *mut SocketAddrIter { + let addr = std_addr_from_c(from, from_len); + + Box::into_raw(Box::new(conn.paths_iter(addr))) +} + +#[no_mangle] +pub extern fn quiche_socket_addr_iter_next( + iter: &mut SocketAddrIter, peer: &mut sockaddr_storage, + peer_len: *mut socklen_t, +) -> bool { + if let Some(v) = iter.next() { + unsafe { *peer_len = std_addr_to_c(&v, peer) } + return true; + } + + false +} + +#[no_mangle] +pub extern fn quiche_socket_addr_iter_free(iter: *mut SocketAddrIter) { + drop(unsafe { Box::from_raw(iter) }); +} + +#[no_mangle] +pub extern fn quiche_conn_is_path_validated( + conn: &Connection, from: &sockaddr, from_len: socklen_t, to: &sockaddr, + to_len: socklen_t, +) -> c_int { + let from = std_addr_from_c(from, from_len); + let to = std_addr_from_c(to, to_len); + match conn.is_path_validated(from, to) { + Ok(v) => v as c_int, + Err(e) => e.to_c() as c_int, + } +} + +#[no_mangle] +pub extern fn quiche_conn_probe_path( + conn: &mut Connection, local: &sockaddr, local_len: socklen_t, + peer: &sockaddr, peer_len: socklen_t, seq: *mut u64, +) -> c_int { + let local = std_addr_from_c(local, local_len); + let peer = std_addr_from_c(peer, peer_len); + match conn.probe_path(local, peer) { + Ok(v) => { + unsafe { *seq = v } + 0 + }, + Err(e) => e.to_c() as c_int, + } +} + +#[no_mangle] +pub extern fn quiche_conn_migrate_source( + conn: &mut Connection, local: &sockaddr, local_len: socklen_t, seq: *mut u64, +) -> c_int { + let local = std_addr_from_c(local, local_len); + match conn.migrate_source(local) { + Ok(v) => { + unsafe { *seq = v } + 0 + }, + Err(e) => e.to_c() as c_int, + } +} + +#[no_mangle] +pub extern fn quiche_conn_migrate( + conn: &mut Connection, local: &sockaddr, local_len: socklen_t, + peer: &sockaddr, peer_len: socklen_t, seq: *mut u64, +) -> c_int { + let local = std_addr_from_c(local, local_len); + let peer = std_addr_from_c(peer, peer_len); + match conn.migrate(local, peer) { + Ok(v) => { + unsafe { *seq = v } + 0 + }, + Err(e) => e.to_c() as c_int, + } +} + +#[no_mangle] +pub extern fn quiche_conn_path_event_next( + conn: &mut Connection, +) -> *const PathEvent { + match conn.path_event_next() { + Some(v) => Box::into_raw(Box::new(v)), + None => ptr::null(), + } +} + +#[no_mangle] +pub extern fn quiche_path_event_type(ev: &PathEvent) -> u32 { + match ev { + PathEvent::New { .. } => 0, + + PathEvent::Validated { .. } => 1, + + PathEvent::FailedValidation { .. } => 2, + + PathEvent::Closed { .. } => 3, + + PathEvent::ReusedSourceConnectionId { .. } => 4, + + PathEvent::PeerMigrated { .. } => 5, + } +} + +#[no_mangle] +pub extern fn quiche_path_event_new( + ev: &PathEvent, local_addr: &mut sockaddr_storage, + local_addr_len: &mut socklen_t, peer_addr: &mut sockaddr_storage, + peer_addr_len: &mut socklen_t, +) { + match ev { + PathEvent::New(local, peer) => { + *local_addr_len = std_addr_to_c(local, local_addr); + *peer_addr_len = std_addr_to_c(peer, peer_addr) + }, + + _ => unreachable!(), + } +} + +#[no_mangle] +pub extern fn quiche_path_event_validated( + ev: &PathEvent, local_addr: &mut sockaddr_storage, + local_addr_len: &mut socklen_t, peer_addr: &mut sockaddr_storage, + peer_addr_len: &mut socklen_t, +) { + match ev { + PathEvent::Validated(local, peer) => { + *local_addr_len = std_addr_to_c(local, local_addr); + *peer_addr_len = std_addr_to_c(peer, peer_addr) + }, + + _ => unreachable!(), + } +} + +#[no_mangle] +pub extern fn quiche_path_event_failed_validation( + ev: &PathEvent, local_addr: &mut sockaddr_storage, + local_addr_len: &mut socklen_t, peer_addr: &mut sockaddr_storage, + peer_addr_len: &mut socklen_t, +) { + match ev { + PathEvent::FailedValidation(local, peer) => { + *local_addr_len = std_addr_to_c(local, local_addr); + *peer_addr_len = std_addr_to_c(peer, peer_addr) + }, + + _ => unreachable!(), + } +} + +#[no_mangle] +pub extern fn quiche_path_event_closed( + ev: &PathEvent, local_addr: &mut sockaddr_storage, + local_addr_len: &mut socklen_t, peer_addr: &mut sockaddr_storage, + peer_addr_len: &mut socklen_t, +) { + match ev { + PathEvent::Closed(local, peer) => { + *local_addr_len = std_addr_to_c(local, local_addr); + *peer_addr_len = std_addr_to_c(peer, peer_addr) + }, + + _ => unreachable!(), + } +} + +#[no_mangle] +pub extern fn quiche_path_event_reused_source_connection_id( + ev: &PathEvent, cid_sequence_number: &mut u64, + old_local_addr: &mut sockaddr_storage, old_local_addr_len: &mut socklen_t, + old_peer_addr: &mut sockaddr_storage, old_peer_addr_len: &mut socklen_t, + local_addr: &mut sockaddr_storage, local_addr_len: &mut socklen_t, + peer_addr: &mut sockaddr_storage, peer_addr_len: &mut socklen_t, +) { + match ev { + PathEvent::ReusedSourceConnectionId(id, old, new) => { + *cid_sequence_number = *id; + *old_local_addr_len = std_addr_to_c(&old.0, old_local_addr); + *old_peer_addr_len = std_addr_to_c(&old.1, old_peer_addr); + + *local_addr_len = std_addr_to_c(&new.0, local_addr); + *peer_addr_len = std_addr_to_c(&new.1, peer_addr) + }, + + _ => unreachable!(), + } +} + +#[no_mangle] +pub extern fn quiche_path_event_peer_migrated( + ev: &PathEvent, local_addr: &mut sockaddr_storage, + local_addr_len: &mut socklen_t, peer_addr: &mut sockaddr_storage, + peer_addr_len: &mut socklen_t, +) { + match ev { + PathEvent::PeerMigrated(local, peer) => { + *local_addr_len = std_addr_to_c(local, local_addr); + *peer_addr_len = std_addr_to_c(peer, peer_addr); + }, + + _ => unreachable!(), + } +} + +#[no_mangle] +pub extern fn quiche_path_event_free(ev: *mut PathEvent) { + drop(unsafe { Box::from_raw(ev) }); +} + #[no_mangle] pub extern fn quiche_put_varint( buf: *mut u8, buf_len: size_t, val: u64, @@ -1379,6 +1795,19 @@ pub extern fn quiche_get_varint( b.off() as ssize_t } +fn optional_std_addr_from_c( + addr: *const sockaddr, addr_len: socklen_t, +) -> Option { + if addr.is_null() || addr_len == 0 { + return None; + } + + Some({ + let addr = unsafe { slice::from_raw_parts(addr, addr_len as usize) }; + std_addr_from_c(addr.first().unwrap(), addr_len) + }) +} + fn std_addr_from_c(addr: &sockaddr, addr_len: socklen_t) -> SocketAddr { match addr.sa_family as i32 { AF_INET => { diff --git a/quiche/src/frame.rs b/quiche/src/frame.rs index 3ecfcd3707..28f61116a1 100644 --- a/quiche/src/frame.rs +++ b/quiche/src/frame.rs @@ -177,6 +177,29 @@ pub enum Frame { DatagramHeader { length: usize, }, + + ACKMP { + space_identifier: u64, + ack_delay: u64, + ranges: ranges::RangeSet, + ecn_counts: Option, + }, + + PathAbandon { + dcid_seq_num: u64, + error_code: u64, + reason: Vec, + }, + + PathStandby { + dcid_seq_num: u64, + seq_num: u64, + }, + + PathAvailable { + dcid_seq_num: u64, + seq_num: u64, + }, } impl Frame { @@ -221,8 +244,15 @@ impl Frame { Frame::Crypto { data } }, - 0x07 => Frame::NewToken { - token: b.get_bytes_with_varint_length()?.to_vec(), + 0x07 => { + let len = b.get_varint()?; + if len == 0 { + return Err(Error::InvalidFrame); + } + + Frame::NewToken { + token: b.get_bytes(len as usize)?.to_vec(), + } }, 0x08..=0x0f => parse_stream_frame(frame_type, b)?, @@ -261,15 +291,25 @@ impl Frame { limit: b.get_varint()?, }, - 0x18 => Frame::NewConnectionId { - seq_num: b.get_varint()?, - retire_prior_to: b.get_varint()?, - conn_id: b.get_bytes_with_u8_length()?.to_vec(), - reset_token: b - .get_bytes(16)? - .buf() - .try_into() - .map_err(|_| Error::BufferTooShort)?, + 0x18 => { + let seq_num = b.get_varint()?; + let retire_prior_to = b.get_varint()?; + let conn_id_len = b.get_u8()?; + + if !(1..=packet::MAX_CID_LEN).contains(&conn_id_len) { + return Err(Error::InvalidFrame); + } + + Frame::NewConnectionId { + seq_num, + retire_prior_to, + conn_id: b.get_bytes(conn_id_len as usize)?.to_vec(), + reset_token: b + .get_bytes(16)? + .buf() + .try_into() + .map_err(|_| Error::BufferTooShort)?, + } }, 0x19 => Frame::RetireConnectionId { @@ -307,6 +347,24 @@ impl Frame { 0x30 | 0x31 => parse_datagram_frame(frame_type, b)?, + 0x15228c00..=0x15228c01 => parse_ack_mp_frame(frame_type, b)?, + + 0x15228c05 => Frame::PathAbandon { + dcid_seq_num: b.get_varint()?, + error_code: b.get_varint()?, + reason: b.get_bytes_with_varint_length()?.to_vec(), + }, + + 0x15228c07 => Frame::PathStandby { + dcid_seq_num: b.get_varint()?, + seq_num: b.get_varint()?, + }, + + 0x15228c08 => Frame::PathAvailable { + dcid_seq_num: b.get_varint()?, + seq_num: b.get_varint()?, + }, + _ => return Err(Error::InvalidFrame), }; @@ -315,7 +373,8 @@ impl Frame { (_, Frame::Padding { .. }) | (_, Frame::Ping { .. }) => true, // ACK, CRYPTO, HANDSHAKE_DONE, NEW_TOKEN, PATH_RESPONSE, and - // RETIRE_CONNECTION_ID can't be sent on 0-RTT packets. + // RETIRE_CONNECTION_ID can't be sent on 0-RTT packets. Multipath + // frames are only available in 1-RTT packets. (packet::Type::ZeroRTT, Frame::ACK { .. }) => false, (packet::Type::ZeroRTT, Frame::Crypto { .. }) => false, (packet::Type::ZeroRTT, Frame::HandshakeDone) => false, @@ -323,6 +382,10 @@ impl Frame { (packet::Type::ZeroRTT, Frame::PathResponse { .. }) => false, (packet::Type::ZeroRTT, Frame::RetireConnectionId { .. }) => false, (packet::Type::ZeroRTT, Frame::ConnectionClose { .. }) => false, + (packet::Type::ZeroRTT, Frame::ACKMP { .. }) => false, + (packet::Type::ZeroRTT, Frame::PathAbandon { .. }) => false, + (packet::Type::ZeroRTT, Frame::PathStandby { .. }) => false, + (packet::Type::ZeroRTT, Frame::PathAvailable { .. }) => false, // ACK, CRYPTO and CONNECTION_CLOSE can be sent on all other packet // types. @@ -339,6 +402,7 @@ impl Frame { }; if !allowed { + error!("Bad frame: {:?}", frame); return Err(Error::InvalidPacket); } @@ -373,34 +437,7 @@ impl Frame { } else { b.put_varint(0x03)?; } - - let mut it = ranges.iter().rev(); - - let first = it.next().unwrap(); - let ack_block = (first.end - 1) - first.start; - - b.put_varint(first.end - 1)?; - b.put_varint(*ack_delay)?; - b.put_varint(it.len() as u64)?; - b.put_varint(ack_block)?; - - let mut smallest_ack = first.start; - - for block in it { - let gap = smallest_ack - block.end - 1; - let ack_block = (block.end - 1) - block.start; - - b.put_varint(gap)?; - b.put_varint(ack_block)?; - - smallest_ack = block.start; - } - - if let Some(ecn) = ecn_counts { - b.put_varint(ecn.ect0_count)?; - b.put_varint(ecn.ect1_count)?; - b.put_varint(ecn.ecn_ce_count)?; - } + common_ack_to_bytes(b, ack_delay, ranges, ecn_counts)?; }, Frame::ResetStream { @@ -569,6 +606,54 @@ impl Frame { }, Frame::DatagramHeader { .. } => (), + + Frame::ACKMP { + space_identifier, + ack_delay, + ranges, + ecn_counts, + } => { + if ecn_counts.is_none() { + b.put_varint(0x15228c00)?; + } else { + b.put_varint(0x15228c01)?; + } + b.put_varint(*space_identifier)?; + common_ack_to_bytes(b, ack_delay, ranges, ecn_counts)?; + }, + + Frame::PathAbandon { + dcid_seq_num, + error_code, + reason, + } => { + b.put_varint(0x15228c05)?; + + b.put_varint(*dcid_seq_num)?; + b.put_varint(*error_code)?; + b.put_varint(reason.len() as u64)?; + b.put_bytes(reason.as_ref())?; + }, + + Frame::PathStandby { + dcid_seq_num, + seq_num, + } => { + b.put_varint(0x15228c07)?; + + b.put_varint(*dcid_seq_num)?; + b.put_varint(*seq_num)?; + }, + + Frame::PathAvailable { + dcid_seq_num, + seq_num, + } => { + b.put_varint(0x15228c08)?; + + b.put_varint(*dcid_seq_num)?; + b.put_varint(*seq_num)?; + }, } Ok(before - b.cap()) @@ -585,36 +670,8 @@ impl Frame { ranges, ecn_counts, } => { - let mut it = ranges.iter().rev(); - - let first = it.next().unwrap(); - let ack_block = (first.end - 1) - first.start; - - let mut len = 1 + // frame type - octets::varint_len(first.end - 1) + // largest_ack - octets::varint_len(*ack_delay) + // ack_delay - octets::varint_len(it.len() as u64) + // block_count - octets::varint_len(ack_block); // first_block - - let mut smallest_ack = first.start; - - for block in it { - let gap = smallest_ack - block.end - 1; - let ack_block = (block.end - 1) - block.start; - - len += octets::varint_len(gap) + // gap - octets::varint_len(ack_block); // ack_block - - smallest_ack = block.start; - } - - if let Some(ecn) = ecn_counts { - len += octets::varint_len(ecn.ect0_count) + - octets::varint_len(ecn.ect1_count) + - octets::varint_len(ecn.ecn_ce_count); - } - - len + 1 + // frame_type + common_ack_wire_len(ack_delay, ranges, ecn_counts) }, Frame::ResetStream { @@ -784,6 +841,47 @@ impl Frame { 2 + // length, always encode as 2-byte varint *length // data }, + + Frame::ACKMP { + space_identifier, + ack_delay, + ranges, + ecn_counts, + } => { + 4 + // frame_type + octets::varint_len(*space_identifier) + // space_identifier + common_ack_wire_len(ack_delay, ranges, ecn_counts) + }, + + Frame::PathAbandon { + dcid_seq_num, + error_code, + reason, + } => { + 4 + // frame type + octets::varint_len(*dcid_seq_num) + + octets::varint_len(*error_code) + + octets::varint_len(reason.len() as u64) + // reason_len + reason.len() + }, + + Frame::PathStandby { + dcid_seq_num, + seq_num, + } => { + 4 + // frame size + octets::varint_len(*dcid_seq_num) + + octets::varint_len(*seq_num) + }, + + Frame::PathAvailable { + dcid_seq_num, + seq_num, + } => { + 4 + // frame size + octets::varint_len(*dcid_seq_num) + + octets::varint_len(*seq_num) + }, } } @@ -794,7 +892,8 @@ impl Frame { Frame::Padding { .. } | Frame::ACK { .. } | Frame::ApplicationClose { .. } | - Frame::ConnectionClose { .. } + Frame::ConnectionClose { .. } | + Frame::ACKMP { .. } ) } @@ -996,6 +1095,62 @@ impl Frame { length: *length as u64, raw: None, }, + + Frame::ACKMP { + space_identifier, + ack_delay, + ranges, + ecn_counts, + } => { + let ack_ranges = AckedRanges::Double( + ranges.iter().map(|r| (r.start, r.end - 1)).collect(), + ); + + let (ect0, ect1, ce) = match ecn_counts { + Some(ecn) => ( + Some(ecn.ect0_count), + Some(ecn.ect1_count), + Some(ecn.ecn_ce_count), + ), + + None => (None, None, None), + }; + + QuicFrame::AckMp { + space_identifier: *space_identifier, + ack_delay: Some(*ack_delay as f32 / 1000.0), + acked_ranges: Some(ack_ranges), + ect1, + ect0, + ce, + } + }, + + Frame::PathAbandon { + dcid_seq_num, + error_code, + reason, + } => QuicFrame::PathAbandon { + dcid_seq_num: *dcid_seq_num, + error_code: *error_code, + reason: Some(String::from_utf8_lossy(reason).into_owned()), + }, + + Frame::PathStandby { + dcid_seq_num, + seq_num, + } => QuicFrame::PathStandby { + dcid_seq_num: *dcid_seq_num, + seq_num: *seq_num, + }, + + Frame::PathAvailable { + dcid_seq_num, + seq_num, + } => QuicFrame::PathAvailable { + dcid_seq_num: *dcid_seq_num, + seq_num: *seq_num, + }, } } } @@ -1048,8 +1203,8 @@ impl std::fmt::Debug for Frame { write!(f, "CRYPTO off={offset} len={length}")?; }, - Frame::NewToken { .. } => { - write!(f, "NEW_TOKEN (TODO)")?; + Frame::NewToken { token } => { + write!(f, "NEW_TOKEN len={}", token.len())?; }, Frame::Stream { stream_id, data } => { @@ -1163,15 +1318,60 @@ impl std::fmt::Debug for Frame { Frame::DatagramHeader { length } => { write!(f, "DATAGRAM len={length}")?; }, + + Frame::ACKMP { + space_identifier, + ack_delay, + ranges, + ecn_counts, + .. + } => { + write!( + f, + "ACK_MP space_id={space_identifier} delay={ack_delay} blocks={ranges:?} ecn_counts={ecn_counts:?}", + )?; + }, + + Frame::PathAbandon { + dcid_seq_num, + error_code, + reason, + .. + } => { + write!( + f, + "PATH_ABANDON dcid_seq_num={dcid_seq_num:x} err={error_code:x} reason={reason:x?}", + )?; + }, + + Frame::PathStandby { + dcid_seq_num, + seq_num, + } => { + write!( + f, + "PATH_STANDBY dcid_seq_num={dcid_seq_num:x} seq_num={seq_num:x}", + )? + }, + + Frame::PathAvailable { + dcid_seq_num, + seq_num, + } => { + write!( + f, + "PATH_AVAILABLE dcid_seq_num={dcid_seq_num:x} seq_num={seq_num:x}", + )? + }, } Ok(()) } } -fn parse_ack_frame(ty: u64, b: &mut octets::Octets) -> Result { - let first = ty as u8; - +fn parse_common_ack_frame( + b: &mut octets::Octets, has_ecn: bool, +) -> Result<(u64, ranges::RangeSet, Option)> { let largest_ack = b.get_varint()?; let ack_delay = b.get_varint()?; let block_count = b.get_varint()?; @@ -1206,7 +1406,7 @@ fn parse_ack_frame(ty: u64, b: &mut octets::Octets) -> Result { ranges.insert(smallest_ack..largest_ack + 1); } - let ecn_counts = if first & 0x01 != 0 { + let ecn_counts = if has_ecn { let ecn = EcnCounts { ect0_count: b.get_varint()?, ect1_count: b.get_varint()?, @@ -1218,6 +1418,14 @@ fn parse_ack_frame(ty: u64, b: &mut octets::Octets) -> Result { None }; + Ok((ack_delay, ranges, ecn_counts)) +} + +fn parse_ack_frame(ty: u64, b: &mut octets::Octets) -> Result { + let first = ty as u8; + let (ack_delay, ranges, ecn_counts) = + parse_common_ack_frame(b, first & 0x01 != 0)?; + Ok(Frame::ACK { ack_delay, ranges, @@ -1225,6 +1433,88 @@ fn parse_ack_frame(ty: u64, b: &mut octets::Octets) -> Result { }) } +fn parse_ack_mp_frame(ty: u64, b: &mut octets::Octets) -> Result { + let space_identifier = b.get_varint()?; + let (ack_delay, ranges, ecn_counts) = + parse_common_ack_frame(b, ty & 0x01 != 0)?; + + Ok(Frame::ACKMP { + space_identifier, + ack_delay, + ranges, + ecn_counts, + }) +} + +fn common_ack_to_bytes( + b: &mut octets::OctetsMut, ack_delay: &u64, ranges: &ranges::RangeSet, + ecn_counts: &Option, +) -> Result<()> { + let mut it = ranges.iter().rev(); + + let first = it.next().unwrap(); + let ack_block = (first.end - 1) - first.start; + + b.put_varint(first.end - 1)?; + b.put_varint(*ack_delay)?; + b.put_varint(it.len() as u64)?; + b.put_varint(ack_block)?; + + let mut smallest_ack = first.start; + + for block in it { + let gap = smallest_ack - block.end - 1; + let ack_block = (block.end - 1) - block.start; + + b.put_varint(gap)?; + b.put_varint(ack_block)?; + + smallest_ack = block.start; + } + + if let Some(ecn) = ecn_counts { + b.put_varint(ecn.ect0_count)?; + b.put_varint(ecn.ect1_count)?; + b.put_varint(ecn.ecn_ce_count)?; + } + + Ok(()) +} + +fn common_ack_wire_len( + ack_delay: &u64, ranges: &ranges::RangeSet, ecn_counts: &Option, +) -> usize { + let mut it = ranges.iter().rev(); + + let first = it.next().unwrap(); + let ack_block = (first.end - 1) - first.start; + + let mut len = octets::varint_len(first.end - 1) + // largest_ack + octets::varint_len(*ack_delay) + // ack_delay + octets::varint_len(it.len() as u64) + // block_count + octets::varint_len(ack_block); // first_block + + let mut smallest_ack = first.start; + + for block in it { + let gap = smallest_ack - block.end - 1; + let ack_block = (block.end - 1) - block.start; + + len += octets::varint_len(gap) + // gap + octets::varint_len(ack_block); // ack_block + + smallest_ack = block.start; + } + + if let Some(ecn) = ecn_counts { + len += octets::varint_len(ecn.ect0_count) + + octets::varint_len(ecn.ect1_count) + + octets::varint_len(ecn.ecn_ce_count); + } + + len +} + pub fn encode_crypto_header( offset: u64, length: u64, b: &mut octets::OctetsMut, ) -> Result<()> { @@ -2082,4 +2372,183 @@ mod tests { assert_eq!(frame_data, data); } + + #[test] + fn path_abandon() { + let mut d = [42; 128]; + + let frame = Frame::PathAbandon { + dcid_seq_num: 421_124, + error_code: 0xbeef, + reason: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + }; + + let wire_len = { + let mut b = octets::OctetsMut::with_slice(&mut d); + frame.to_bytes(&mut b).unwrap() + }; + + assert_eq!(wire_len, 25); + + let mut b = octets::Octets::with_slice(&mut d); + assert_eq!( + Frame::from_bytes(&mut b, packet::Type::Short), + Ok(frame.clone()) + ); + + let mut b = octets::Octets::with_slice(&mut d); + assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err()); + + let mut b = octets::Octets::with_slice(&mut d); + assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err()); + + let mut b = octets::Octets::with_slice(&mut d); + assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err()); + } + + #[test] + fn ack_mp() { + let mut d = [42; 128]; + + let mut ranges = ranges::RangeSet::default(); + ranges.insert(4..7); + ranges.insert(9..12); + ranges.insert(15..19); + ranges.insert(3000..5000); + + let frame = Frame::ACKMP { + space_identifier: 894_994, + ack_delay: 874_656_534, + ranges, + ecn_counts: None, + }; + + let wire_len = { + let mut b = octets::OctetsMut::with_slice(&mut d); + frame.to_bytes(&mut b).unwrap() + }; + + assert_eq!(wire_len, 24); + + let mut b = octets::Octets::with_slice(&d); + assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame)); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err()); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err()); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err()); + } + + #[test] + fn ack_mp_ecn() { + let mut d = [42; 128]; + + let mut ranges = ranges::RangeSet::default(); + ranges.insert(4..7); + ranges.insert(9..12); + ranges.insert(15..19); + ranges.insert(3000..5000); + + let ecn_counts = Some(EcnCounts { + ect0_count: 100, + ect1_count: 200, + ecn_ce_count: 300, + }); + + let frame = Frame::ACKMP { + space_identifier: 894_994, + ack_delay: 874_656_534, + ranges, + ecn_counts, + }; + + let wire_len = { + let mut b = octets::OctetsMut::with_slice(&mut d); + frame.to_bytes(&mut b).unwrap() + }; + + assert_eq!(wire_len, 30); + + let mut b = octets::Octets::with_slice(&d); + assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame)); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err()); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err()); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err()); + } + + #[test] + fn path_standby() { + let mut d = [42; 128]; + + let dcid_seq_num = 0xabcdef00; + let seq_num = 0x42; + + let frame = Frame::PathStandby { + dcid_seq_num, + seq_num, + }; + + let wire_len = { + let mut b = octets::OctetsMut::with_slice(&mut d); + frame.to_bytes(&mut b).unwrap() + }; + + assert_eq!(frame.wire_len(), wire_len); + assert_eq!(wire_len, 14); + + let mut b = octets::Octets::with_slice(&d); + assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame)); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err()); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err()); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err()); + } + + #[test] + fn path_available() { + let mut d = [42; 128]; + + let dcid_seq_num = 0xabcdef00; + let seq_num = 0x42; + + let frame = Frame::PathAvailable { + dcid_seq_num, + seq_num, + }; + + let wire_len = { + let mut b = octets::OctetsMut::with_slice(&mut d); + frame.to_bytes(&mut b).unwrap() + }; + + assert_eq!(frame.wire_len(), wire_len); + assert_eq!(wire_len, 14); + + let mut b = octets::Octets::with_slice(&d); + assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame)); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err()); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err()); + + let mut b = octets::Octets::with_slice(&d); + assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err()); + } } diff --git a/quiche/src/h3/ffi.rs b/quiche/src/h3/ffi.rs index a79ae29ce7..578e5c4908 100644 --- a/quiche/src/h3/ffi.rs +++ b/quiche/src/h3/ffi.rs @@ -143,11 +143,11 @@ pub extern fn quiche_h3_event_type(ev: &h3::Event) -> u32 { h3::Event::Finished { .. } => 2, - h3::Event::GoAway { .. } => 4, + h3::Event::GoAway { .. } => 3, - h3::Event::Reset { .. } => 5, + h3::Event::Reset { .. } => 4, - h3::Event::PriorityUpdate { .. } => 6, + h3::Event::PriorityUpdate { .. } => 5, } } diff --git a/quiche/src/h3/mod.rs b/quiche/src/h3/mod.rs index 47c5d62c23..42146f6e59 100644 --- a/quiche/src/h3/mod.rs +++ b/quiche/src/h3/mod.rs @@ -2716,7 +2716,7 @@ impl Connection { } /// Generates an HTTP/3 GREASE variable length integer. -fn grease_value() -> u64 { +pub fn grease_value() -> u64 { let n = super::rand::rand_u64_uniform(148_764_065_110_560_899); 31 * n + 33 } @@ -6277,6 +6277,10 @@ mod tests { #[cfg(feature = "ffi")] mod ffi; +#[cfg(feature = "internal")] +#[doc(hidden)] +pub mod frame; +#[cfg(not(feature = "internal"))] mod frame; #[doc(hidden)] pub mod qpack; diff --git a/quiche/src/lib.rs b/quiche/src/lib.rs index 0a30c30cf3..8a8ecb45c1 100644 --- a/quiche/src/lib.rs +++ b/quiche/src/lib.rs @@ -457,6 +457,9 @@ const MAX_SEND_UDP_PAYLOAD_SIZE: usize = 1200; // The default length of DATAGRAM queues. const DEFAULT_MAX_DGRAM_QUEUE_LEN: usize = 0; +// The default length of PATH_CHALLENGE receive queue. +const DEFAULT_MAX_PATH_CHALLENGE_RX_QUEUE_LEN: usize = 3; + // The DATAGRAM standard recommends either none or 65536 as maximum DATAGRAM // frames size. We enforce the recommendation for forward compatibility. const MAX_DGRAM_FRAME_SIZE: u64 = 65536; @@ -564,6 +567,12 @@ pub enum Error { /// Error in key update. KeyUpdate, + + /// The considered path is currently not usable. + UnavailablePath, + + /// Compliance error with the multipath extensions. + MultiPathViolation, } impl Error { @@ -577,6 +586,7 @@ impl Error { Error::StreamLimit => 0x4, Error::FinalSize => 0x6, Error::KeyUpdate => 0xe, + Error::MultiPathViolation => 0xba01, _ => 0xa, } } @@ -603,6 +613,8 @@ impl Error { Error::IdLimit => -17, Error::OutOfIdentifiers => -18, Error::KeyUpdate => -19, + Error::UnavailablePath => -20, + Error::MultiPathViolation => -21, } } } @@ -718,6 +730,8 @@ pub struct Config { dgram_recv_max_queue_len: usize, dgram_send_max_queue_len: usize, + path_challenge_recv_max_queue_len: usize, + max_send_udp_payload_size: usize, max_connection_window: u64, @@ -780,6 +794,9 @@ impl Config { dgram_recv_max_queue_len: DEFAULT_MAX_DGRAM_QUEUE_LEN, dgram_send_max_queue_len: DEFAULT_MAX_DGRAM_QUEUE_LEN, + path_challenge_recv_max_queue_len: + DEFAULT_MAX_PATH_CHALLENGE_RX_QUEUE_LEN, + max_send_udp_payload_size: MAX_SEND_UDP_PAYLOAD_SIZE, max_connection_window: MAX_CONNECTION_WINDOW, @@ -1123,6 +1140,14 @@ impl Config { self.local_transport_params.disable_active_migration = v; } + /// Sets the `enable_multipath` transport parameter, negotiating the usage + /// of the multipath extension over this connection. + /// + /// The default value is `false`. + pub fn set_multipath(&mut self, v: bool) { + self.local_transport_params.enable_multipath = v; + } + /// Sets the congestion control algorithm used by string. /// /// The default value is `cubic`. On error `Error::CongestionControl` @@ -1194,6 +1219,16 @@ impl Config { self.dgram_send_max_queue_len = send_queue_len; } + /// Configures the max number of queued received PATH_CHALLENGE frames. + /// + /// When an endpoint receives a PATH_CHALLENGE frame and the queue is full, + /// the frame is discarded. + /// + /// The default is 3. + pub fn set_path_challenge_recv_max_queue_len(&mut self, queue_len: usize) { + self.path_challenge_recv_max_queue_len = queue_len; + } + /// Sets the maximum size of the connection window. /// /// The default value is MAX_CONNECTION_WINDOW (24MBytes). @@ -1245,7 +1280,7 @@ pub struct Connection { trace_id: String, /// Packet number spaces. - pkt_num_spaces: [packet::PktNumSpace; packet::Epoch::count()], + pkt_num_spaces: packet::PktNumSpaceMap, /// Peer's transport parameters. peer_transport_params: TransportParams, @@ -1268,6 +1303,12 @@ pub struct Connection { /// The path manager. paths: path::PathMap, + /// PATH_CHALLENGE receive queue max length. + path_challenge_recv_max_queue_len: usize, + + /// Total number of received PATH_CHALLENGE frames. + path_challenge_rx_count: u64, + /// List of supported application protocols. application_protos: Vec>, @@ -1426,6 +1467,21 @@ pub struct Connection { /// A resusable buffer used by Recovery newly_acked: Vec, + + /// The number of streams reset by local. + reset_stream_local_count: u64, + + /// The number of streams stopped by local. + stopped_stream_local_count: u64, + + /// The number of streams reset by remote. + reset_stream_remote_count: u64, + + /// The number of streams stopped by remote. + stopped_stream_remote_count: u64, + + /// Structure used when coping with abandoned paths in multipath. + dcid_seq_to_abandon: VecDeque, } /// Creates a new server-side connection. @@ -1613,21 +1669,6 @@ macro_rules! push_frame_to_pkt { }}; } -/// Conditional qlog actions. -/// -/// Executes the provided body if the qlog feature is enabled and quiche -/// has been configured with a log writer. -macro_rules! qlog_with { - ($qlog:expr, $qlog_streamer_ref:ident, $body:block) => {{ - #[cfg(feature = "qlog")] - { - if let Some($qlog_streamer_ref) = &mut $qlog.streamer { - $body - } - } - }}; -} - /// Executes the provided body if the qlog feature is enabled, quiche has been /// configured with a log writer, the event's importance is within the /// configured level. @@ -1708,7 +1749,13 @@ impl Connection { let recovery_config = recovery::RecoveryConfig::from_config(config); - let mut path = path::Path::new(local, peer, &recovery_config, true); + let mut path = path::Path::new( + local, + peer, + &recovery_config, + config.path_challenge_recv_max_queue_len, + true, + ); // If we did stateless retry assume the peer's address is verified. path.verified_peer_address = odcid.is_some(); // Assume clients validate the server's address implicitly. @@ -1737,11 +1784,7 @@ impl Connection { trace_id: scid_as_hex.join(""), - pkt_num_spaces: [ - packet::PktNumSpace::new(), - packet::PktNumSpace::new(), - packet::PktNumSpace::new(), - ], + pkt_num_spaces: packet::PktNumSpaceMap::new(), peer_transport_params: TransportParams::default(), @@ -1754,6 +1797,9 @@ impl Connection { recovery_config, paths, + path_challenge_recv_max_queue_len: config + .path_challenge_recv_max_queue_len, + path_challenge_rx_count: 0, application_protos: config.application_protos.clone(), @@ -1859,8 +1905,20 @@ impl Connection { disable_dcid_reuse: config.disable_dcid_reuse, newly_acked: Vec::new(), + + reset_stream_local_count: 0, + stopped_stream_local_count: 0, + reset_stream_remote_count: 0, + stopped_stream_remote_count: 0, + + dcid_seq_to_abandon: VecDeque::new(), }; + // Don't support multipath with zero-length CIDs. + if conn.ids.zero_length_scid() || conn.ids.zero_length_dcid() { + conn.local_transport_params.enable_multipath = false; + } + if let Some(odcid) = odcid { conn.local_transport_params .original_destination_connection_id = Some(odcid.to_vec().into()); @@ -1900,10 +1958,14 @@ impl Connection { active_path_id, )?; - conn.pkt_num_spaces[packet::Epoch::Initial].crypto_open = - Some(aead_open); - conn.pkt_num_spaces[packet::Epoch::Initial].crypto_seal = - Some(aead_seal); + conn.pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Initial) + .crypto_open = Some(aead_open); + conn.pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Initial) + .crypto_seal = Some(aead_seal); conn.derived_initial_secrets = true; } @@ -1995,7 +2057,7 @@ impl Connection { None, time::Instant::now(), trace, - self.qlog.level.clone(), + self.qlog.level, writer, ); @@ -2011,6 +2073,13 @@ impl Connection { self.qlog.streamer = Some(streamer); } + /// Returns a mutable reference to the QlogStreamer, if it exists. + #[cfg(feature = "qlog")] + #[cfg_attr(docsrs, doc(cfg(feature = "qlog")))] + pub fn qlog_streamer(&mut self) -> Option<&mut qlog::streamer::QlogStreamer> { + self.qlog.streamer.as_mut() + } + /// Configures the given session for resumption. /// /// On the client, this can be used to offer the given serialized session, @@ -2138,7 +2207,7 @@ impl Connection { if self.is_stateless_reset(&buf[len - left..len]) { trace!("{} packet is a stateless reset", self.trace_id); - self.closed = true; + self.mark_closed(); } left @@ -2167,7 +2236,10 @@ impl Connection { fn process_undecrypted_0rtt_packets(&mut self) -> Result<()> { // Process previously undecryptable 0-RTT packets if the decryption key // is now available. - if self.pkt_num_spaces[packet::Epoch::Application] + if self + .pkt_num_spaces + .crypto + .get(packet::Epoch::Application) .crypto_0rtt_open .is_some() { @@ -2331,10 +2403,14 @@ impl Connection { self.got_peer_conn_id = false; self.handshake.clear()?; - self.pkt_num_spaces[packet::Epoch::Initial].crypto_open = - Some(aead_open); - self.pkt_num_spaces[packet::Epoch::Initial].crypto_seal = - Some(aead_seal); + self.pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Initial) + .crypto_open = Some(aead_open); + self.pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Initial) + .crypto_seal = Some(aead_seal); self.handshake .use_legacy_codepoint(self.version != PROTOCOL_VERSION_V1); @@ -2396,10 +2472,14 @@ impl Connection { self.got_peer_conn_id = false; self.handshake.clear()?; - self.pkt_num_spaces[packet::Epoch::Initial].crypto_open = - Some(aead_open); - self.pkt_num_spaces[packet::Epoch::Initial].crypto_seal = - Some(aead_seal); + self.pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Initial) + .crypto_open = Some(aead_open); + self.pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Initial) + .crypto_seal = Some(aead_seal); return Err(Error::Done); } @@ -2460,10 +2540,14 @@ impl Connection { self.is_server, )?; - self.pkt_num_spaces[packet::Epoch::Initial].crypto_open = - Some(aead_open); - self.pkt_num_spaces[packet::Epoch::Initial].crypto_seal = - Some(aead_seal); + self.pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Initial) + .crypto_open = Some(aead_open); + self.pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Initial) + .crypto_seal = Some(aead_seal); self.derived_initial_secrets = true; } @@ -2474,10 +2558,14 @@ impl Connection { // Select AEAD context used to open incoming packet. let aead = if hdr.ty == packet::Type::ZeroRTT { // Only use 0-RTT key if incoming packet is 0-RTT. - self.pkt_num_spaces[epoch].crypto_0rtt_open.as_ref() + self.pkt_num_spaces + .crypto + .get(epoch) + .crypto_0rtt_open + .as_ref() } else { // Otherwise use the packet number space's main key. - self.pkt_num_spaces[epoch].crypto_open.as_ref() + self.pkt_num_spaces.crypto.get(epoch).crypto_open.as_ref() }; // Finally, discard packet if no usable key is available. @@ -2518,8 +2606,31 @@ impl Connection { drop_pkt_on_err(e, self.recv_count, self.is_server, &self.trace_id) })?; + let space_id = if self.is_multipath_enabled() { + if let Some((scid_seq, _)) = self.ids.find_scid_seq(&hdr.dcid) { + scid_seq + } else { + trace!( + "{} ignored unknown Source CID {:?}", + self.trace_id, + hdr.dcid + ); + return Err(Error::Done); + } + } else { + packet::INITIAL_PACKET_NUMBER_SPACE_ID + }; + + // This might be a new space identifier yet unseen before. In such case, + // it should start with 0. + let largest_rx_pkt_num = self + .pkt_num_spaces + .spaces + .get(epoch, space_id) + .map(|pns| pns.largest_rx_pkt_num) + .unwrap_or(0); let pn = packet::decode_pkt_num( - self.pkt_num_spaces[epoch].largest_rx_pkt_num, + largest_rx_pkt_num, hdr.pkt_num, hdr.pkt_num_len, ); @@ -2546,7 +2657,10 @@ impl Connection { hdr.key_phase != self.key_phase { // Check if this packet arrived before key update. - if let Some(key_update) = self.pkt_num_spaces[epoch] + if let Some(key_update) = self + .pkt_num_spaces + .crypto + .get(epoch) .key_update .as_ref() .and_then(|key_update| { @@ -2558,12 +2672,16 @@ impl Connection { trace!("{} peer-initiated key update", self.trace_id); aead_next = Some(( - self.pkt_num_spaces[epoch] + self.pkt_num_spaces + .crypto + .get(epoch) .crypto_open .as_ref() .unwrap() .derive_next_packet_key()?, - self.pkt_num_spaces[epoch] + self.pkt_num_spaces + .crypto + .get(epoch) .crypto_seal .as_ref() .unwrap() @@ -2578,6 +2696,7 @@ impl Connection { let mut payload = packet::decrypt_pkt( &mut b, + space_id as u32, pn, pn_len, payload_len, @@ -2587,7 +2706,11 @@ impl Connection { drop_pkt_on_err(e, self.recv_count, self.is_server, &self.trace_id) })?; - if self.pkt_num_spaces[epoch].recv_pkt_num.contains(pn) { + let pkt_num_space = self + .pkt_num_spaces + .spaces + .get_mut_or_create(epoch, space_id); + if pkt_num_space.recv_pkt_num.contains(pn) { trace!("{} ignored duplicate packet {}", self.trace_id, pn); return Err(Error::Done); } @@ -2610,7 +2733,10 @@ impl Connection { // The key update is verified once a packet is successfully decrypted // using the new keys. if let Some((open_next, seal_next)) = aead_next { - if !self.pkt_num_spaces[epoch] + if !self + .pkt_num_spaces + .crypto + .get(epoch) .key_update .as_ref() .map_or(true, |prev| prev.update_acked) @@ -2621,21 +2747,30 @@ impl Connection { trace!("{} key update verified", self.trace_id); - let _ = self.pkt_num_spaces[epoch].crypto_seal.replace(seal_next); + let _ = self + .pkt_num_spaces + .crypto + .get_mut(epoch) + .crypto_seal + .replace(seal_next); - let open_prev = self.pkt_num_spaces[epoch] + let open_prev = self + .pkt_num_spaces + .crypto + .get_mut(epoch) .crypto_open .replace(open_next) .unwrap(); let recv_path = self.paths.get_mut(recv_pid)?; - self.pkt_num_spaces[epoch].key_update = Some(packet::KeyUpdate { - crypto_open: open_prev, - pn_on_update: pn, - update_acked: false, - timer: now + (recv_path.recovery.pto() * 3), - }); + self.pkt_num_spaces.crypto.get_mut(epoch).key_update = + Some(packet::KeyUpdate { + crypto_open: open_prev, + pn_on_update: pn, + update_acked: false, + timer: now + (recv_path.recovery.pto() * 3), + }); self.key_phase = !self.key_phase; @@ -2808,14 +2943,21 @@ impl Connection { // largest acknowledged in the sent ACK frame that, in // turn, got acked. if let Some(largest_acked) = ranges.last() { - self.pkt_num_spaces[epoch] - .recv_pkt_need_ack - .remove_until(largest_acked); + self.pkt_num_spaces + .spaces + .get_mut(epoch, 0) + .map(|pns| { + pns.recv_pkt_need_ack + .remove_until(largest_acked) + }) + .ok(); } }, frame::Frame::CryptoHeader { offset, length } => { - self.pkt_num_spaces[epoch] + self.pkt_num_spaces + .crypto + .get_mut(epoch) .crypto_stream .send .ack_and_drop(offset, length); @@ -2886,19 +3028,63 @@ impl Connection { } }, + frame::Frame::ACKMP { + space_identifier, + ranges, + .. + } => { + // Stop acknowledging packets less than or equal to the + // largest acknowledged in the sent ACK_MP frame that, + // in turn, got acked. + if let Some(largest_acked) = ranges.last() { + self.pkt_num_spaces + .spaces + .get_mut( + packet::Epoch::Application, + space_identifier, + ) + .map(|pns| { + pns.recv_pkt_need_ack + .remove_until(largest_acked) + }) + .ok(); + } + }, + + frame::Frame::PathAbandon { dcid_seq_num, .. } => { + self.dcid_seq_to_abandon.push_back(dcid_seq_num); + }, + _ => (), } } } + for dcid_seq in self.dcid_seq_to_abandon.drain(..) { + // The path might be already abandoned. + if let Ok(e) = self.ids.get_dcid(dcid_seq) { + if let Some(pid) = e.path_id { + let (lost_packets, lost_bytes) = close_path( + &mut self.ids, + &mut self.pkt_num_spaces, + &mut self.paths, + pid, + now, + &self.trace_id, + )?; + self.lost_count += lost_packets; + self.lost_bytes += lost_bytes as u64; + } + } + } + // Now that we processed all the frames, if there is a path that has no // Destination CID, try to allocate one. - let no_dcid = self + for (pid, p) in self .paths .iter_mut() - .filter(|(_, p)| p.active_dcid_seq.is_none()); - - for (pid, p) in no_dcid { + .filter(|(_, p)| p.active_dcid_seq.is_none()) + { if self.ids.zero_length_dcid() { p.active_dcid_seq = Some(0); continue; @@ -2909,39 +3095,41 @@ impl Connection { None => break, }; - self.ids.link_dcid_to_path_id(dcid_seq, pid)?; - - p.active_dcid_seq = Some(dcid_seq); + update_dcid(&mut self.ids, pid, p, Some(dcid_seq))?; } + let multipath_enabled = self.paths.multipath(); + let pkt_num_space = + self.pkt_num_spaces.spaces.get_mut(epoch, space_id)?; + // We only record the time of arrival of the largest packet number // that still needs to be acked, to be used for ACK delay calculation. - if self.pkt_num_spaces[epoch].recv_pkt_need_ack.last() < Some(pn) { - self.pkt_num_spaces[epoch].largest_rx_pkt_time = now; + if pkt_num_space.recv_pkt_need_ack.last() < Some(pn) { + pkt_num_space.largest_rx_pkt_time = now; } - self.pkt_num_spaces[epoch].recv_pkt_num.insert(pn); + pkt_num_space.recv_pkt_num.insert(pn); - self.pkt_num_spaces[epoch].recv_pkt_need_ack.push_item(pn); + pkt_num_space.recv_pkt_need_ack.push_item(pn); - self.pkt_num_spaces[epoch].ack_elicited = - cmp::max(self.pkt_num_spaces[epoch].ack_elicited, ack_elicited); + pkt_num_space.ack_elicited = + cmp::max(pkt_num_space.ack_elicited, ack_elicited); - self.pkt_num_spaces[epoch].largest_rx_pkt_num = - cmp::max(self.pkt_num_spaces[epoch].largest_rx_pkt_num, pn); + pkt_num_space.largest_rx_pkt_num = + cmp::max(pkt_num_space.largest_rx_pkt_num, pn); if !probing { - self.pkt_num_spaces[epoch].largest_rx_non_probing_pkt_num = cmp::max( - self.pkt_num_spaces[epoch].largest_rx_non_probing_pkt_num, - pn, - ); + pkt_num_space.largest_rx_non_probing_pkt_num = + cmp::max(pkt_num_space.largest_rx_non_probing_pkt_num, pn); - // Did the peer migrated to another path? + // Did the peer migrate to another path? This only applies when + // multipath extensions have not been negotiated. let active_path_id = self.paths.get_active_path_id()?; if self.is_server && + !multipath_enabled && recv_pid != active_path_id && - self.pkt_num_spaces[epoch].largest_rx_non_probing_pkt_num == pn + pkt_num_space.largest_rx_non_probing_pkt_num == pn { self.on_peer_migrated(recv_pid, self.disable_dcid_reuse, now)?; } @@ -3279,14 +3467,20 @@ impl Connection { }; let epoch = pkt_type.to_epoch()?; - let pkt_space = &mut self.pkt_num_spaces[epoch]; + let multiple_application_data_pkt_num_spaces = + self.use_path_pkt_num_space(epoch); // Process lost frames. There might be several paths having lost frames. for (_, p) in self.paths.iter_mut() { for lost in p.recovery.lost[epoch].drain(..) { match lost { frame::Frame::CryptoHeader { offset, length } => { - pkt_space.crypto_stream.send.retransmit(offset, length); + self.pkt_num_spaces + .crypto + .get_mut(epoch) + .crypto_stream + .send + .retransmit(offset, length); self.stream_retrans_bytes += length as u64; p.stream_retrans_bytes += length as u64; @@ -3334,7 +3528,13 @@ impl Connection { }, frame::Frame::ACK { .. } => { - pkt_space.ack_elicited = true; + self.pkt_num_spaces + .spaces + .get_mut(epoch, 0) + .map(|pns| { + pns.ack_elicited = true; + }) + .ok(); }, frame::Frame::ResetStream { @@ -3371,26 +3571,51 @@ impl Connection { self.ids.mark_retire_dcid_seq(seq_num, true); }, + frame::Frame::ACKMP { + space_identifier, .. + } => { + self.pkt_num_spaces + .spaces + .get_mut(epoch, space_identifier) + .map(|pns| { + pns.ack_elicited = true; + }) + .ok(); + }, + _ => (), } } } - let is_app_limited = self.delivery_rate_check_if_app_limited(); + let consider_standby_paths = self.paths.consider_standby_paths(); + let is_app_limited = self.delivery_rate_check_if_app_limited(send_pid); let n_paths = self.paths.len(); - let path = self.paths.get_mut(send_pid)?; let flow_control = &mut self.flow_control; - let pkt_space = &mut self.pkt_num_spaces[epoch]; + let crypto_space = self.pkt_num_spaces.crypto.get_mut(epoch); let mut left = b.cap(); + let path = self.paths.get_mut(send_pid)?; + + let dcid_seq = path.active_dcid_seq.ok_or(Error::OutOfIdentifiers)?; + + let space_id = if multiple_application_data_pkt_num_spaces { + dcid_seq + } else { + packet::INITIAL_PACKET_NUMBER_SPACE_ID + }; + let pkt_space = self + .pkt_num_spaces + .spaces + .get_mut_or_create(epoch, space_id); + let pn = pkt_space.next_pkt_num; let pn_len = packet::pkt_num_len(pn)?; // The AEAD overhead at the current encryption level. - let crypto_overhead = pkt_space.crypto_overhead().ok_or(Error::Done)?; - - let dcid_seq = path.active_dcid_seq.ok_or(Error::OutOfIdentifiers)?; + let crypto_overhead = + crypto_space.crypto_overhead().ok_or(Error::Done)?; let dcid = ConnectionId::from_ref(self.ids.get_dcid(dcid_seq)?.cid.as_ref()); @@ -3515,7 +3740,8 @@ impl Connection { // generate an ACK (if there's anything to ACK) since we're going to // send a packet with PING anyways, even if we haven't received anything // ACK eliciting. - if pkt_space.recv_pkt_need_ack.len() > 0 && + if !multiple_application_data_pkt_num_spaces && + pkt_space.recv_pkt_need_ack.len() > 0 && (pkt_space.ack_elicited || ack_elicit_required) && (!is_closing || (pkt_type == Type::Handshake && @@ -3550,6 +3776,110 @@ impl Connection { } } + // Create ACK_MP frames if needed. + if multiple_application_data_pkt_num_spaces && + !is_closing && + path.active() + { + // We first check if we should bundle the ACK_MP belonging to our + // path. We only bundle additional ACK_MP from other paths if we + // need to send one. This avoids sending ACK_MP frames endlessly. + let mut wrote_ack_mp = false; + if let Some(active_scid_seq) = path.active_scid_seq { + let pns = + self.pkt_num_spaces.spaces.get_mut(epoch, active_scid_seq)?; + if pns.recv_pkt_need_ack.len() > 0 && + (pns.ack_elicited || ack_elicit_required) + { + let ack_delay = pns.largest_rx_pkt_time.elapsed(); + + let ack_delay = ack_delay.as_micros() as u64 / + 2_u64.pow( + self.local_transport_params.ack_delay_exponent as u32, + ); + + let frame = frame::Frame::ACKMP { + space_identifier: active_scid_seq, + ack_delay, + ranges: pns.recv_pkt_need_ack.clone(), + ecn_counts: None, /* sending ECN is not supported at + * this time */ + }; + + // When a PING frame needs to be sent, avoid sending the ACK_MP if + // there is not enough cwnd available for both (note that PING + // frames are always 1 byte, so we just need to check that the + // ACK_MP's length is lower than cwnd). + if pns.ack_elicited || (left_before_packing_ack_frame - left) + frame.wire_len() < cwnd_available { + if push_frame_to_pkt!(b, frames, frame, left) { + pns.ack_elicited = false; + wrote_ack_mp = true; + } + } + } + if wrote_ack_mp { + for space_id in self + .pkt_num_spaces + .spaces + .application_data_space_ids() + .collect::>() + { + // Don't process twice the path's packet number space. + if space_id == active_scid_seq { + continue; + } + // If the SCID is no more present, do not raise an error. + let pns_path_id = self + .ids + .get_scid(space_id) + .ok() + .and_then(|e| e.path_id); + let pns = self + .pkt_num_spaces + .spaces + .get_mut(epoch, space_id)?; + if pns.recv_pkt_need_ack.len() > 0 && + (pns.ack_elicited || ack_elicit_required) + { + let ack_delay = pns.largest_rx_pkt_time.elapsed(); + + let ack_delay = ack_delay.as_micros() as u64 / + 2_u64.pow( + self.local_transport_params.ack_delay_exponent + as u32, + ); + + let frame = frame::Frame::ACKMP { + space_identifier: space_id, + ack_delay, + ranges: pns.recv_pkt_need_ack.clone(), + ecn_counts: None, /* sending ECN is not + * supported at + * this time */ + }; + + if !ack_elicit_required || (left_before_packing_ack_frame - left) + frame.wire_len() < cwnd_available { + if push_frame_to_pkt!(b, frames, frame, left) { + // Continue advertising until we send the ACK_MP + // on + // its own path, unless the path is not active. + if let Some(path_id) = pns_path_id { + if !self.paths.get(path_id)?.active() { + pns.ack_elicited = false; + } + } else { + pns.ack_elicited = false; + } + } + } + } + } + } + } + } + + let path = self.paths.get_mut(send_pid)?; + // Limit output packet size by congestion window size. left = cmp::min( left, @@ -3591,7 +3921,7 @@ impl Connection { } } - if let Some(key_update) = pkt_space.key_update.as_mut() { + if let Some(key_update) = crypto_space.key_update.as_mut() { key_update.update_acked = true; } } @@ -3814,8 +4144,60 @@ impl Connection { break; } } + + // Create PATH_ABANDON frames as needed. + while let Some(pid) = self.paths.path_abandon() { + let abandoned_path = self.paths.get(pid)?; + let dcid_seq_num = + abandoned_path.active_dcid_seq.ok_or(Error::InvalidState)?; + let (error_code, reason) = + abandoned_path.closing_error_code_and_reason()?; + let frame = frame::Frame::PathAbandon { + dcid_seq_num, + error_code, + reason, + }; + if push_frame_to_pkt!(b, frames, frame, left) { + self.paths.on_path_abandon_sent(pid, now)?; + + ack_eliciting = true; + in_flight = true; + } else { + break; + } + } + + // Create PATH_AVAILABLE/PATH_STANDBY frames as needed. + while let Some((path_id, seq_num, available)) = + self.paths.path_status() + { + let status_path = self.paths.get(path_id)?; + let dcid_seq_num = + status_path.active_dcid_seq.ok_or(Error::InvalidState)?; + let frame = if available { + frame::Frame::PathAvailable { + dcid_seq_num, + seq_num, + } + } else { + frame::Frame::PathStandby { + dcid_seq_num, + seq_num, + } + }; + if push_frame_to_pkt!(b, frames, frame, left) { + self.paths.on_path_status_sent(); + + ack_eliciting = true; + in_flight = true; + } else { + break; + } + } } + let path = self.paths.get_mut(send_pid)?; + // Create CONNECTION_CLOSE frame. Try to send this only on the active // path, unless it is the last one available. if path.active() || n_paths == 1 { @@ -3856,12 +4238,13 @@ impl Connection { } // Create CRYPTO frame. - if pkt_space.crypto_stream.is_flushable() && + let crypto_space = self.pkt_num_spaces.crypto.get_mut(epoch); + if crypto_space.crypto_stream.is_flushable() && left > frame::MAX_CRYPTO_OVERHEAD && !is_closing && path.active() { - let crypto_off = pkt_space.crypto_stream.send.off_front(); + let crypto_off = crypto_space.crypto_stream.send.off_front(); // Encode the frame. // @@ -3886,7 +4269,7 @@ impl Connection { b.split_at(hdr_off + hdr_len)?; // Write stream data into the packet buffer. - let (len, _) = pkt_space + let (len, _) = crypto_space .crypto_stream .send .emit(&mut crypto_payload.as_mut()[..max_len])?; @@ -4020,7 +4403,8 @@ impl Connection { left > frame::MAX_STREAM_OVERHEAD && !is_closing && path.active() && - !dgram_emitted + !dgram_emitted && + (consider_standby_paths || !path.is_standby()) { while let Some(priority_key) = self.streams.peek_flushable() { let stream_id = priority_key.id; @@ -4255,13 +4639,14 @@ impl Connection { } }); - let aead = match pkt_space.crypto_seal { + let aead = match crypto_space.crypto_seal { Some(ref v) => v, None => return Err(Error::InvalidState), }; let written = packet::encrypt_pkt( &mut b, + space_id as u32, pn, pn_len, payload_len, @@ -4270,8 +4655,10 @@ impl Connection { aead, )?; + let pkt_num = recovery::SpacedPktNum::new(space_id as u32, pn); + let sent_pkt = recovery::Sent { - pkt_num: pn, + pkt_num, frames, time_sent: now, time_acked: None, @@ -4292,10 +4679,14 @@ impl Connection { path.recovery.delivery_rate_update_app_limited(true); } + let pkt_space = self.pkt_num_spaces.spaces.get_mut(epoch, space_id)?; pkt_space.next_pkt_num += 1; let handshake_status = recovery::HandshakeStatus { - has_handshake_keys: self.pkt_num_spaces[packet::Epoch::Handshake] + has_handshake_keys: self + .pkt_num_spaces + .crypto + .get(packet::Epoch::Handshake) .has_keys(), peer_verified_address: self.peer_verified_initial_address, completed: self.handshake_completed, @@ -4797,6 +5188,9 @@ impl Connection { // Once shutdown, the stream is guaranteed to be non-readable. self.streams.remove_readable(&priority_key); + + self.stopped_stream_local_count = + self.stopped_stream_local_count.saturating_add(1); }, Shutdown::Write => { @@ -4816,6 +5210,9 @@ impl Connection { // Once shutdown, the stream is guaranteed to be non-writable. self.streams.remove_writable(&priority_key); + + self.reset_stream_local_count = + self.reset_stream_local_count.saturating_add(1); }, } @@ -5446,7 +5843,9 @@ impl Connection { max_len = max_len.saturating_sub(packet::MAX_PKT_NUM_LEN); // ...subtract the crypto overhead... max_len = max_len.saturating_sub( - self.pkt_num_spaces[packet::Epoch::Application] + self.pkt_num_spaces + .crypto + .get(packet::Epoch::Application) .crypto_overhead()?, ); // ...clamp to what peer can support... @@ -5486,18 +5885,15 @@ impl Connection { // detection timers. If they are both unset (i.e. `None`) then the // result is `None`, but if at least one of them is set then a // `Some(...)` value is returned. - let path_timer = self - .paths - .iter() - .filter_map(|(_, p)| p.recovery.loss_detection_timer()) - .min(); - - let key_update_timer = self.pkt_num_spaces - [packet::Epoch::Application] + let path_timer = + self.paths.iter().filter_map(|(_, p)| p.path_timer()).min(); + let key_update_timer = self + .pkt_num_spaces + .crypto + .get(packet::Epoch::Application) .key_update .as_ref() .map(|key_update| key_update.timer); - let timers = [self.idle_timer, path_timer, key_update_timer]; timers.iter().filter_map(|&x| x).min() @@ -5532,11 +5928,7 @@ impl Connection { if draining_timer <= now { trace!("{} draining timeout expired", self.trace_id); - qlog_with!(self.qlog, q, { - q.finish_log().ok(); - }); - - self.closed = true; + self.mark_closed(); } // Draining timer takes precedence over all other timers. If it is @@ -5549,24 +5941,26 @@ impl Connection { if timer <= now { trace!("{} idle timeout expired", self.trace_id); - qlog_with!(self.qlog, q, { - q.finish_log().ok(); - }); - - self.closed = true; + self.mark_closed(); self.timed_out = true; return; } } - if let Some(timer) = self.pkt_num_spaces[packet::Epoch::Application] + if let Some(timer) = self + .pkt_num_spaces + .crypto + .get(packet::Epoch::Application) .key_update .as_ref() .map(|key_update| key_update.timer) { if timer <= now { // Discard previous key once key update timer expired. - let _ = self.pkt_num_spaces[packet::Epoch::Application] + let _ = self + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Application) .key_update .take(); } @@ -5575,6 +5969,18 @@ impl Connection { let handshake_status = self.handshake_status(); for (_, p) in self.paths.iter_mut() { + if let Some(timer) = p.closing_timer() { + if timer <= now { + trace!("{} path closing timeout expired", self.trace_id); + if let Some(dcid_seq) = p.active_dcid_seq { + self.ids.retire_dcid(dcid_seq).ok(); + self.pkt_num_spaces + .spaces + .update_lowest_active_tx_id(self.ids.min_dcid_seq()); + } + p.on_closing_timeout(); + } + } if let Some(timer) = p.recovery.loss_detection_timer() { if timer <= now { trace!("{} loss detection timeout expired", self.trace_id); @@ -5600,6 +6006,7 @@ impl Connection { // Notify timeout events to the application. self.paths.notify_failed_validations(); + self.paths.notify_closed_paths(); // If the active path failed, try to find a new candidate. if self.paths.get_active_path_id().is_err() { @@ -5607,11 +6014,13 @@ impl Connection { Some(pid) => if self.set_active_path(pid, now).is_err() { // The connection cannot continue. - self.closed = true; + self.mark_closed(); }, // The connection cannot continue. - None => self.closed = true, + None => { + self.mark_closed(); + }, } } } @@ -5750,17 +6159,100 @@ impl Connection { Ok(dcid_seq) } + /// Request the usage of the provided 4-tuple to send non-probing packets. + /// + /// This API is only available when the multipath extensions were negotiated + /// over this connection. If it was not, returns an [`InvalidState`]. When + /// disabled, the caller should instead call [`migrate()`]. + /// + /// If the path specified by the 4-tuple does not exist, returns an + /// [`Done`]. + /// + /// [`InvalidState`]: enum.Error.html#InvalidState + /// [`migrate()`]: struct.Connection.html#method.migrate + pub fn set_active( + &mut self, local: SocketAddr, peer: SocketAddr, active: bool, + ) -> Result<()> { + let pid = self + .paths + .path_id_from_addrs(&(local, peer)) + .ok_or(Error::Done)?; + let request = if active { + path::PathRequest::Active + } else { + path::PathRequest::Unused + }; + self.paths.request(pid, request)?; + + // After any path state change, check for the transmission rate. + self.update_tx_cap(); + + Ok(()) + } + + /// Abandon the provided 4-tuple. + /// + /// This API is only available when the multipath extensions were negotiated + /// over this connection. If it was not, returns an [`InvalidState`]. + /// + /// If the path specified by the 4-tuple does not exist, returns an + /// [`Done`]. + /// + /// [`InvalidState`]: enum.Error.html#InvalidState + /// [`Done`]: enum.Error.html#Done + pub fn abandon_path( + &mut self, local: SocketAddr, peer: SocketAddr, err_code: u64, + reason: Vec, + ) -> Result<()> { + let pid = self + .paths + .path_id_from_addrs(&(local, peer)) + .ok_or(Error::Done)?; + self.paths + .request(pid, path::PathRequest::Abandon(err_code, reason))?; + + // After any path state change, check for the transmission rate. + self.update_tx_cap(); + + Ok(()) + } + + /// Specifies the status of the path, and advertises it to the peer + /// if requested. + /// + /// This status applies on the whole connection (including, e.g., + /// DATAGRAMs). + /// + /// When `advertise` is set, this call also advertises the path status to + /// the peer, asking it to take the provided status into account. + /// + /// Note that specifying a 4-tuple that does not map to existing paths has + /// no effect. + pub fn set_path_status( + &mut self, local: SocketAddr, peer: SocketAddr, status: PathStatus, + advertise: bool, + ) -> Result<()> { + if let Some(path_id) = self.paths.path_id_from_addrs(&(local, peer)) { + self.paths.set_path_status(path_id, status)?; + if advertise { + self.paths.advertise_path_status(path_id)?; + } + } + + Ok(()) + } + /// Provides additional source Connection IDs that the peer can use to reach /// this host. /// /// This triggers sending NEW_CONNECTION_ID frames if the provided Source /// Connection ID is not already present. In the case the caller tries to /// reuse a Connection ID with a different reset token, this raises an - /// `InvalidState`. + /// [`InvalidState`]. /// /// At any time, the peer cannot have more Destination Connection IDs than /// the maximum number of active Connection IDs it negotiated. In such case - /// (i.e., when [`source_cids_left()`] returns 0), if the host agrees to + /// (i.e., when [`scids_left()`] returns 0), if the host agrees to /// request the removal of previous connection IDs, it sets the /// `retire_if_needed` parameter. Otherwise, an [`IdLimit`] is returned. /// @@ -5779,10 +6271,10 @@ impl Connection { /// /// Returns the sequence number associated to the provided Connection ID. /// - /// [`source_cids_left()`]: struct.Connection.html#method.source_cids_left + /// [`scids_left()`]: struct.Connection.html#method.scids_left /// [`IdLimit`]: enum.Error.html#IdLimit /// [`InvalidState`]: enum.Error.html#InvalidState - pub fn new_source_cid( + pub fn new_scid( &mut self, scid: &ConnectionId, reset_token: u128, retire_if_needed: bool, ) -> Result { self.ids.new_scid( @@ -5796,7 +6288,7 @@ impl Connection { /// Returns the number of source Connection IDs that are active. This is /// only meaningful if the host uses non-zero length Source Connection IDs. - pub fn active_source_cids(&self) -> usize { + pub fn active_scids(&self) -> usize { self.ids.active_source_cids() } @@ -5812,13 +6304,13 @@ impl Connection { /// /// [`peer_active_conn_id_limit`]: struct.Stats.html#structfield.peer_active_conn_id_limit #[inline] - pub fn source_cids_left(&self) -> usize { + pub fn scids_left(&self) -> usize { let max_active_source_cids = cmp::min( self.peer_transport_params.active_conn_id_limit, self.local_transport_params.active_conn_id_limit, ) as usize; - max_active_source_cids - self.active_source_cids() + max_active_source_cids - self.active_scids() } /// Requests the retirement of the destination Connection ID used by the @@ -5840,7 +6332,7 @@ impl Connection { /// /// [`InvalidState`]: enum.Error.html#InvalidState /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers - pub fn retire_destination_cid(&mut self, dcid_seq: u64) -> Result<()> { + pub fn retire_dcid(&mut self, dcid_seq: u64) -> Result<()> { if self.ids.zero_length_dcid() { return Err(Error::InvalidState); } @@ -5866,14 +6358,13 @@ impl Connection { if let Some(pid) = self.ids.retire_dcid(dcid_seq)? { // The retired Destination CID was associated to a given path. Let's // find an available DCID to associate to that path. - let path = self.paths.get_mut(pid)?; let dcid_seq = self.ids.lowest_available_dcid_seq(); + let path = self.paths.get_mut(pid)?; + update_dcid(&mut self.ids, pid, path, dcid_seq)?; - if let Some(dcid_seq) = dcid_seq { - self.ids.link_dcid_to_path_id(dcid_seq, pid)?; - } - - path.active_dcid_seq = dcid_seq; + self.pkt_num_spaces + .spaces + .update_lowest_active_tx_id(self.ids.min_dcid_seq()); } Ok(()) @@ -5982,6 +6473,13 @@ impl Connection { } } + /// Returns whether the multipath extensions have been enabled on this + /// connection. + #[inline] + pub fn is_multipath_enabled(&self) -> bool { + self.paths.multipath() + } + /// Closes the connection with the given error and reason. /// /// The `app` parameter specifies whether an application close should be @@ -6036,7 +6534,7 @@ impl Connection { // When no packet was successfully processed close connection immediately. if self.recv_count == 0 { - self.closed = true; + self.mark_closed(); } Ok(()) @@ -6257,6 +6755,11 @@ impl Connection { lost_bytes: self.lost_bytes, stream_retrans_bytes: self.stream_retrans_bytes, paths_count: self.paths.len(), + reset_stream_count_local: self.reset_stream_local_count, + stopped_stream_count_local: self.stopped_stream_local_count, + reset_stream_count_remote: self.reset_stream_remote_count, + stopped_stream_count_remote: self.stopped_stream_remote_count, + path_challenge_rx_count: self.path_challenge_rx_count, } } @@ -6282,7 +6785,7 @@ impl Connection { } fn encode_transport_params(&mut self) -> Result<()> { - let mut raw_params = [0; 128]; + let mut raw_params = [0; 168]; let raw_params = TransportParams::encode( &self.local_transport_params, @@ -6378,6 +6881,12 @@ impl Connection { self.ids .set_source_conn_id_limit(peer_params.active_conn_id_limit); + if self.local_transport_params.enable_multipath && + peer_params.enable_multipath + { + self.paths.set_multipath(true); + } + self.peer_transport_params = peer_params; Ok(()) @@ -6498,7 +7007,10 @@ impl Connection { // Downgrade the epoch to Initial as the remote peer might // not be able to decrypt handshake packets yet. packet::Epoch::Handshake - if self.pkt_num_spaces[packet::Epoch::Initial] + if self + .pkt_num_spaces + .crypto + .get(packet::Epoch::Initial) .has_keys() => return Ok(packet::Type::Initial), @@ -6513,12 +7025,11 @@ impl Connection { packet::Epoch::Initial..=packet::Epoch::Application, ) { // Only send packets in a space when we have the send keys for it. - if self.pkt_num_spaces[epoch].crypto_seal.is_none() { + if self.pkt_num_spaces.crypto.get(epoch).crypto_seal.is_none() { continue; } - // We are ready to send data for this packet number space. - if self.pkt_num_spaces[epoch].ready() { + if self.pkt_num_spaces.is_ready(epoch, None) { return Ok(packet::Type::from_epoch(epoch)); } @@ -6555,6 +7066,8 @@ impl Connection { self.streams.has_stopped() || self.ids.has_new_scids() || self.ids.has_retire_dcids() || + self.paths.has_path_abandon() || + self.paths.has_path_status() || send_path.needs_ack_eliciting || send_path.probing_required()) { @@ -6613,25 +7126,60 @@ impl Connection { let handshake_status = self.handshake_status(); - let is_app_limited = self.delivery_rate_check_if_app_limited(); - - for (_, p) in self.paths.iter_mut() { - if is_app_limited { - p.recovery.delivery_rate_update_app_limited(true); + if self.use_path_pkt_num_space(epoch) { + if let Some(path_id) = + self.ids.get_scid(0).ok().and_then(|e| e.path_id) + { + let is_app_limited = + self.delivery_rate_check_if_app_limited(path_id); + let p = self.paths.get_mut(path_id)?; + if is_app_limited { + p.recovery.delivery_rate_update_app_limited(true); + } + let (lost_packets, lost_bytes) = + p.recovery.on_ack_received( + 0, + &ranges, + ack_delay, + epoch, + handshake_status, + now, + &self.trace_id, + &mut self.newly_acked, + )?; + self.lost_count += lost_packets; + self.lost_bytes += lost_bytes as u64; + } + } else { + // This ACK may acknowledge several packets on different + // paths. + for pid in self + .paths + .iter() + .map(|(pid, _)| pid) + .collect::>() + { + let is_app_limited = + self.delivery_rate_check_if_app_limited(pid); + let p = self.paths.get_mut(pid)?; + if is_app_limited { + p.recovery.delivery_rate_update_app_limited(true); + } + let (lost_packets, lost_bytes) = + p.recovery.on_ack_received( + 0, + &ranges, + ack_delay, + epoch, + handshake_status, + now, + &self.trace_id, + &mut self.newly_acked, + )?; + + self.lost_count += lost_packets; + self.lost_bytes += lost_bytes as u64; } - - let (lost_packets, lost_bytes) = p.recovery.on_ack_received( - &ranges, - ack_delay, - epoch, - handshake_status, - now, - &self.trace_id, - &mut self.newly_acked, - )?; - - self.lost_count += lost_packets; - self.lost_bytes += lost_bytes as u64; } }, @@ -6682,6 +7230,9 @@ impl Connection { } self.rx_data += max_off_delta; + + self.reset_stream_remote_count = + self.reset_stream_remote_count.saturating_add(1); }, frame::Frame::StopSending { @@ -6735,12 +7286,22 @@ impl Connection { if !was_writable { self.streams.insert_writable(&priority_key); } + + self.stopped_stream_remote_count = + self.stopped_stream_remote_count.saturating_add(1); + self.reset_stream_local_count = + self.reset_stream_local_count.saturating_add(1); } }, frame::Frame::Crypto { data } => { // Push the data to the stream so it can be re-ordered. - self.pkt_num_spaces[epoch].crypto_stream.recv.write(data)?; + self.pkt_num_spaces + .crypto + .get_mut(epoch) + .crypto_stream + .recv + .write(data)?; // Feed crypto data to the TLS state, if there's data // available at the expected offset. @@ -6748,7 +7309,8 @@ impl Connection { let level = crypto::Level::from_epoch(epoch); - let stream = &mut self.pkt_num_spaces[epoch].crypto_stream; + let stream = + &mut self.pkt_num_spaces.crypto.get_mut(epoch).crypto_stream; while let Ok((read, _)) = stream.recv.emit(&mut crypto_buf) { let recv_buf = &crypto_buf[..read]; @@ -6761,7 +7323,10 @@ impl Connection { frame::Frame::CryptoHeader { .. } => unreachable!(), // TODO: implement stateless retry - frame::Frame::NewToken { .. } => (), + frame::Frame::NewToken { .. } => + if self.is_server { + return Err(Error::InvalidPacket); + }, frame::Frame::Stream { stream_id, data } => { // Peer can't send on our unidirectional streams. @@ -6932,21 +7497,15 @@ impl Connection { continue; } - if let Some(new_dcid_seq) = - self.ids.lowest_available_dcid_seq() - { - path.active_dcid_seq = Some(new_dcid_seq); - - self.ids.link_dcid_to_path_id(new_dcid_seq, pid)?; + let new_dcid_seq = self.ids.lowest_available_dcid_seq(); + update_dcid(&mut self.ids, pid, path, new_dcid_seq)?; + if let Some(new_dcid_seq) = new_dcid_seq { trace!( "{} path ID {} changed DCID: old seq num {} new seq num {}", self.trace_id, pid, dcid_seq, new_dcid_seq, ); } else { - // We cannot use this path anymore for now. - path.active_dcid_seq = None; - trace!( "{} path ID {} cannot be used; DCID seq num {} has been retired", self.trace_id, pid, dcid_seq, @@ -6963,17 +7522,41 @@ impl Connection { if let Some(pid) = self.ids.retire_scid(seq_num, &hdr.dcid)? { let path = self.paths.get_mut(pid)?; - // Maybe we already linked a new SCID to that path. - if path.active_scid_seq == Some(seq_num) { + // If we are closing the path and retiring the SCID before + // receiving the ACK(_MP), it won't be possible to associate + // the space ID with the path, and so get the acknowledgment + // for the PATH_ABANDON. We thus assume that in such case, + // the PATH_ABANDON got acknowledged and the path is fully + // closed at this point. + if path.is_closing() { + let (lost_packets, lost_bytes) = close_path( + &mut self.ids, + &mut self.pkt_num_spaces, + &mut self.paths, + pid, + now, + &self.trace_id, + )?; + self.lost_count += lost_packets; + self.lost_bytes += lost_bytes as u64; + } else if path.active_scid_seq == Some(seq_num) { + // Maybe we already linked a new SCID to that path. + // XXX: We do not remove unused paths now, we instead // wait until we need to maintain more paths than the // host is willing to. path.active_scid_seq = None; } + + self.pkt_num_spaces + .spaces + .update_lowest_active_rx_id(self.ids.min_scid_seq()); } }, frame::Frame::PathChallenge { data } => { + self.path_challenge_rx_count += 1; + self.paths .get_mut(recv_path_id)? .on_challenge_received(data); @@ -7038,20 +7621,143 @@ impl Connection { }, frame::Frame::DatagramHeader { .. } => unreachable!(), - } - - Ok(()) - } - - /// Drops the keys and recovery state for the given epoch. - fn drop_epoch_state(&mut self, epoch: packet::Epoch, now: time::Instant) { - if self.pkt_num_spaces[epoch].crypto_open.is_none() { - return; - } - - self.pkt_num_spaces[epoch].crypto_open = None; - self.pkt_num_spaces[epoch].crypto_seal = None; - self.pkt_num_spaces[epoch].clear(); + + frame::Frame::ACKMP { + space_identifier, + ranges, + ack_delay, + .. + } => { + if !self.use_path_pkt_num_space(epoch) { + return Err(Error::MultiPathViolation); + } + let ack_delay = ack_delay + .checked_mul(2_u64.pow( + self.peer_transport_params.ack_delay_exponent as u32, + )) + .ok_or(Error::InvalidFrame)?; + + // When we receive an ACK for a 1-RTT packet after handshake + // completion, it means the handshake has been confirmed. + if epoch == packet::Epoch::Application && self.is_established() { + self.peer_verified_initial_address = true; + + self.handshake_confirmed = true; + } + + let handshake_status = self.handshake_status(); + + // If an endpoint receives an ACK_MP frame with a packet number + // space ID which was never issued by endpoints (i.e., with a + // sequence number larger than the largest one advertised), it + // MUST treat this as a connection error of type + // MP_PROTOCOL_VIOLATION and close the connection. + if space_identifier > self.ids.largest_dcid_seq() { + return Err(Error::MultiPathViolation); + } + + // If an endpoint receives an ACK_MP frame with a packet number + // space ID which is no more active (e.g., retired by a + // RETIRE_CONNECTION_ID frame or belonging to closed paths), it + // MUST ignore the ACK_MP frame without causing a connection + // error. + if let Ok(e) = self.ids.get_dcid(space_identifier) { + if let Some(path_id) = e.path_id { + let is_app_limited = + self.delivery_rate_check_if_app_limited(path_id); + let p = self.paths.get_mut(path_id)?; + if is_app_limited { + p.recovery.delivery_rate_update_app_limited(true); + } + let (lost_packets, lost_bytes) = + p.recovery.on_ack_received( + space_identifier as u32, + &ranges, + ack_delay, + epoch, + handshake_status, + now, + &self.trace_id, + &mut self.newly_acked, + )?; + self.lost_count += lost_packets; + self.lost_bytes += lost_bytes as u64; + } + } + + // Once the handshake is confirmed, we can drop Handshake keys. + if self.handshake_confirmed { + self.drop_epoch_state(packet::Epoch::Handshake, now); + } + }, + + frame::Frame::PathAbandon { + dcid_seq_num, + error_code, + reason, + .. + } => { + if !self.use_path_pkt_num_space(epoch) { + return Err(Error::MultiPathViolation); + } + let abandon_pid = match self + .ids + .get_dcid(dcid_seq_num)? + .path_id + .ok_or(Error::InvalidFrame) + { + Ok(ap) => ap, + Err(_) => return Ok(()), + }; + self.paths.on_path_abandon_received( + abandon_pid, + error_code, + reason, + )?; + }, + + frame::Frame::PathStandby { + dcid_seq_num, + seq_num, + } => { + if !self.use_path_pkt_num_space(epoch) { + return Err(Error::MultiPathViolation); + } + let pid = self + .ids + .get_dcid(dcid_seq_num)? + .path_id + .ok_or(Error::InvalidFrame)?; + self.paths.on_path_status_received(pid, seq_num, false); + }, + + frame::Frame::PathAvailable { + dcid_seq_num, + seq_num, + } => { + if !self.use_path_pkt_num_space(epoch) { + return Err(Error::MultiPathViolation); + } + let pid = self + .ids + .get_dcid(dcid_seq_num)? + .path_id + .ok_or(Error::InvalidFrame)?; + self.paths.on_path_status_received(pid, seq_num, true); + }, + }; + Ok(()) + } + + /// Drops the keys and recovery state for the given epoch. + fn drop_epoch_state(&mut self, epoch: packet::Epoch, now: time::Instant) { + if self.pkt_num_spaces.crypto.get(epoch).crypto_open.is_none() { + return; + } + + self.pkt_num_spaces.crypto.get_mut(epoch).crypto_open = None; + self.pkt_num_spaces.crypto.get_mut(epoch).crypto_seal = None; + self.pkt_num_spaces.clear(epoch); let handshake_status = self.handshake_status(); for (_, p) in self.paths.iter_mut() { @@ -7120,7 +7826,10 @@ impl Connection { /// Returns the connection's handshake status for use in loss recovery. fn handshake_status(&self) -> recovery::HandshakeStatus { recovery::HandshakeStatus { - has_handshake_keys: self.pkt_num_spaces[packet::Epoch::Handshake] + has_handshake_keys: self + .pkt_num_spaces + .crypto + .get(packet::Epoch::Handshake) .has_keys(), peer_verified_address: self.peer_verified_initial_address, @@ -7131,16 +7840,22 @@ impl Connection { /// Updates send capacity. fn update_tx_cap(&mut self) { - let cwin_available = match self.paths.get_active() { - Ok(p) => p.recovery.cwnd_available() as u64, - Err(_) => 0, - }; - - self.tx_cap = - cmp::min(cwin_available, self.max_tx_data - self.tx_data) as usize; + let cwin_available = self + .paths + .iter() + .filter(|(_, p)| p.active()) + .map(|(_, p)| p.recovery.cwnd_available()) + .filter(|cwnd| *cwnd != std::usize::MAX) + .sum(); + self.tx_cap = cmp::min( + cwin_available, + (self.max_tx_data - self.tx_data) + .try_into() + .unwrap_or(usize::MAX), + ); } - fn delivery_rate_check_if_app_limited(&self) -> bool { + fn delivery_rate_check_if_app_limited(&self, path_id: usize) -> bool { // Enter the app-limited phase of delivery rate when these conditions // are met: // @@ -7158,10 +7873,9 @@ impl Connection { // and only applies to delivery rate calculation. let cwin_available = self .paths - .iter() - .filter(|&(_, p)| p.active()) - .map(|(_, p)| p.recovery.cwnd_available()) - .sum(); + .get(path_id) + .map(|p| p.recovery.cwnd_available()) + .unwrap_or(0); ((self.tx_buffered + self.dgram_send_queue_byte_size()) < cwin_available) && (self.tx_data.saturating_sub(self.last_tx_data)) < @@ -7185,20 +7899,19 @@ impl Connection { &mut self, recv_pid: Option, dcid: &ConnectionId, buf_len: usize, info: &RecvInfo, ) -> Result { - let ids = &mut self.ids; - let (in_scid_seq, mut in_scid_pid) = - ids.find_scid_seq(dcid).ok_or(Error::InvalidState)?; + self.ids.find_scid_seq(dcid).ok_or(Error::InvalidState)?; if let Some(recv_pid) = recv_pid { // If the path observes a change of SCID used, note it. let recv_path = self.paths.get_mut(recv_pid)?; - let cid_entry = - recv_path.active_scid_seq.and_then(|v| ids.get_scid(v).ok()); + let cid_entry = recv_path + .active_scid_seq + .and_then(|v| self.ids.get_scid(v).ok()); if cid_entry.map(|e| &e.cid) != Some(dcid) { - let incoming_cid_entry = ids.get_scid(in_scid_seq)?; + let incoming_cid_entry = self.ids.get_scid(in_scid_seq)?; let prev_recv_pid = incoming_cid_entry.path_id.unwrap_or(recv_pid); @@ -7222,8 +7935,8 @@ impl Connection { in_scid_seq ); - recv_path.active_scid_seq = Some(in_scid_seq); - ids.link_scid_to_path_id(in_scid_seq, recv_pid)?; + let recv_path = self.paths.get_mut(recv_pid)?; + update_scid(&mut self.ids, recv_pid, recv_path, in_scid_seq)?; } return Ok(recv_pid); @@ -7233,7 +7946,7 @@ impl Connection { // another path. // Ignore this step if are using zero-length SCID. - if ids.zero_length_scid() { + if self.ids.zero_length_scid() { in_scid_pid = None; } @@ -7266,21 +7979,23 @@ impl Connection { } // This is a new path using an unassigned CID; create it! - let mut path = - path::Path::new(info.to, info.from, &self.recovery_config, false); + let mut path = path::Path::new( + info.to, + info.from, + &self.recovery_config, + self.path_challenge_recv_max_queue_len, + false, + ); path.max_send_bytes = buf_len * MAX_AMPLIFICATION_FACTOR; - path.active_scid_seq = Some(in_scid_seq); // Automatically probes the new path. path.request_validation(); let pid = self.paths.insert_path(path, self.is_server)?; - // Do not record path reuse. - if in_scid_pid.is_none() { - ids.link_scid_to_path_id(in_scid_seq, pid)?; - } + let path = self.paths.get_mut(pid)?; + update_scid(&mut self.ids, pid, path, in_scid_seq)?; Ok(pid) } @@ -7306,6 +8021,64 @@ impl Connection { } } + let mut consider_standby = false; + let dgrams_to_emit = self.dgram_send_queue.has_pending(); + let stream_to_emit = self.streams.has_flushable(); + // When using aggregate mode, favour lowest-latency path on which CWIN + // is open. This should only be used when data need to be sent. + // If we have standby paths, we may run the loop a second time. + if self.paths.multipath() && (dgrams_to_emit || stream_to_emit) { + // We loop at most twice. + loop { + if let Some(pid) = self + .paths + .iter() + .filter(|(_, p)| { + // Follow the filter provided as parameters. + let local = from.map(|f| f == p.local_addr()).unwrap_or(true); + let peer = to.map(|t| t == p.peer_addr()).unwrap_or(true); + // Favour non-standby paths first, only consider active ones with open CWND. + local && peer && (consider_standby || !p.is_standby()) && p.active() && p.recovery.cwnd_available() > 0 + }) + // Lowest-latency first. + .min_by_key(|(_, p)| p.recovery.rtt()) + .map(|(pid, _)| pid) + { + return Ok(pid); + } + if consider_standby || !self.paths.consider_standby_paths() { + break; + } + consider_standby = true; + } + } + + // When using multiple packet number spaces, let's force ACK_MP sending + // on their corresponding paths. + if self.is_multipath_enabled() { + if let Some(pid) = + self.pkt_num_spaces + .spaces + .application_data_space_ids() + .find_map(|seq| { + self.pkt_num_spaces + .is_ready(packet::Epoch::Application, Some(seq)) + .then(|| { + self.ids.get_scid(seq).ok().and_then(|e| { + e.path_id.and_then(|pid| { + self.paths.get(pid).ok().and_then(|p| { + p.active().then_some(pid) + }) + }) + }) + }) + .flatten() + }) + { + return Ok(pid); + } + } + if let Some((pid, p)) = self.paths.get_active_with_pid() { if from.is_some() && Some(p.local_addr()) != from { return Err(Error::Done); @@ -7389,18 +8162,47 @@ impl Connection { .ok_or(Error::OutOfIdentifiers)? }; - let mut path = - path::Path::new(local_addr, peer_addr, &self.recovery_config, false); + let mut path = path::Path::new( + local_addr, + peer_addr, + &self.recovery_config, + self.path_challenge_recv_max_queue_len, + false, + ); path.active_dcid_seq = Some(dcid_seq); let pid = self .paths .insert_path(path, false) .map_err(|_| Error::OutOfIdentifiers)?; - self.ids.link_dcid_to_path_id(dcid_seq, pid)?; + let path = self.paths.get_mut(pid)?; + update_dcid(&mut self.ids, pid, path, Some(dcid_seq))?; Ok(pid) } + + // Marks the connection as closed and does any related tidyup. + fn mark_closed(&mut self) { + #[cfg(feature = "qlog")] + { + self.qlog.streamer = None; + } + self.closed = true; + } + + /// Returns whether the path-specific packet number space should be used for + /// sending packets. + #[inline] + fn use_path_pkt_num_space(&self, epoch: packet::Epoch) -> bool { + self.is_multipath_enabled() && epoch == packet::Epoch::Application + } +} + +#[cfg(feature = "boringssl-boring-crate")] +impl AsMut for Connection { + fn as_mut(&mut self) -> &mut boring::ssl::SslRef { + self.handshake.ssl_mut() + } } /// Maps an `Error` to `Error::Done`, or itself. @@ -7438,6 +8240,64 @@ fn drop_pkt_on_err( Error::Done } +/// Sets the DCID sequence number of the provided path identifier and +/// updates our internal state. +/// `path_id` must be the identifier of `path`. +fn update_dcid( + ids: &mut cid::ConnectionIdentifiers, path_id: usize, path: &mut path::Path, + dcid_seq: Option, +) -> Result<()> { + let dcid_seq = match dcid_seq { + Some(s) => s, + None => { + path.active_dcid_seq = None; + return Ok(()); + }, + }; + ids.link_dcid_to_path_id(dcid_seq, path_id)?; + path.active_dcid_seq = Some(dcid_seq); + + Ok(()) +} + +/// Sets the SCID sequence number of the provided path identifier and +/// updates our internal state. +fn update_scid( + ids: &mut cid::ConnectionIdentifiers, path_id: usize, path: &mut path::Path, + scid_seq: u64, +) -> Result<()> { + ids.link_scid_to_path_id(scid_seq, path_id)?; + path.active_scid_seq = Some(scid_seq); + + Ok(()) +} + +/// Closes the path with the corresponding path identifier. +fn close_path( + ids: &mut cid::ConnectionIdentifiers, + pkt_num_spaces: &mut packet::PktNumSpaceMap, paths: &mut path::PathMap, + pid: usize, now: time::Instant, trace_id: &str, +) -> Result<(usize, usize)> { + // If the path had a active DCID, remove it. + if let Ok(p) = paths.get(pid) { + if let Some(dcid_seq) = p.active_dcid_seq { + let _ = ids.retire_dcid(dcid_seq); + } + pkt_num_spaces + .spaces + .update_lowest_active_rx_id(ids.min_scid_seq()); + pkt_num_spaces + .spaces + .update_lowest_active_tx_id(ids.min_dcid_seq()); + } + paths.on_path_abandon_acknowledged(pid); + debug!("Closing path with pid {}; cleaning recovery", pid); + let abandon_path = paths.get_mut(pid)?; + Ok(abandon_path + .recovery + .mark_all_inflight_as_lost(now, trace_id)) +} + struct AddrTupleFmt(SocketAddr, SocketAddr); impl std::fmt::Display for AddrTupleFmt { @@ -7485,6 +8345,21 @@ pub struct Stats { /// The number of known paths for the connection. pub paths_count: usize, + + /// The number of streams reset by local. + pub reset_stream_count_local: u64, + + /// The number of streams stopped by local. + pub stopped_stream_count_local: u64, + + /// The number of streams reset by remote. + pub reset_stream_count_remote: u64, + + /// The number of streams stopped by remote. + pub stopped_stream_count_remote: u64, + + /// The total number of PATH_CHALLENGE frames that were received. + pub path_challenge_rx_count: u64, } impl std::fmt::Debug for Stats { @@ -7545,7 +8420,8 @@ pub struct TransportParams { pub retry_source_connection_id: Option>, /// DATAGRAM frame extension parameter, if any. pub max_datagram_frame_size: Option, - // pub preferred_address: ..., + /// Multipath extension parameter, if any. + pub enable_multipath: bool, } impl Default for TransportParams { @@ -7568,6 +8444,7 @@ impl Default for TransportParams { initial_source_connection_id: None, retry_source_connection_id: None, max_datagram_frame_size: None, + enable_multipath: false, } } } @@ -7718,6 +8595,10 @@ impl TransportParams { tp.max_datagram_frame_size = Some(val.get_varint()?); }, + 0x0f739bbc1b666d06 => { + tp.enable_multipath = true; + }, + // Ignore unknown parameters. _ => (), } @@ -7880,6 +8761,10 @@ impl TransportParams { b.put_varint(max_datagram_frame_size)?; } + if tp.enable_multipath { + TransportParams::encode_param(&mut b, 0x0f739bbc1b666d06, 0)?; + } + let out_len = b.off(); Ok(&mut out[..out_len]) @@ -8208,8 +9093,16 @@ pub mod testing { } pub fn client_update_key(&mut self) -> Result<()> { - let space = - &mut self.client.pkt_num_spaces[packet::Epoch::Application]; + let space = self + .client + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Application); + let pkt_space = self + .client + .pkt_num_spaces + .spaces + .get(packet::Epoch::Application, 0)?; let open_next = space .crypto_open @@ -8229,7 +9122,7 @@ pub mod testing { space.key_update = Some(packet::KeyUpdate { crypto_open: open_prev.unwrap(), - pn_on_update: space.next_pkt_num, + pn_on_update: pkt_space.next_pkt_num, update_acked: true, timer: time::Instant::now(), }); @@ -8329,9 +9222,8 @@ pub mod testing { let epoch = pkt_type.to_epoch()?; - let space = &mut conn.pkt_num_spaces[epoch]; - - let pn = space.next_pkt_num; + let multipath_multiple_spaces = conn.is_multipath_enabled(); + let pn = conn.pkt_num_spaces.spaces.get(epoch, 0)?.next_pkt_num; let pn_len = 4; let send_path = conn.paths.get_active()?; @@ -8365,7 +9257,13 @@ pub mod testing { let payload_len = frames.iter().fold(0, |acc, x| acc + x.wire_len()); if pkt_type != packet::Type::Short { - let len = pn_len + payload_len + space.crypto_overhead().unwrap(); + let len = pn_len + + payload_len + + conn.pkt_num_spaces + .crypto + .get(epoch) + .crypto_overhead() + .unwrap(); b.put_varint(len as u64)?; } @@ -8379,13 +9277,18 @@ pub mod testing { frame.to_bytes(&mut b)?; } - let aead = match space.crypto_seal { + let aead = match conn.pkt_num_spaces.crypto.get(epoch).crypto_seal { Some(ref v) => v, None => return Err(Error::InvalidState), }; + // We don't support multipath in this method. + assert!(!multipath_multiple_spaces); + let path_seq = packet::INITIAL_PACKET_NUMBER_SPACE_ID as u32; + let written = packet::encrypt_pkt( &mut b, + path_seq, pn, pn_len, payload_len, @@ -8394,7 +9297,7 @@ pub mod testing { aead, )?; - space.next_pkt_num += 1; + conn.pkt_num_spaces.spaces.get_mut(epoch, 0)?.next_pkt_num += 1; Ok(written) } @@ -8408,21 +9311,42 @@ pub mod testing { let epoch = hdr.ty.to_epoch()?; - let aead = conn.pkt_num_spaces[epoch].crypto_open.as_ref().unwrap(); + let aead = conn + .pkt_num_spaces + .crypto + .get(epoch) + .crypto_open + .as_ref() + .unwrap(); let payload_len = b.cap(); packet::decrypt_hdr(&mut b, &mut hdr, aead).unwrap(); let pn = packet::decode_pkt_num( - conn.pkt_num_spaces[epoch].largest_rx_pkt_num, + conn.pkt_num_spaces.spaces.get(epoch, 0)?.largest_rx_pkt_num, hdr.pkt_num, hdr.pkt_num_len, ); - let mut payload = - packet::decrypt_pkt(&mut b, pn, hdr.pkt_num_len, payload_len, aead) - .unwrap(); + let space_seq = if conn.is_multipath_enabled() { + conn.ids + .find_scid_seq(&hdr.dcid) + .map(|(seq, _)| seq) + .unwrap() as u32 + } else { + packet::INITIAL_PACKET_NUMBER_SPACE_ID as u32 + }; + + let mut payload = packet::decrypt_pkt( + &mut b, + space_seq, + pn, + hdr.pkt_num_len, + payload_len, + aead, + ) + .unwrap(); let mut frames = Vec::new(); @@ -8474,12 +9398,13 @@ mod tests { initial_source_connection_id: Some(b"woot woot".to_vec().into()), retry_source_connection_id: Some(b"retry".to_vec().into()), max_datagram_frame_size: Some(32), + enable_multipath: true, }; let mut raw_params = [42; 256]; let raw_params = TransportParams::encode(&tp, true, &mut raw_params).unwrap(); - assert_eq!(raw_params.len(), 94); + assert_eq!(raw_params.len(), 103); let new_tp = TransportParams::decode(raw_params, false).unwrap(); @@ -8504,12 +9429,13 @@ mod tests { initial_source_connection_id: Some(b"woot woot".to_vec().into()), retry_source_connection_id: None, max_datagram_frame_size: Some(32), + enable_multipath: true, }; let mut raw_params = [42; 256]; let raw_params = TransportParams::encode(&tp, false, &mut raw_params).unwrap(); - assert_eq!(raw_params.len(), 69); + assert_eq!(raw_params.len(), 78); let new_tp = TransportParams::decode(raw_params, true).unwrap(); @@ -9308,7 +10234,10 @@ mod tests { // Ensure ACK for key update. assert!( - pipe.server.pkt_num_spaces[packet::Epoch::Application] + pipe.server + .pkt_num_spaces + .crypto + .get(packet::Epoch::Application) .key_update .as_ref() .unwrap() @@ -9954,6 +10883,131 @@ mod tests { assert_eq!(3, pipe.client.peer_streams_left_bidi()); } + #[test] + fn stream_reset_counts() { + let mut pipe = testing::Pipe::new().unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + pipe.client.stream_send(0, b"a", false).ok(); + pipe.client.stream_send(2, b"a", false).ok(); + pipe.client.stream_send(4, b"a", false).ok(); + pipe.client.stream_send(8, b"a", false).ok(); + pipe.advance().unwrap(); + + let stats = pipe.client.stats(); + assert_eq!(stats.reset_stream_count_local, 0); + + // Client resets the stream. + pipe.client + .stream_shutdown(0, Shutdown::Write, 1001) + .unwrap(); + pipe.advance().unwrap(); + + let stats = pipe.client.stats(); + assert_eq!(stats.reset_stream_count_local, 1); + assert_eq!(stats.reset_stream_count_remote, 0); + let stats = pipe.server.stats(); + assert_eq!(stats.reset_stream_count_local, 0); + assert_eq!(stats.reset_stream_count_remote, 1); + + // Server resets the stream in reaction. + pipe.server + .stream_shutdown(0, Shutdown::Write, 1001) + .unwrap(); + pipe.advance().unwrap(); + + let stats = pipe.client.stats(); + assert_eq!(stats.reset_stream_count_local, 1); + assert_eq!(stats.reset_stream_count_remote, 1); + let stats = pipe.server.stats(); + assert_eq!(stats.reset_stream_count_local, 1); + assert_eq!(stats.reset_stream_count_remote, 1); + + // Repeat for the other streams + pipe.client + .stream_shutdown(2, Shutdown::Write, 1001) + .unwrap(); + pipe.client + .stream_shutdown(4, Shutdown::Write, 1001) + .unwrap(); + pipe.client + .stream_shutdown(8, Shutdown::Write, 1001) + .unwrap(); + pipe.advance().unwrap(); + + pipe.server + .stream_shutdown(4, Shutdown::Write, 1001) + .unwrap(); + pipe.server + .stream_shutdown(8, Shutdown::Write, 1001) + .unwrap(); + pipe.advance().unwrap(); + + let stats = pipe.client.stats(); + assert_eq!(stats.reset_stream_count_local, 4); + assert_eq!(stats.reset_stream_count_remote, 3); + let stats = pipe.server.stats(); + assert_eq!(stats.reset_stream_count_local, 3); + assert_eq!(stats.reset_stream_count_remote, 4); + } + + #[test] + fn stream_stop_counts() { + let mut pipe = testing::Pipe::new().unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + pipe.client.stream_send(0, b"a", false).ok(); + pipe.client.stream_send(2, b"a", false).ok(); + pipe.client.stream_send(4, b"a", false).ok(); + pipe.client.stream_send(8, b"a", false).ok(); + pipe.advance().unwrap(); + + let stats = pipe.client.stats(); + assert_eq!(stats.reset_stream_count_local, 0); + + // Server stops the stream and client automatically resets. + pipe.server + .stream_shutdown(0, Shutdown::Read, 1001) + .unwrap(); + pipe.advance().unwrap(); + + let stats = pipe.client.stats(); + assert_eq!(stats.stopped_stream_count_local, 0); + assert_eq!(stats.stopped_stream_count_remote, 1); + assert_eq!(stats.reset_stream_count_local, 1); + assert_eq!(stats.reset_stream_count_remote, 0); + + let stats = pipe.server.stats(); + assert_eq!(stats.stopped_stream_count_local, 1); + assert_eq!(stats.stopped_stream_count_remote, 0); + assert_eq!(stats.reset_stream_count_local, 0); + assert_eq!(stats.reset_stream_count_remote, 1); + + // Repeat for the other streams + pipe.server + .stream_shutdown(2, Shutdown::Read, 1001) + .unwrap(); + pipe.server + .stream_shutdown(4, Shutdown::Read, 1001) + .unwrap(); + pipe.server + .stream_shutdown(8, Shutdown::Read, 1001) + .unwrap(); + pipe.advance().unwrap(); + + let stats = pipe.client.stats(); + assert_eq!(stats.stopped_stream_count_local, 0); + assert_eq!(stats.stopped_stream_count_remote, 4); + assert_eq!(stats.reset_stream_count_local, 4); + assert_eq!(stats.reset_stream_count_remote, 0); + + let stats = pipe.server.stats(); + assert_eq!(stats.stopped_stream_count_local, 4); + assert_eq!(stats.stopped_stream_count_remote, 0); + assert_eq!(stats.reset_stream_count_local, 0); + assert_eq!(stats.reset_stream_count_remote, 4); + } + #[test] fn streams_blocked_max_bidi() { let mut buf = [0; 65535]; @@ -10346,7 +11400,11 @@ mod tests { // Note that `largest_rx_pkt_num` is initialized to 0, so we need to // send another 1-RTT packet to make this check meaningful. assert_eq!( - pipe.server.pkt_num_spaces[packet::Epoch::Application] + pipe.server + .pkt_num_spaces + .spaces + .get(packet::Epoch::Application, 0) + .unwrap() .largest_rx_pkt_num, 0 ); @@ -10357,7 +11415,11 @@ mod tests { assert!(pipe.server.is_established()); assert_eq!( - pipe.server.pkt_num_spaces[packet::Epoch::Application] + pipe.server + .pkt_num_spaces + .spaces + .get(packet::Epoch::Application, 0) + .unwrap() .largest_rx_pkt_num, 0 ); @@ -11380,15 +12442,26 @@ mod tests { frame.to_bytes(&mut b).unwrap(); } - let space = &mut pipe.client.pkt_num_spaces[epoch]; + let space_seq = if pipe.client.is_multipath_enabled() { + pipe.client + .ids + .find_scid_seq(&hdr.dcid) + .map(|(seq, _)| seq) + .unwrap() as u32 + } else { + packet::INITIAL_PACKET_NUMBER_SPACE_ID as u32 + }; + + let crypto = pipe.client.pkt_num_spaces.crypto.get(epoch); // Use correct payload length when encrypting the packet. let payload_len = frames.iter().fold(0, |acc, x| acc + x.wire_len()); - let aead = space.crypto_seal.as_ref().unwrap(); + let aead = crypto.crypto_seal.as_ref().unwrap(); let written = packet::encrypt_pkt( &mut b, + space_seq, pn, pn_len, payload_len, @@ -12007,46 +13080,96 @@ mod tests { ); } - fn check_send(_: &mut impl Send) {} - #[test] - fn config_must_be_send() { - let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); - check_send(&mut config); - } + /// Tests that a zero-length NEW_TOKEN frame is detected as an error. + fn zero_length_new_token() { + let mut buf = [0; 65535]; - #[test] - fn connection_must_be_send() { let mut pipe = testing::Pipe::new().unwrap(); - check_send(&mut pipe.client); - } + assert_eq!(pipe.handshake(), Ok(())); - fn check_sync(_: &mut impl Sync) {} + let mut frames = Vec::new(); - #[test] - fn config_must_be_sync() { - let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); - check_sync(&mut config); - } + frames.push(frame::Frame::NewToken { token: vec![] }); - #[test] - fn connection_must_be_sync() { - let mut pipe = testing::Pipe::new().unwrap(); - check_sync(&mut pipe.client); + let pkt_type = packet::Type::Short; + + let written = + testing::encode_pkt(&mut pipe.server, pkt_type, &frames, &mut buf) + .unwrap(); + + assert_eq!( + pipe.client_recv(&mut buf[..written]), + Err(Error::InvalidFrame) + ); } #[test] - fn data_blocked() { + /// Tests that a NEW_TOKEN frame sent by client is detected as an error. + fn client_sent_new_token() { let mut buf = [0; 65535]; let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); - assert_eq!(pipe.client.stream_send(0, b"aaaaaaaaaa", false), Ok(10)); - assert_eq!(pipe.client.blocked_limit, None); - assert_eq!(pipe.advance(), Ok(())); + let mut frames = Vec::new(); - assert_eq!(pipe.client.stream_send(4, b"aaaaaaaaaa", false), Ok(10)); + frames.push(frame::Frame::NewToken { + token: vec![1, 2, 3], + }); + + let pkt_type = packet::Type::Short; + + let written = + testing::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf) + .unwrap(); + + assert_eq!( + pipe.server_recv(&mut buf[..written]), + Err(Error::InvalidPacket) + ); + } + + fn check_send(_: &mut impl Send) {} + + #[test] + fn config_must_be_send() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + check_send(&mut config); + } + + #[test] + fn connection_must_be_send() { + let mut pipe = testing::Pipe::new().unwrap(); + check_send(&mut pipe.client); + } + + fn check_sync(_: &mut impl Sync) {} + + #[test] + fn config_must_be_sync() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + check_sync(&mut config); + } + + #[test] + fn connection_must_be_sync() { + let mut pipe = testing::Pipe::new().unwrap(); + check_sync(&mut pipe.client); + } + + #[test] + fn data_blocked() { + let mut buf = [0; 65535]; + + let mut pipe = testing::Pipe::new().unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + assert_eq!(pipe.client.stream_send(0, b"aaaaaaaaaa", false), Ok(10)); + assert_eq!(pipe.client.blocked_limit, None); + assert_eq!(pipe.advance(), Ok(())); + + assert_eq!(pipe.client.stream_send(4, b"aaaaaaaaaa", false), Ok(10)); assert_eq!(pipe.client.blocked_limit, None); assert_eq!(pipe.advance(), Ok(())); @@ -12577,7 +13700,16 @@ mod tests { let epoch = packet::Epoch::Application; - assert_eq!(pipe.server.pkt_num_spaces[epoch].recv_pkt_need_ack.len(), 0); + assert_eq!( + pipe.server + .pkt_num_spaces + .spaces + .get(epoch, 0) + .unwrap() + .recv_pkt_need_ack + .len(), + 0 + ); let frames = [frame::Frame::Ping, frame::Frame::Padding { len: 3 }]; @@ -12588,7 +13720,13 @@ mod tests { for _ in 0..512 { let recv_count = pipe.server.recv_count; - last_packet_sent = pipe.client.pkt_num_spaces[epoch].next_pkt_num; + last_packet_sent = pipe + .client + .pkt_num_spaces + .spaces + .get(epoch, 0) + .unwrap() + .next_pkt_num; pipe.send_pkt_to_server(pkt_type, &frames, &mut buf) .unwrap(); @@ -12596,21 +13734,44 @@ mod tests { assert_eq!(pipe.server.recv_count, recv_count + 1); // Skip packet number. - pipe.client.pkt_num_spaces[epoch].next_pkt_num += 1; + pipe.client + .pkt_num_spaces + .spaces + .get_mut(epoch, 0) + .unwrap() + .next_pkt_num += 1; } assert_eq!( - pipe.server.pkt_num_spaces[epoch].recv_pkt_need_ack.len(), + pipe.server + .pkt_num_spaces + .spaces + .get(epoch, 0) + .unwrap() + .recv_pkt_need_ack + .len(), MAX_ACK_RANGES ); assert_eq!( - pipe.server.pkt_num_spaces[epoch].recv_pkt_need_ack.first(), + pipe.server + .pkt_num_spaces + .spaces + .get(epoch, 0) + .unwrap() + .recv_pkt_need_ack + .first(), Some(last_packet_sent - ((MAX_ACK_RANGES as u64) - 1) * 2) ); assert_eq!( - pipe.server.pkt_num_spaces[epoch].recv_pkt_need_ack.last(), + pipe.server + .pkt_num_spaces + .spaces + .get(epoch, 0) + .unwrap() + .recv_pkt_need_ack + .last(), Some(last_packet_sent) ); } @@ -14371,10 +15532,10 @@ mod tests { // So far, there should not have any QUIC event. assert_eq!(pipe.client.path_event_next(), None); assert_eq!(pipe.server.path_event_next(), None); - assert_eq!(pipe.client.source_cids_left(), 2); + assert_eq!(pipe.client.scids_left(), 2); let (scid, reset_token) = testing::create_cid_and_reset_token(16); - assert_eq!(pipe.client.new_source_cid(&scid, reset_token, false), Ok(1)); + assert_eq!(pipe.client.new_scid(&scid, reset_token, false), Ok(1)); // Let exchange packets over the connection. assert_eq!(pipe.advance(), Ok(())); @@ -14383,11 +15544,11 @@ mod tests { assert_eq!(pipe.server.available_dcids(), 1); assert_eq!(pipe.server.path_event_next(), None); assert_eq!(pipe.client.path_event_next(), None); - assert_eq!(pipe.client.source_cids_left(), 1); + assert_eq!(pipe.client.scids_left(), 1); // Now, a second CID can be provided. let (scid, reset_token) = testing::create_cid_and_reset_token(16); - assert_eq!(pipe.client.new_source_cid(&scid, reset_token, false), Ok(2)); + assert_eq!(pipe.client.new_scid(&scid, reset_token, false), Ok(2)); // Let exchange packets over the connection. assert_eq!(pipe.advance(), Ok(())); @@ -14396,19 +15557,160 @@ mod tests { assert_eq!(pipe.server.available_dcids(), 2); assert_eq!(pipe.server.path_event_next(), None); assert_eq!(pipe.client.path_event_next(), None); - assert_eq!(pipe.client.source_cids_left(), 0); + assert_eq!(pipe.client.scids_left(), 0); // If now the client tries to send another CID, it reports an error // since it exceeds the limit of active CIDs. let (scid, reset_token) = testing::create_cid_and_reset_token(16); assert_eq!( - pipe.client.new_source_cid(&scid, reset_token, false), + pipe.client.new_scid(&scid, reset_token, false), Err(Error::IdLimit), ); } #[test] - /// Exercices the handling of NEW_CONNECTION_ID and RETIRE_CONNECTION_ID + /// Tests that NEW_CONNECTION_ID with zero-length CID are rejected. + fn connection_id_zero() { + let mut buf = [0; 65535]; + + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + let mut frames = Vec::new(); + + // Client adds a CID that is too short. + let (scid, reset_token) = testing::create_cid_and_reset_token(0); + + frames.push(frame::Frame::NewConnectionId { + seq_num: 1, + retire_prior_to: 0, + conn_id: scid.to_vec(), + reset_token: reset_token.to_be_bytes(), + }); + + let pkt_type = packet::Type::Short; + + let written = + testing::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf) + .unwrap(); + + let active_path = pipe.server.paths.get_active().unwrap(); + let info = RecvInfo { + to: active_path.local_addr(), + from: active_path.peer_addr(), + }; + + assert_eq!( + pipe.server.recv(&mut buf[..written], info), + Err(Error::InvalidFrame) + ); + + let written = match pipe.server.send(&mut buf) { + Ok((write, _)) => write, + + Err(_) => unreachable!(), + }; + + let frames = + testing::decode_pkt(&mut pipe.client, &mut buf[..written]).unwrap(); + let mut iter = frames.iter(); + + assert_eq!( + iter.next(), + Some(&frame::Frame::ConnectionClose { + error_code: 0x7, + frame_type: 0, + reason: Vec::new(), + }) + ); + } + + #[test] + /// Tests that NEW_CONNECTION_ID with too long CID are rejected. + fn connection_id_invalid_max_len() { + let mut buf = [0; 65535]; + + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + let mut frames = Vec::new(); + + // Client adds a CID that is too long. + let (scid, reset_token) = + testing::create_cid_and_reset_token(MAX_CONN_ID_LEN + 1); + + frames.push(frame::Frame::NewConnectionId { + seq_num: 1, + retire_prior_to: 0, + conn_id: scid.to_vec(), + reset_token: reset_token.to_be_bytes(), + }); + + let pkt_type = packet::Type::Short; + + let written = + testing::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf) + .unwrap(); + + let active_path = pipe.server.paths.get_active().unwrap(); + let info = RecvInfo { + to: active_path.local_addr(), + from: active_path.peer_addr(), + }; + + assert_eq!( + pipe.server.recv(&mut buf[..written], info), + Err(Error::InvalidFrame) + ); + + let written = match pipe.server.send(&mut buf) { + Ok((write, _)) => write, + + Err(_) => unreachable!(), + }; + + let frames = + testing::decode_pkt(&mut pipe.client, &mut buf[..written]).unwrap(); + let mut iter = frames.iter(); + + assert_eq!( + iter.next(), + Some(&frame::Frame::ConnectionClose { + error_code: 0x7, + frame_type: 0, + reason: Vec::new(), + }) + ); + } + + #[test] + /// Exercises the handling of NEW_CONNECTION_ID and RETIRE_CONNECTION_ID /// frames. fn connection_id_handling() { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); @@ -14430,15 +15732,12 @@ mod tests { // So far, there should not have any QUIC event. assert_eq!(pipe.client.path_event_next(), None); assert_eq!(pipe.server.path_event_next(), None); - assert_eq!(pipe.client.source_cids_left(), 1); + assert_eq!(pipe.client.scids_left(), 1); let scid = pipe.client.source_id().into_owned(); let (scid_1, reset_token_1) = testing::create_cid_and_reset_token(16); - assert_eq!( - pipe.client.new_source_cid(&scid_1, reset_token_1, false), - Ok(1) - ); + assert_eq!(pipe.client.new_scid(&scid_1, reset_token_1, false), Ok(1)); // Let exchange packets over the connection. assert_eq!(pipe.advance(), Ok(())); @@ -14447,7 +15746,7 @@ mod tests { assert_eq!(pipe.server.available_dcids(), 1); assert_eq!(pipe.server.path_event_next(), None); assert_eq!(pipe.client.path_event_next(), None); - assert_eq!(pipe.client.source_cids_left(), 0); + assert_eq!(pipe.client.scids_left(), 0); // Now we assume that the client wants to advertise more source // Connection IDs than the advertised limit. This is valid if it @@ -14455,10 +15754,7 @@ mod tests { // limits. let (scid_2, reset_token_2) = testing::create_cid_and_reset_token(16); - assert_eq!( - pipe.client.new_source_cid(&scid_2, reset_token_2, true), - Ok(2) - ); + assert_eq!(pipe.client.new_scid(&scid_2, reset_token_2, true), Ok(2)); // Let exchange packets over the connection. assert_eq!(pipe.advance(), Ok(())); @@ -14472,7 +15768,7 @@ mod tests { assert_eq!(pipe.client.retired_scid_next(), None); assert_eq!(pipe.client.path_event_next(), None); - assert_eq!(pipe.client.source_cids_left(), 0); + assert_eq!(pipe.client.scids_left(), 0); // The active Destination Connection ID of the server should now be the // one with sequence number 1. @@ -14480,17 +15776,11 @@ mod tests { // Now tries to experience CID retirement. If the server tries to remove // non-existing DCIDs, it fails. - assert_eq!( - pipe.server.retire_destination_cid(0), - Err(Error::InvalidState) - ); - assert_eq!( - pipe.server.retire_destination_cid(3), - Err(Error::InvalidState) - ); + assert_eq!(pipe.server.retire_dcid(0), Err(Error::InvalidState)); + assert_eq!(pipe.server.retire_dcid(3), Err(Error::InvalidState)); // Now it removes DCID with sequence 1. - assert_eq!(pipe.server.retire_destination_cid(1), Ok(())); + assert_eq!(pipe.server.retire_dcid(1), Ok(())); // Let exchange packets over the connection. assert_eq!(pipe.advance(), Ok(())); @@ -14503,10 +15793,7 @@ mod tests { assert_eq!(pipe.server.available_dcids(), 0); // Trying to remove the last DCID triggers an error. - assert_eq!( - pipe.server.retire_destination_cid(2), - Err(Error::OutOfIdentifiers) - ); + assert_eq!(pipe.server.retire_dcid(2), Err(Error::OutOfIdentifiers)); } #[test] @@ -14530,10 +15817,7 @@ mod tests { let scid = pipe.client.source_id().into_owned(); let (scid_1, reset_token_1) = testing::create_cid_and_reset_token(16); - assert_eq!( - pipe.client.new_source_cid(&scid_1, reset_token_1, false), - Ok(1) - ); + assert_eq!(pipe.client.new_scid(&scid_1, reset_token_1, false), Ok(1)); // Packets are sent, but never received. testing::emit_flight(&mut pipe.client).unwrap(); @@ -14551,7 +15835,7 @@ mod tests { assert_eq!(pipe.server.available_dcids(), 1); // Now the server retires the first Destination CID. - assert_eq!(pipe.server.retire_destination_cid(0), Ok(())); + assert_eq!(pipe.server.retire_dcid(0), Ok(())); // But the packet never reaches the client. testing::emit_flight(&mut pipe.server).unwrap(); @@ -14588,38 +15872,29 @@ mod tests { assert_eq!(pipe.handshake(), Ok(())); let (scid_1, reset_token_1) = testing::create_cid_and_reset_token(16); - assert_eq!( - pipe.client.new_source_cid(&scid_1, reset_token_1, false), - Ok(1) - ); + assert_eq!(pipe.client.new_scid(&scid_1, reset_token_1, false), Ok(1)); assert_eq!(pipe.advance(), Ok(())); // Trying to send the same CID with a different reset token raises an // InvalidState error. let reset_token_2 = reset_token_1.wrapping_add(1); assert_eq!( - pipe.client.new_source_cid(&scid_1, reset_token_2, false), + pipe.client.new_scid(&scid_1, reset_token_2, false), Err(Error::InvalidState), ); // Retrying to send the exact same CID with the same token returns the // previously assigned CID seq, but without sending anything. - assert_eq!( - pipe.client.new_source_cid(&scid_1, reset_token_1, false), - Ok(1) - ); + assert_eq!(pipe.client.new_scid(&scid_1, reset_token_1, false), Ok(1)); assert!(!pipe.client.ids.has_new_scids()); // Now retire this new CID. - assert_eq!(pipe.server.retire_destination_cid(1), Ok(())); + assert_eq!(pipe.server.retire_dcid(1), Ok(())); assert_eq!(pipe.advance(), Ok(())); // It is up to the application to ensure that a given SCID is not reused // later. - assert_eq!( - pipe.client.new_source_cid(&scid_1, reset_token_1, false), - Ok(2), - ); + assert_eq!(pipe.client.new_scid(&scid_1, reset_token_1, false), Ok(2)); } // Utility function. @@ -14648,11 +15923,7 @@ mod tests { c_reset_tokens.push(c_reset_token); assert_eq!( - pipe.client.new_source_cid( - &c_cids[i], - c_reset_tokens[i], - true - ), + pipe.client.new_scid(&c_cids[i], c_reset_tokens[i], true), Ok(i as u64 + 1) ); } @@ -14663,11 +15934,7 @@ mod tests { s_cids.push(s_cid); s_reset_tokens.push(s_reset_token); assert_eq!( - pipe.server.new_source_cid( - &s_cids[i], - s_reset_tokens[i], - true - ), + pipe.server.new_scid(&s_cids[i], s_reset_tokens[i], true), Ok(i as u64 + 1) ); } @@ -14719,16 +15986,10 @@ mod tests { let (c_cid, c_reset_token) = testing::create_cid_and_reset_token(16); - assert_eq!( - pipe.client.new_source_cid(&c_cid, c_reset_token, true), - Ok(1) - ); + assert_eq!(pipe.client.new_scid(&c_cid, c_reset_token, true), Ok(1)); let (s_cid, s_reset_token) = testing::create_cid_and_reset_token(16); - assert_eq!( - pipe.server.new_source_cid(&s_cid, s_reset_token, true), - Ok(1) - ); + assert_eq!(pipe.server.new_scid(&s_cid, s_reset_token, true), Ok(1)); // We need to exchange the CIDs first. assert_eq!( @@ -15070,10 +16331,7 @@ mod tests { let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); - assert_eq!( - pipe.client.retire_destination_cid(0), - Err(Error::OutOfIdentifiers) - ); + assert_eq!(pipe.client.retire_dcid(0), Err(Error::OutOfIdentifiers)); } #[test] @@ -15102,6 +16360,12 @@ mod tests { let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + let mut got = pipe.client.paths_iter(client_addr_2).collect::>(); + let mut expected = vec![server_addr]; + got.sort(); + expected.sort(); + assert_eq!(got, expected); + let mut buf = [0; 65535]; // There is nothing to send on the initial path. assert_eq!( @@ -15128,6 +16392,9 @@ mod tests { }; assert_eq!(pipe.server.recv(&mut buf[..sent], ri), Ok(sent)); + let stats = pipe.server.stats(); + assert_eq!(stats.path_challenge_rx_count, 1); + // A non-existing 4-tuple raises an InvalidState. let client_addr_3 = "127.0.0.1:9012".parse().unwrap(); let server_addr_2 = "127.0.0.1:9876".parse().unwrap(); @@ -15154,6 +16421,18 @@ mod tests { // Just to fit in two packets. assert_eq!(pipe.client.stream_send(0, &buf[..1201], true), Ok(1201)); + let mut got = pipe.client.paths_iter(client_addr).collect::>(); + let mut expected = vec![server_addr, server_addr_2]; + got.sort(); + expected.sort(); + assert_eq!(got, expected); + + let mut got = pipe.client.paths_iter(client_addr_3).collect::>(); + let mut expected = vec![server_addr]; + got.sort(); + expected.sort(); + assert_eq!(got, expected); + // PATH_CHALLENGE let (sent, si) = pipe .client @@ -15169,6 +16448,9 @@ mod tests { }; assert_eq!(pipe.server.recv(&mut buf[..sent], ri), Ok(sent)); + let stats = pipe.server.stats(); + assert_eq!(stats.path_challenge_rx_count, 2); + // STREAM frame on active path. let (sent, si) = pipe .client @@ -15183,6 +16465,9 @@ mod tests { }; assert_eq!(pipe.server.recv(&mut buf[..sent], ri), Ok(sent)); + let stats = pipe.server.stats(); + assert_eq!(stats.path_challenge_rx_count, 2); + // PATH_CHALLENGE let (sent, si) = pipe .client @@ -15198,6 +16483,9 @@ mod tests { }; assert_eq!(pipe.server.recv(&mut buf[..sent], ri), Ok(sent)); + let stats = pipe.server.stats(); + assert_eq!(stats.path_challenge_rx_count, 3); + // STREAM frame on active path. let (sent, si) = pipe .client @@ -15247,6 +16535,9 @@ mod tests { v2.sort(); assert_eq!(v1, v2); + + let stats = pipe.server.stats(); + assert_eq!(stats.path_challenge_rx_count, 3); } #[test] @@ -15724,6 +17015,7 @@ mod tests { let server_active_path = pipe.server.paths.get_active().unwrap(); assert_eq!(server_active_path.local_addr(), server_addr); assert_eq!(server_active_path.peer_addr(), client_addr); + assert_eq!(server_active_path.active(), true); assert_eq!(pipe.advance(), Ok(())); let (rcv_data_2, fin) = pipe.client.stream_recv(1, &mut recv_buf).unwrap(); @@ -15843,6 +17135,7 @@ mod tests { config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); + config.set_initial_max_data(999999999); config.set_initial_max_stream_data_bidi_local(30); config.set_initial_max_stream_data_bidi_remote(30); @@ -16048,7 +17341,7 @@ mod tests { for _ in 0..2 { let (cid, reset_token) = testing::create_cid_and_reset_token(16); pipe.server - .new_source_cid(&cid, reset_token, true) + .new_scid(&cid, reset_token, true) .expect("server issue cid"); server_cids.push(cid); } @@ -16064,7 +17357,7 @@ mod tests { let mut pkt_buf = [0u8; 1500]; let mut b = octets::OctetsMut::with_slice(&mut pkt_buf); let epoch = packet::Type::Short.to_epoch().unwrap(); - let space = &mut pipe.client.pkt_num_spaces[epoch]; + let space = pipe.client.pkt_num_spaces.spaces.get_mut(epoch, 0).unwrap(); let pn = space.next_pkt_num; let pn_len = 4; @@ -16089,10 +17382,19 @@ mod tests { frame.to_bytes(&mut b).expect("encode frames"); } - let aead = space.crypto_seal.as_ref().expect("crypto seal"); + let aead = pipe + .client + .pkt_num_spaces + .crypto + .get(epoch) + .crypto_seal + .as_ref() + .expect("crypto seal"); + let path_seq = packet::INITIAL_PACKET_NUMBER_SPACE_ID as u32; let written = packet::encrypt_pkt( &mut b, + path_seq, pn, pn_len, payload_len, @@ -16116,6 +17418,231 @@ mod tests { .paths_iter(server_addr) .any(|path| path == client_addr_2)); } + + #[test] + fn multipath() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(3); + config.set_initial_max_data(100000); + config.set_initial_max_stream_data_bidi_local(100000); + config.set_initial_max_stream_data_bidi_remote(100000); + config.set_initial_max_streams_bidi(2); + // To test with enabled datagrams. + config.enable_dgram(true, 10, 10); + config.set_multipath(true); + + let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1); + + assert_eq!(pipe.client.is_multipath_enabled(), true); + assert_eq!(pipe.server.is_multipath_enabled(), true); + + let client_addr = testing::Pipe::client_addr(); + let server_addr = testing::Pipe::server_addr(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + + let cid_c2s_0 = pipe.client.destination_id().into_owned(); + let cid_s2c_0 = pipe.server.destination_id().into_owned(); + + assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + assert_eq!(pipe.advance(), Ok(())); + assert_eq!( + pipe.client.path_event_next(), + Some(PathEvent::Validated(client_addr_2, server_addr)) + ); + assert_eq!(pipe.client.path_event_next(), None); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::New(server_addr, client_addr_2)) + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::Validated(server_addr, client_addr_2)) + ); + assert_eq!(pipe.server.path_event_next(), None); + + let pid_c2s_0 = pipe + .client + .paths + .path_id_from_addrs(&(client_addr, server_addr)) + .expect("no such path"); + let pid_c2s_1 = pipe + .client + .paths + .path_id_from_addrs(&(client_addr_2, server_addr)) + .expect("no such path"); + let pid_s2c_0 = pipe + .server + .paths + .path_id_from_addrs(&(server_addr, client_addr)) + .expect("no such path"); + let pid_s2c_1 = pipe + .server + .paths + .path_id_from_addrs(&(server_addr, client_addr_2)) + .expect("no such path"); + + let path_c2s_0 = pipe.client.paths.get(pid_c2s_0).expect("no such path"); + let path_c2s_1 = pipe.client.paths.get(pid_c2s_1).expect("no such path"); + let path_s2c_0 = pipe.server.paths.get(pid_s2c_0).expect("no such path"); + let path_s2c_1 = pipe.server.paths.get(pid_s2c_1).expect("no such path"); + + assert_eq!(path_c2s_0.active(), true); + assert_eq!(path_c2s_1.active(), false); + assert_eq!(path_s2c_0.active(), true); + assert_eq!(path_s2c_1.active(), false); + + assert_eq!( + pipe.client.set_active(client_addr_2, server_addr, true,), + Ok(()) + ); + assert_eq!( + pipe.server.set_active(server_addr, client_addr_2, true,), + Ok(()) + ); + + let path_c2s_0 = pipe.client.paths.get(pid_c2s_0).expect("no such path"); + let path_c2s_1 = pipe.client.paths.get(pid_c2s_1).expect("no such path"); + let path_s2c_0 = pipe.server.paths.get(pid_s2c_0).expect("no such path"); + let path_s2c_1 = pipe.server.paths.get(pid_s2c_1).expect("no such path"); + + assert_eq!(path_c2s_0.active(), true); + assert_eq!(path_c2s_1.active(), true); + assert_eq!(path_s2c_0.active(), true); + assert_eq!(path_s2c_1.active(), true); + + // Flush the ACK_MP on the newly active path. + assert_eq!(pipe.advance(), Ok(())); + + // Emit enough data to use both paths, but no more than their initial + // summed CWIN. + const DATA_BYTES: usize = 24000; + let buf = [42; DATA_BYTES]; + let mut recv_buf = [0; DATA_BYTES]; + + assert_eq!(pipe.server.stream_send(1, &buf, true), Ok(DATA_BYTES)); + assert_eq!(pipe.advance(), Ok(())); + let (rcv_data, fin) = pipe.client.stream_recv(1, &mut recv_buf).unwrap(); + assert_eq!(fin, true); + assert_eq!(rcv_data, DATA_BYTES); + + assert_eq!(pipe.server.path_event_next(), None); + + let path_c2s_0 = pipe.client.paths.get(pid_c2s_0).expect("no such path"); + let path_c2s_1 = pipe.client.paths.get(pid_c2s_1).expect("no such path"); + let path_s2c_0 = pipe.server.paths.get(pid_s2c_0).expect("no such path"); + let path_s2c_1 = pipe.server.paths.get(pid_s2c_1).expect("no such path"); + + assert_eq!(path_c2s_0.active(), true); + assert_eq!(path_c2s_1.active(), true); + assert_eq!(path_s2c_0.active(), true); + assert_eq!(path_s2c_1.active(), true); + assert!(path_s2c_0.recovery.bytes_sent >= DATA_BYTES / 2); + assert!(path_s2c_1.recovery.bytes_sent >= DATA_BYTES / 2); + + // Now close the initial path. + assert_eq!( + pipe.client.abandon_path( + client_addr, + server_addr, + 0, + "no error".into(), + ), + Ok(()), + ); + + let path_c2s_0 = pipe.client.paths.get(pid_c2s_0).expect("no such path"); + let path_c2s_1 = pipe.client.paths.get(pid_c2s_1).expect("no such path"); + let path_s2c_0 = pipe.server.paths.get(pid_s2c_0).expect("no such path"); + let path_s2c_1 = pipe.server.paths.get(pid_s2c_1).expect("no such path"); + + assert_eq!(path_c2s_0.active(), false); + assert_eq!(path_c2s_1.active(), true); + assert_eq!(path_s2c_0.active(), true); + assert_eq!(path_s2c_1.active(), true); + + assert_eq!(pipe.advance(), Ok(())); + + let path_c2s_0 = pipe.client.paths.get(pid_c2s_0).expect("no such path"); + let path_c2s_1 = pipe.client.paths.get(pid_c2s_1).expect("no such path"); + let path_s2c_0 = pipe.server.paths.get(pid_s2c_0).expect("no such path"); + let path_s2c_1 = pipe.server.paths.get(pid_s2c_1).expect("no such path"); + + assert_eq!(path_c2s_0.active(), false); + assert_eq!(path_c2s_1.active(), true); + assert_eq!(path_s2c_0.active(), false); + assert_eq!(path_s2c_1.active(), true); + + // No more in-flight packets on closed paths. + assert_eq!( + path_c2s_0.recovery.cwnd(), + path_c2s_0.recovery.cwnd_available() + ); + assert_eq!( + path_s2c_0.recovery.cwnd(), + path_s2c_0.recovery.cwnd_available() + ); + + assert_eq!(pipe.server.retired_scid_next(), Some(cid_c2s_0)); + assert_eq!(pipe.server.retired_scid_next(), None); + + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::Closed( + server_addr, + client_addr, + 0, + "no error".into(), + )) + ); + + assert_eq!(pipe.client.retired_scid_next(), Some(cid_s2c_0)); + assert_eq!(pipe.client.retired_scid_next(), None); + + assert_eq!( + pipe.client.path_event_next(), + Some(PathEvent::Closed( + client_addr, + server_addr, + 0, + "no error".into(), + )) + ); + } + + #[test] + fn multipath_zero_length_cid_not_supported() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(3); + config.set_initial_max_data(100000); + config.set_initial_max_stream_data_bidi_local(100000); + config.set_initial_max_stream_data_bidi_remote(100000); + config.set_initial_max_streams_bidi(2); + config.set_multipath(true); + + let pipe = pipe_with_exchanged_cids(&mut config, 0, 16, 1); + + assert_eq!(pipe.client.is_multipath_enabled(), false); + } } pub use crate::packet::ConnectionId; @@ -16123,7 +17650,9 @@ pub use crate::packet::Header; pub use crate::packet::Type; pub use crate::path::PathEvent; +pub use crate::path::PathState; pub use crate::path::PathStats; +pub use crate::path::PathStatus; pub use crate::path::SocketAddrIter; pub use crate::recovery::CongestionControlAlgorithm; diff --git a/quiche/src/packet.rs b/quiche/src/packet.rs index c2dbddbfbd..f67750ff0a 100644 --- a/quiche/src/packet.rs +++ b/quiche/src/packet.rs @@ -24,6 +24,7 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::collections::BTreeMap; use std::fmt::Display; use std::ops::Index; use std::ops::IndexMut; @@ -107,6 +108,8 @@ where } } +pub const INITIAL_PACKET_NUMBER_SPACE_ID: u64 = 0; + /// QUIC packet type. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Type { @@ -649,8 +652,8 @@ pub fn decode_pkt_num(largest_pn: u64, truncated_pn: u64, pn_len: usize) -> u64 } pub fn decrypt_pkt<'a>( - b: &'a mut octets::OctetsMut, pn: u64, pn_len: usize, payload_len: usize, - aead: &crypto::Open, + b: &'a mut octets::OctetsMut, path_seq: u32, pn: u64, pn_len: usize, + payload_len: usize, aead: &crypto::Open, ) -> Result> { let payload_offset = b.off(); @@ -662,8 +665,12 @@ pub fn decrypt_pkt<'a>( let mut ciphertext = payload.peek_bytes_mut(payload_len)?; - let payload_len = - aead.open_with_u64_counter(pn, header.as_ref(), ciphertext.as_mut())?; + let payload_len = aead.open_with_u64_counter( + path_seq, + pn, + header.as_ref(), + ciphertext.as_mut(), + )?; Ok(b.get_bytes(payload_len)?) } @@ -694,13 +701,16 @@ pub fn encrypt_hdr( Ok(()) } +#[allow(clippy::too_many_arguments)] pub fn encrypt_pkt( - b: &mut octets::OctetsMut, pn: u64, pn_len: usize, payload_len: usize, - payload_offset: usize, extra_in: Option<&[u8]>, aead: &crypto::Seal, + b: &mut octets::OctetsMut, path_seq: u32, pn: u64, pn_len: usize, + payload_len: usize, payload_offset: usize, extra_in: Option<&[u8]>, + aead: &crypto::Seal, ) -> Result { let (mut header, mut payload) = b.split_at(payload_offset)?; let ciphertext_len = aead.seal_with_u64_counter( + path_seq, pn, header.as_ref(), payload.as_mut(), @@ -867,16 +877,6 @@ pub struct PktNumSpace { pub recv_pkt_num: PktNumWindow, pub ack_elicited: bool, - - pub key_update: Option, - - pub crypto_open: Option, - pub crypto_seal: Option, - - pub crypto_0rtt_open: Option, - pub crypto_0rtt_seal: Option, - - pub crypto_stream: stream::Stream, } impl PktNumSpace { @@ -895,7 +895,33 @@ impl PktNumSpace { recv_pkt_num: PktNumWindow::default(), ack_elicited: false, + } + } + + fn clear(&mut self) { + self.ack_elicited = false; + } + + fn ready(&self) -> bool { + self.ack_elicited + } +} + +pub struct PktNumSpaceCrypto { + pub key_update: Option, + + pub crypto_open: Option, + pub crypto_seal: Option, + + pub crypto_0rtt_open: Option, + pub crypto_0rtt_seal: Option, + + pub crypto_stream: stream::Stream, +} +impl PktNumSpaceCrypto { + pub fn new() -> PktNumSpaceCrypto { + PktNumSpaceCrypto { key_update: None, crypto_open: None, @@ -915,7 +941,7 @@ impl PktNumSpace { } } - pub fn clear(&mut self) { + fn clear(&mut self) { self.crypto_stream = stream::Stream::new( 0, // dummy u64::MAX, @@ -924,16 +950,14 @@ impl PktNumSpace { true, stream::MAX_STREAM_WINDOW, ); - - self.ack_elicited = false; } pub fn crypto_overhead(&self) -> Option { Some(self.crypto_seal.as_ref()?.alg().tag_len()) } - pub fn ready(&self) -> bool { - self.crypto_stream.is_flushable() || self.ack_elicited + fn ready(&self) -> bool { + self.crypto_stream.is_flushable() } pub fn has_keys(&self) -> bool { @@ -941,6 +965,159 @@ impl PktNumSpace { } } +pub struct PktNumSpaceImplMap { + pkt_num_spaces: [PktNumSpace; Epoch::Application as usize], + application_pkt_num_spaces: BTreeMap, + + /// Lowest possible RX space ID still in use. + lowest_rx_space_id: u64, + /// Lowest possible TX space ID still in use. + lowest_tx_space_id: u64, +} + +impl PktNumSpaceImplMap { + fn new() -> PktNumSpaceImplMap { + PktNumSpaceImplMap { + pkt_num_spaces: [PktNumSpace::new(), PktNumSpace::new()], + application_pkt_num_spaces: BTreeMap::from([(0, PktNumSpace::new())]), + + lowest_rx_space_id: 0, + lowest_tx_space_id: 0, + } + } + + pub fn get(&self, epoch: Epoch, space_id: u64) -> Result<&PktNumSpace> { + match epoch { + Epoch::Application => self + .application_pkt_num_spaces + .get(&space_id) + .ok_or(Error::InvalidState), + e => Ok(&self.pkt_num_spaces[e]), + } + } + + pub fn get_mut_or_create( + &mut self, epoch: Epoch, space_id: u64, + ) -> &mut PktNumSpace { + match epoch { + Epoch::Application => self + .application_pkt_num_spaces + .entry(space_id) + .or_insert_with(PktNumSpace::new), + e => &mut self.pkt_num_spaces[e], + } + } + + pub fn get_mut( + &mut self, epoch: Epoch, space_id: u64, + ) -> Result<&mut PktNumSpace> { + match epoch { + Epoch::Application => self + .application_pkt_num_spaces + .get_mut(&space_id) + .ok_or(Error::InvalidState), + e => Ok(&mut self.pkt_num_spaces[e]), + } + } + + fn is_ready(&self, epoch: Epoch, space_id: Option) -> bool { + match (epoch, space_id) { + (Epoch::Application, None) => self + .application_pkt_num_spaces + .values() + .any(|pns| pns.ready()), + (e, Some(s)) => match self.get(e, s) { + Ok(pns) => pns.ready(), + Err(_) => false, + }, + (e, None) => match self.get(e, 0) { + Ok(pns) => pns.ready(), + Err(_) => false, + }, + } + } + + /// Remove application packet number spaces whose connection IDs have been + /// retired, to free their memory. + fn remove_dangling_application_pkt_num_spaces(&mut self) { + let lowest_space_id = + std::cmp::min(self.lowest_rx_space_id, self.lowest_tx_space_id); + self.application_pkt_num_spaces + .retain(|&s, _| s >= lowest_space_id) + } + + pub fn update_lowest_active_rx_id(&mut self, rx_id: u64) { + if rx_id > self.lowest_rx_space_id { + self.lowest_rx_space_id = rx_id; + self.remove_dangling_application_pkt_num_spaces(); + } + } + + pub fn update_lowest_active_tx_id(&mut self, tx_id: u64) { + if tx_id > self.lowest_tx_space_id { + self.lowest_tx_space_id = tx_id; + self.remove_dangling_application_pkt_num_spaces(); + } + } + + pub fn application_data_space_ids(&self) -> impl Iterator + '_ { + self.application_pkt_num_spaces.keys().copied() + } +} + +pub struct PktNumSpaceCryptoMap { + inner: [PktNumSpaceCrypto; Epoch::count()], +} + +impl PktNumSpaceCryptoMap { + fn new() -> PktNumSpaceCryptoMap { + PktNumSpaceCryptoMap { + inner: [ + PktNumSpaceCrypto::new(), + PktNumSpaceCrypto::new(), + PktNumSpaceCrypto::new(), + ], + } + } + + #[inline] + pub fn get(&self, epoch: Epoch) -> &PktNumSpaceCrypto { + &self.inner[epoch] + } + + #[inline] + pub fn get_mut(&mut self, epoch: Epoch) -> &mut PktNumSpaceCrypto { + &mut self.inner[epoch] + } +} + +pub struct PktNumSpaceMap { + pub spaces: PktNumSpaceImplMap, + pub crypto: PktNumSpaceCryptoMap, +} + +impl PktNumSpaceMap { + pub fn new() -> PktNumSpaceMap { + PktNumSpaceMap { + spaces: PktNumSpaceImplMap::new(), + crypto: PktNumSpaceCryptoMap::new(), + } + } + + pub fn clear(&mut self, epoch: Epoch) { + self.spaces.get_mut(epoch, 0).map(|pns| pns.clear()).ok(); + self.crypto.get_mut(epoch).clear(); + } + + /// Returns whether the epoch is ready. + /// When specifying the Epoch::Application, if `space_id` is `None`, it will + /// consider all the application data packet number spaces; otherwise it + /// only consider the specified `space_id`. + pub fn is_ready(&self, epoch: Epoch, space_id: Option) -> bool { + self.crypto.get(epoch).ready() || self.spaces.is_ready(epoch, space_id) + } +} + #[derive(Clone, Copy, Default)] pub struct PktNumWindow { lower: u64, @@ -1310,7 +1487,8 @@ mod tests { assert_eq!(pn, expected_pn); let payload = - decrypt_pkt(&mut b, pn, hdr.pkt_num_len, payload_len, &aead).unwrap(); + decrypt_pkt(&mut b, 0, pn, hdr.pkt_num_len, payload_len, &aead) + .unwrap(); let payload = payload.as_ref(); assert_eq!(&payload[..expected_frames.len()], expected_frames); @@ -1514,7 +1692,7 @@ mod tests { let alg = crypto::Algorithm::ChaCha20_Poly1305; - let aead = crypto::Open::from_secret(alg, &secret).unwrap(); + let aead = crypto::Open::from_secret(alg, secret.into()).unwrap(); let mut hdr = Header::from_bytes(&mut b, 0).unwrap(); assert_eq!(hdr.ty, Type::Short); @@ -1528,7 +1706,8 @@ mod tests { assert_eq!(pn, 654_360_564); let payload = - decrypt_pkt(&mut b, pn, hdr.pkt_num_len, payload_len, &aead).unwrap(); + decrypt_pkt(&mut b, 0, pn, hdr.pkt_num_len, payload_len, &aead) + .unwrap(); let payload = payload.as_ref(); assert_eq!(&payload, &[0x01]); @@ -1560,6 +1739,7 @@ mod tests { let written = encrypt_pkt( &mut b, + 0, pn, pn_len, payload_len, @@ -1882,7 +2062,7 @@ mod tests { let alg = crypto::Algorithm::ChaCha20_Poly1305; - let aead = crypto::Seal::from_secret(alg, &secret).unwrap(); + let aead = crypto::Seal::from_secret(alg, secret.into()).unwrap(); let pn = 654_360_564; let pn_len = 3; @@ -1897,6 +2077,7 @@ mod tests { let written = encrypt_pkt( &mut b, + 0, pn, pn_len, payload_len, @@ -1937,7 +2118,7 @@ mod tests { crypto::derive_initial_key_material(b"", hdr.version, true).unwrap(); assert_eq!( - decrypt_pkt(&mut b, 0, 1, payload_len, &aead), + decrypt_pkt(&mut b, 0, 0, 1, payload_len, &aead), Err(Error::InvalidPacket) ); } @@ -1970,7 +2151,7 @@ mod tests { crypto::derive_initial_key_material(b"", hdr.version, true).unwrap(); assert_eq!( - decrypt_pkt(&mut b, 0, 1, payload_len, &aead), + decrypt_pkt(&mut b, 0, 0, 1, payload_len, &aead), Err(Error::CryptoFail) ); } diff --git a/quiche/src/path.rs b/quiche/src/path.rs index 395f5b30c2..8589d56640 100644 --- a/quiche/src/path.rs +++ b/quiche/src/path.rs @@ -42,7 +42,7 @@ use crate::recovery::HandshakeStatus; /// The different states of the path validation. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum PathState { +pub enum PathValidationState { /// The path failed its validation. Failed, @@ -59,15 +59,75 @@ pub enum PathState { Validated, } -impl PathState { +impl PathValidationState { #[cfg(feature = "ffi")] pub fn to_c(self) -> libc::ssize_t { match self { - PathState::Failed => -1, - PathState::Unknown => 0, - PathState::Validating => 1, - PathState::ValidatingMTU => 2, - PathState::Validated => 3, + PathValidationState::Failed => -1, + PathValidationState::Unknown => 0, + PathValidationState::Validating => 1, + PathValidationState::ValidatingMTU => 2, + PathValidationState::Validated => 3, + } + } +} + +/// The different usage states of the path. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum PathState { + /// The path only sends probing packets. + Unused, + /// The path can send non-probing packets. + Active, + /// The path is under closing process. + Closing(u64, Vec), + /// The path is now closed. + Closed(u64, Vec), +} + +/// The different requests that can be assigned to a path. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum PathRequest { + /// The path should not send non-probing packets. + Unused, + /// The path should send probing packets. + Active, + /// The path should be abandonned, with the provided error code and reason + /// message. + Abandon(u64, Vec), +} + +impl PathRequest { + fn requested_state(self) -> PathState { + match self { + PathRequest::Unused => PathState::Unused, + PathRequest::Active => PathState::Active, + PathRequest::Abandon(e, r) => PathState::Closing(e, r), + } + } +} + +/// The status of a path, advertised through the PATH_STATUS frame. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PathStatus { + /// The host should stop sending non-probing packets on the path. + Standby, + + /// The host should consider this path to send non-probing packets. + Available, +} + +impl From for bool { + fn from(s: PathStatus) -> Self { + matches!(s, PathStatus::Available) + } +} + +impl From for PathStatus { + fn from(v: bool) -> Self { + match v { + false => PathStatus::Standby, + true => PathStatus::Available, } } } @@ -92,7 +152,8 @@ pub enum PathEvent { /// The related network path between local `SocketAddr` and peer /// `SocketAddr` has been closed and is now unusable on this connection. - Closed(SocketAddr, SocketAddr), + /// An error code and a reason message are provided. + Closed(SocketAddr, SocketAddr, u64, Vec), /// The stack observes that the Source Connection ID with the given sequence /// number, initially used by the peer over the first pair of `SocketAddr`s, @@ -109,10 +170,12 @@ pub enum PathEvent { /// /// Note that this event is only raised if the path has been validated. PeerMigrated(SocketAddr, SocketAddr), + + /// The peer advertised the path status for the mentioned 4-tuple. + PeerPathStatus((SocketAddr, SocketAddr), PathStatus), } /// A network path on which QUIC packets can be sent. -#[derive(Debug)] pub struct Path { /// The local address. local_addr: SocketAddr, @@ -127,11 +190,10 @@ pub struct Path { pub active_dcid_seq: Option, /// The current validation state of the path. + validation_state: PathValidationState, + /// The usage state of this path. state: PathState, - /// Is this path used to send non-probing packets. - active: bool, - /// Loss recovery and congestion control state. pub recovery: recovery::Recovery, @@ -151,6 +213,9 @@ pub struct Path { /// Received challenge data. received_challenges: VecDeque<[u8; 8]>, + /// Max length of received challenges queue. + received_challenges_max_len: usize, + /// Number of packets sent on this path. pub sent_count: usize, @@ -170,6 +235,14 @@ pub struct Path { /// This counts only STREAM and CRYPTO data. pub stream_retrans_bytes: u64, + /// The timeout of closing the path. + closing_timer: Option, + /// Whether the peer abandoned this path. + peer_abandoned: bool, + + /// The scheduling status of this path. + status: PathStatus, + /// Total number of bytes the server can send before the peer's address /// is verified. pub max_send_bytes: usize, @@ -192,6 +265,9 @@ pub struct Path { /// Whether or not we should force eliciting of an ACK (e.g. via PING frame) pub needs_ack_eliciting: bool, + + /// The expected sequence number of the PATH_STATUS to be received. + expected_path_status_seq_num: u64, } impl Path { @@ -199,12 +275,13 @@ impl Path { /// the fields being set to their default value. pub fn new( local_addr: SocketAddr, peer_addr: SocketAddr, - recovery_config: &recovery::RecoveryConfig, is_initial: bool, + recovery_config: &recovery::RecoveryConfig, + path_challenge_recv_max_queue_len: usize, is_initial: bool, ) -> Self { - let (state, active_scid_seq, active_dcid_seq) = if is_initial { - (PathState::Validated, Some(0), Some(0)) + let (validation_state, active_scid_seq, active_dcid_seq) = if is_initial { + (PathValidationState::Validated, Some(0), Some(0)) } else { - (PathState::Unknown, None, None) + (PathValidationState::Unknown, None, None) }; Self { @@ -212,20 +289,26 @@ impl Path { peer_addr, active_scid_seq, active_dcid_seq, - state, - active: false, + validation_state, + state: PathState::Unused, recovery: recovery::Recovery::new_with_config(recovery_config), in_flight_challenges: VecDeque::new(), max_challenge_size: 0, probing_lost: 0, last_probe_lost_time: None, - received_challenges: VecDeque::new(), + received_challenges: VecDeque::with_capacity( + path_challenge_recv_max_queue_len, + ), + received_challenges_max_len: path_challenge_recv_max_queue_len, sent_count: 0, recv_count: 0, retrans_count: 0, sent_bytes: 0, recv_bytes: 0, stream_retrans_bytes: 0, + closing_timer: None, + peer_abandoned: false, + status: PathStatus::Available, max_send_bytes: 0, verified_peer_address: false, peer_verified_local_address: false, @@ -233,6 +316,7 @@ impl Path { failure_notified: false, migrating: false, needs_ack_eliciting: false, + expected_path_status_seq_num: 0, } } @@ -250,21 +334,23 @@ impl Path { /// Returns whether the path is working (i.e., not failed). #[inline] - fn working(&self) -> bool { - self.state > PathState::Failed + pub fn working(&self) -> bool { + self.validation_state > PathValidationState::Failed } /// Returns whether the path is active. #[inline] pub fn active(&self) -> bool { - self.active && self.working() && self.active_dcid_seq.is_some() + self.state == PathState::Active && + self.working() && + self.active_dcid_seq.is_some() } /// Returns whether the path can be used to send non-probing packets. #[inline] pub fn usable(&self) -> bool { self.active() || - (self.state == PathState::Validated && + (self.validation_state == PathValidationState::Validated && self.active_dcid_seq.is_some()) } @@ -281,30 +367,45 @@ impl Path { !self.received_challenges.is_empty() || self.validation_requested() } - /// Promotes the path to the provided state only if the new state is greater - /// than the current one. - fn promote_to(&mut self, state: PathState) { - if self.state < state { - self.state = state; + /// Returns whether this path is under closing process. + #[inline] + pub fn is_closing(&self) -> bool { + matches!(self.state, PathState::Closing(_, _)) + } + + /// Returns whether this path is closed. + #[inline] + fn closed(&self) -> bool { + matches!(self.state, PathState::Closed(_, _)) + } + + /// Promotes the path to the provided validation state only if the new state + /// is greater than the current one. + fn promote_to(&mut self, state: PathValidationState) { + if self.validation_state < state { + self.validation_state = state; } } /// Returns whether the path is validated. #[inline] pub fn validated(&self) -> bool { - self.state == PathState::Validated + self.validation_state == PathValidationState::Validated } /// Returns whether this path failed its validation. #[inline] fn validation_failed(&self) -> bool { - self.state == PathState::Failed + self.validation_state == PathValidationState::Failed } // Returns whether this path is under path validation process. #[inline] pub fn under_validation(&self) -> bool { - matches!(self.state, PathState::Validating | PathState::ValidatingMTU) + matches!( + self.validation_state, + PathValidationState::Validating | PathValidationState::ValidatingMTU + ) } /// Requests path validation. @@ -320,7 +421,7 @@ impl Path { } pub fn on_challenge_sent(&mut self) { - self.promote_to(PathState::Validating); + self.promote_to(PathValidationState::Validating); self.challenge_requested = false; } @@ -334,6 +435,11 @@ impl Path { } pub fn on_challenge_received(&mut self, data: [u8; 8]) { + // Discard challenges that would cause us to queue more than we want. + if self.received_challenges.len() == self.received_challenges_max_len { + return; + } + self.received_challenges.push_back(data); self.peer_verified_local_address = true; } @@ -342,6 +448,10 @@ impl Path { self.in_flight_challenges.iter().any(|(d, ..)| *d == data) } + pub fn on_abandon_received(&mut self) { + self.peer_abandoned = true; + } + /// Returns whether the path is now validated. pub fn on_response_received(&mut self, data: [u8; 8]) -> bool { self.verified_peer_address = true; @@ -358,15 +468,15 @@ impl Path { }); // The 4-tuple is reachable, but we didn't check Path MTU yet. - self.promote_to(PathState::ValidatingMTU); + self.promote_to(PathValidationState::ValidatingMTU); self.max_challenge_size = std::cmp::max(self.max_challenge_size, challenge_size); - if self.state == PathState::ValidatingMTU { + if self.validation_state == PathValidationState::ValidatingMTU { if self.max_challenge_size >= crate::MIN_CLIENT_INITIAL_LEN { // Path MTU is sufficient for QUIC traffic. - self.promote_to(PathState::Validated); + self.promote_to(PathValidationState::Validated); return true; } @@ -378,8 +488,50 @@ impl Path { } fn on_failed_validation(&mut self) { - self.state = PathState::Failed; - self.active = false; + self.validation_state = PathValidationState::Failed; + self.state = PathState::Unused; + } + + pub fn on_closing_timeout(&mut self) { + self.closing_timer = None; + if let PathState::Closing(e, r) = &mut self.state { + self.state = PathState::Closed(*e, std::mem::take(r)); + } + } + + pub fn closing_error_code_and_reason(&self) -> Result<(u64, Vec)> { + match &self.state { + PathState::Closing(e, r) | PathState::Closed(e, r) => + Ok((*e, r.clone())), + _ => Err(Error::InvalidState), + } + } + + #[inline] + fn valid_state_transition(&self, new_state: &PathState) -> bool { + match (&self.state, new_state) { + // In Unused or Active, we can transition to any state. + (PathState::Unused, _) => true, + (PathState::Active, _) => true, + // In Closing, we can only transition to Closing or Closed. + (PathState::Closing(..), PathState::Closing(..)) => true, + (PathState::Closing(..), PathState::Closed(..)) => true, + // In Close, we can only transition to itself. + (PathState::Closed(..), PathState::Closed(..)) => true, + // Any other transition is invalid. + (..) => false, + } + } + + /// Sets the state of a path, returning an error if the transition is not + /// valid. + fn set_state(&mut self, state: PathState) -> Result<()> { + if !self.valid_state_transition(&state) { + return Err(Error::InvalidState); + } + + self.state = state; + Ok(()) } #[inline] @@ -387,6 +539,20 @@ impl Path { self.received_challenges.pop_front() } + /// Returns the time at which a timeout will occur on the path. + #[inline] + pub fn path_timer(&self) -> Option { + [self.closing_timer, self.recovery.loss_detection_timer()] + .iter() + .filter_map(|&t| t) + .min() + } + + #[inline] + pub fn closing_timer(&self) -> Option { + self.closing_timer + } + pub fn on_loss_detection_timeout( &mut self, handshake_status: HandshakeStatus, now: time::Instant, is_server: bool, trace_id: &str, @@ -442,19 +608,27 @@ impl Path { (lost_packets, lost_bytes) } + #[inline] + pub fn is_standby(&self) -> bool { + matches!(self.status, PathStatus::Standby) + } + pub fn stats(&self) -> PathStats { PathStats { local_addr: self.local_addr, peer_addr: self.peer_addr, - validation_state: self.state, - active: self.active, + validation_state: self.validation_state, + state: self.state.clone(), + active: self.active(), recv: self.recv_count, sent: self.sent_count, lost: self.recovery.lost_count, + lost_spurious: self.recovery.lost_spurious_count, retrans: self.retrans_count, rtt: self.recovery.rtt(), min_rtt: self.recovery.min_rtt(), rttvar: self.recovery.rttvar(), + rtt_update: self.recovery.rtt_update_count, cwnd: self.recovery.cwnd(), sent_bytes: self.sent_bytes, recv_bytes: self.recv_bytes, @@ -509,6 +683,18 @@ pub struct PathMap { /// Whether this manager serves a connection as a server. is_server: bool, + + /// Whether the multipath extensions are enabled. + multipath: bool, + + /// Path identifiers requiring sending PATH_ABANDON frames. + path_abandon: VecDeque, + + /// Whether a connection-wide PATH_STATUS frame should be sent. + /// Send a PATH_AVAILABLE is true, PATH_STANDBY else. + path_status_to_advertise: VecDeque<(usize, u64, bool)>, + /// The sequence number for the next PATH_STATUS. + next_path_status_seq_num: u64, } impl PathMap { @@ -524,7 +710,7 @@ impl PathMap { let peer_addr = initial_path.peer_addr; // As it is the first path, it is active by default. - initial_path.active = true; + initial_path.state = PathState::Active; let active_path_id = paths.insert(initial_path); addrs_to_paths.insert((local_addr, peer_addr), active_path_id); @@ -535,6 +721,10 @@ impl PathMap { addrs_to_paths, events: VecDeque::new(), is_server, + multipath: false, + path_abandon: VecDeque::new(), + path_status_to_advertise: VecDeque::new(), + next_path_status_seq_num: 0, } } @@ -646,11 +836,38 @@ impl PathMap { self.addrs_to_paths .remove(&(path.local_addr, path.peer_addr)); - self.notify_event(PathEvent::Closed(path.local_addr, path.peer_addr)); + self.notify_event(PathEvent::Closed( + path.local_addr, + path.peer_addr, + 0, + "unused path".into(), + )); Ok(()) } + /// Adds or remove the path ID from the set of paths requiring sending a + /// PATH_ABANDON frame. + fn mark_path_abandon(&mut self, path_id: usize, abandon: bool) { + if abandon { + self.path_abandon.push_back(path_id); + } else { + self.path_abandon.retain(|p| *p != path_id); + } + } + + /// Returns the Path ID that should be advertised in the next PATH_ABANDON + /// frame. + pub fn path_abandon(&self) -> Option { + self.path_abandon.front().copied() + } + + /// Returns true if there are any paths that need to send PATH_ABANDON + /// frames. + pub fn has_path_abandon(&self) -> bool { + !self.path_abandon.is_empty() + } + /// Records the provided `Path` and returns its assigned identifier. /// /// On success, this method takes care of creating a notification to the @@ -705,6 +922,25 @@ impl PathMap { } } + pub fn notify_closed_paths(&mut self) { + let paths = &mut self.paths; + let events = &mut self.events; + for (_, p) in paths + .iter_mut() + .filter(|(_, p)| p.closed() && !p.failure_notified) + { + if let PathState::Closed(e, r) = &p.state { + events.push_back(PathEvent::Closed( + p.local_addr, + p.peer_addr, + *e, + r.clone(), + )); + p.failure_notified = true; + } + } + } + /// Finds a path candidate to be active and returns its identifier. pub fn find_candidate_path(&self) -> Option { // TODO: also consider unvalidated paths if there are no more validated. @@ -714,6 +950,11 @@ impl PathMap { .map(|(pid, _)| pid) } + /// Returns whether standby paths should be considered to send data packets. + pub fn consider_standby_paths(&self) -> bool { + self.iter().filter(|(_, p)| !p.is_standby()).count() == 0 + } + /// Handles incoming PATH_RESPONSE data. pub fn on_response_received(&mut self, data: [u8; 8]) -> Result<()> { let active_pid = self.get_active_path_id()?; @@ -744,35 +985,129 @@ impl PathMap { Ok(()) } + /// Handles acknowledged PATH_ABANDONs. + pub fn on_path_abandon_acknowledged(&mut self, abandon_path_id: usize) { + if let Ok(path) = self.get_mut(abandon_path_id) { + let local_addr = path.local_addr; + let peer_addr = path.peer_addr; + let to_notify = if let PathState::Closing(e, r) = &mut path.state { + let to_notify = Some((*e, r.clone())); + path.state = PathState::Closed(*e, std::mem::take(r)); + to_notify + } else { + None + }; + if let Some((e, r)) = to_notify { + self.notify_event(PathEvent::Closed(local_addr, peer_addr, e, r)); + } + } + } + + /// Handles incoming PATH_ABANDONs. + pub fn on_path_abandon_received( + &mut self, abandon_path_id: usize, error_code: u64, reason: Vec, + ) -> Result<()> { + let is_server = self.is_server; + let nb_paths = self.paths.len(); + let abandon_path = self.get_mut(abandon_path_id)?; + // If we are the server, and receiving a PATH_ABANDON for the only + // active path, request a connection closure. + if is_server && nb_paths == 1 { + return Err(Error::UnavailablePath); + } + // If the path was already closed, just close it. + if abandon_path.closed() { + return Ok(()); + } + let was_closing = abandon_path.is_closing(); + abandon_path.set_state(PathState::Closing(error_code, reason))?; + abandon_path.on_abandon_received(); + if !was_closing { + self.mark_path_abandon(abandon_path_id, true); + } + Ok(()) + } + + /// Handles the sending of PATH_ABANDONs. + pub fn on_path_abandon_sent( + &mut self, abandon_path_id: usize, now: time::Instant, + ) -> Result<()> { + let abandoned_path = self.get_mut(abandon_path_id)?; + abandoned_path.closing_timer = Some(now + abandoned_path.recovery.pto()); + self.mark_path_abandon(abandon_path_id, false); + Ok(()) + } + + /// Returns whether multipath extension has been enabled. + pub fn multipath(&self) -> bool { + self.multipath + } + + /// Sets whether multipath extension is enabled. + pub fn set_multipath(&mut self, v: bool) { + self.multipath = v; + } + + /// Changes the state of the path with the identifier `path_id` according to + /// the provided `PathRequest`. + /// + /// This API is only usable when multipath extensions are enabled. + /// Otherwise, it raises an [`InvalidState`]. + /// + /// In case the request is invalid, returns an [`InvalidState`]. + /// + /// [`InvalidState`]: enum.Error.html#variant.InvalidState + pub fn request( + &mut self, path_id: usize, request: PathRequest, + ) -> Result<()> { + if !self.multipath { + return Err(Error::InvalidState); + } + let path = self.get_mut(path_id)?; + let requested_state = request.requested_state(); + path.set_state(requested_state)?; + if path.is_closing() { + self.mark_path_abandon(path_id, true); + } + Ok(()) + } + /// Sets the path with identifier 'path_id' to be active. /// - /// There can be exactly one active path on which non-probing packets can be - /// sent. If another path is marked as active, it will be superseded by the - /// one having `path_id` as identifier. + /// When multipath extensions are disabled, there can be exactly one active + /// path on which non-probing packets can be sent. If another path is marked + /// as active, it will be superseeded by the one having `path_id` as + /// identifier. /// /// A server should always ensure that the active path is validated. If it - /// is already the case, it notifies the application that the connection - /// migrated. Otherwise, it triggers a path validation and defers the - /// notification once it is actually validated. + /// is already the case, when the multipath extensions are disabled, it + /// notifies the application that the connection migrated. Otherwise, it + /// triggers a path validation and, if multipath extensions are disabled, + /// defers the notification once it is actually validated. + /// + /// When multipath extensions are enabled, this call is equivalent to + /// calling [`request()`] with `PathRequest::Active`. + /// + /// [`request()`]: struct.PathManager.html#method.request pub fn set_active_path(&mut self, path_id: usize) -> Result<()> { let is_server = self.is_server; - - if let Ok(old_active_path) = self.get_active_mut() { - old_active_path.active = false; + let multipath = self.multipath; + if !multipath { + if let Ok(old_active_path) = self.get_active_mut() { + old_active_path.set_state(PathState::Unused)?; + } } let new_active_path = self.get_mut(path_id)?; - new_active_path.active = true; + new_active_path.set_state(PathState::Active)?; if is_server { - if new_active_path.validated() { + if new_active_path.validated() && !multipath { let local_addr = new_active_path.local_addr(); let peer_addr = new_active_path.peer_addr(); - self.notify_event(PathEvent::PeerMigrated(local_addr, peer_addr)); - } else { - new_active_path.migrating = true; - + } else if !new_active_path.validated() { + new_active_path.migrating = !multipath; // Requests path validation if needed. if !new_active_path.under_validation() { new_active_path.request_validation(); @@ -782,6 +1117,57 @@ impl PathMap { Ok(()) } + + /// Sets the provided `status` on he path identified by `path_id`. + pub fn set_path_status( + &mut self, path_id: usize, status: PathStatus, + ) -> Result<()> { + self.get_mut(path_id)?.status = status; + Ok(()) + } + + /// Requests the advertisement of a path status. + pub fn advertise_path_status(&mut self, path_id: usize) -> Result<()> { + let status = self.get(path_id)?.status; + self.path_status_to_advertise.push_back(( + path_id, + self.next_path_status_seq_num, + status.into(), + )); + self.next_path_status_seq_num += 1; + Ok(()) + } + + /// Returns true if the host should send a PATH_STATUS frame. + #[inline] + pub fn has_path_status(&self) -> bool { + !self.path_status_to_advertise.is_empty() + } + + /// Returns the Path ID, the sequence number and the availability + /// status (PATH_STANDBY or PATH_AVAILABLE) that should be advertised next. + pub fn path_status(&self) -> Option<(usize, u64, bool)> { + self.path_status_to_advertise.front().copied() + } + + /// Handles the sending of PATH_STANDBY/PATH_AVAILABLE. + pub fn on_path_status_sent(&mut self) { + self.path_status_to_advertise.pop_front(); + } + + /// Handles the reception of PATH_STANDBY/PATH_AVAILABLE. + pub fn on_path_status_received( + &mut self, path_id: usize, seq_num: u64, available: bool, + ) { + if let Ok(p) = self.get_mut(path_id) { + if seq_num >= p.expected_path_status_seq_num { + p.expected_path_status_seq_num = seq_num.saturating_add(1); + let addr = (p.local_addr(), p.peer_addr()); + self.events + .push_back(PathEvent::PeerPathStatus(addr, available.into())); + } + } + } } /// Statistics about the path of a connection. @@ -798,9 +1184,12 @@ pub struct PathStats { pub peer_addr: SocketAddr, /// The path validation state. - pub validation_state: PathState, + pub validation_state: PathValidationState, + + /// The path state. + pub state: PathState, - /// Whether the path is marked as active. + /// Is it active? pub active: bool, /// The number of QUIC packets received. @@ -812,6 +1201,9 @@ pub struct PathStats { /// The number of QUIC packets that were lost. pub lost: usize, + /// The number of QUIC packets that were spuriously marked as lost. + pub lost_spurious: usize, + /// The number of sent QUIC packets with retransmitted data. pub retrans: usize, @@ -825,6 +1217,9 @@ pub struct PathStats { /// variation. pub rttvar: time::Duration, + /// The number of round-trip time updates over that path. + pub rtt_update: usize, + /// The size of the connection's congestion window in bytes. pub cwnd: usize, @@ -864,13 +1259,13 @@ impl std::fmt::Debug for PathStats { )?; write!( f, - "validation_state={:?} active={} ", - self.validation_state, self.active, + "validation_state={:?} state={:?} ", + self.validation_state, self.state, )?; write!( f, - "recv={} sent={} lost={} retrans={} rtt={:?} min_rtt={:?} rttvar={:?} cwnd={}", - self.recv, self.sent, self.lost, self.retrans, self.rtt, self.min_rtt, self.rttvar, self.cwnd, + "recv={} sent={} lost={} lost_spurious={} retrans={} rtt={:?} min_rtt={:?} rttvar={:?} rtt_update={} cwnd={}", + self.recv, self.sent, self.lost, self.lost_spurious, self.retrans, self.rtt, self.min_rtt, self.rttvar, self.rtt_update, self.cwnd, )?; write!( @@ -906,11 +1301,22 @@ mod tests { let config = Config::new(crate::PROTOCOL_VERSION).unwrap(); let recovery_config = RecoveryConfig::from_config(&config); - let path = Path::new(client_addr, server_addr, &recovery_config, true); + let path = Path::new( + client_addr, + server_addr, + &recovery_config, + config.path_challenge_recv_max_queue_len, + true, + ); let mut path_mgr = PathMap::new(path, 2, false); - let probed_path = - Path::new(client_addr_2, server_addr, &recovery_config, false); + let probed_path = Path::new( + client_addr_2, + server_addr, + &recovery_config, + config.path_challenge_recv_max_queue_len, + false, + ); path_mgr.insert_path(probed_path, false).unwrap(); let pid = path_mgr @@ -933,7 +1339,10 @@ mod tests { assert!(!path_mgr.get_mut(pid).unwrap().probing_required()); assert!(path_mgr.get_mut(pid).unwrap().under_validation()); assert!(!path_mgr.get_mut(pid).unwrap().validated()); - assert_eq!(path_mgr.get_mut(pid).unwrap().state, PathState::Validating); + assert_eq!( + path_mgr.get_mut(pid).unwrap().validation_state, + PathValidationState::Validating + ); assert_eq!(path_mgr.pop_event(), None); // Receives the response. The path is reachable, but the MTU is not @@ -945,8 +1354,8 @@ mod tests { assert!(path_mgr.get_mut(pid).unwrap().under_validation()); assert!(!path_mgr.get_mut(pid).unwrap().validated()); assert_eq!( - path_mgr.get_mut(pid).unwrap().state, - PathState::ValidatingMTU + path_mgr.get_mut(pid).unwrap().validation_state, + PathValidationState::ValidatingMTU ); assert_eq!(path_mgr.pop_event(), None); @@ -965,7 +1374,10 @@ mod tests { assert!(!path_mgr.get_mut(pid).unwrap().probing_required()); assert!(!path_mgr.get_mut(pid).unwrap().under_validation()); assert!(path_mgr.get_mut(pid).unwrap().validated()); - assert_eq!(path_mgr.get_mut(pid).unwrap().state, PathState::Validated); + assert_eq!( + path_mgr.get_mut(pid).unwrap().validation_state, + PathValidationState::Validated + ); assert_eq!( path_mgr.pop_event(), Some(PathEvent::Validated(client_addr_2, server_addr)) @@ -980,10 +1392,21 @@ mod tests { let config = Config::new(crate::PROTOCOL_VERSION).unwrap(); let recovery_config = RecoveryConfig::from_config(&config); - let path = Path::new(client_addr, server_addr, &recovery_config, true); + let path = Path::new( + client_addr, + server_addr, + &recovery_config, + config.path_challenge_recv_max_queue_len, + true, + ); let mut client_path_mgr = PathMap::new(path, 2, false); - let mut server_path = - Path::new(server_addr, client_addr, &recovery_config, false); + let mut server_path = Path::new( + server_addr, + client_addr, + &recovery_config, + config.path_challenge_recv_max_queue_len, + false, + ); let client_pid = client_path_mgr .path_id_from_addrs(&(client_addr, server_addr)) @@ -1049,4 +1472,226 @@ mod tests { 0 ); } + + #[test] + fn too_many_probes() { + let client_addr = "127.0.0.1:1234".parse().unwrap(); + let server_addr = "127.0.0.1:4321".parse().unwrap(); + + // Default to DEFAULT_MAX_PATH_CHALLENGE_RX_QUEUE_LEN + let config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + let recovery_config = RecoveryConfig::from_config(&config); + + let path = Path::new( + client_addr, + server_addr, + &recovery_config, + config.path_challenge_recv_max_queue_len, + true, + ); + let mut client_path_mgr = PathMap::new(path, 2, false); + let mut server_path = Path::new( + server_addr, + client_addr, + &recovery_config, + config.path_challenge_recv_max_queue_len, + false, + ); + + let client_pid = client_path_mgr + .path_id_from_addrs(&(client_addr, server_addr)) + .unwrap(); + + // First probe. + let data = rand::rand_u64().to_be_bytes(); + + client_path_mgr + .get_mut(client_pid) + .unwrap() + .add_challenge_sent( + data, + MIN_CLIENT_INITIAL_LEN, + time::Instant::now(), + ); + + // Second probe. + let data_2 = rand::rand_u64().to_be_bytes(); + + client_path_mgr + .get_mut(client_pid) + .unwrap() + .add_challenge_sent( + data_2, + MIN_CLIENT_INITIAL_LEN, + time::Instant::now(), + ); + assert_eq!( + client_path_mgr + .get(client_pid) + .unwrap() + .in_flight_challenges + .len(), + 2 + ); + + // Third probe. + let data_3 = rand::rand_u64().to_be_bytes(); + + client_path_mgr + .get_mut(client_pid) + .unwrap() + .add_challenge_sent( + data_3, + MIN_CLIENT_INITIAL_LEN, + time::Instant::now(), + ); + assert_eq!( + client_path_mgr + .get(client_pid) + .unwrap() + .in_flight_challenges + .len(), + 3 + ); + + // Fourth probe. + let data_4 = rand::rand_u64().to_be_bytes(); + + client_path_mgr + .get_mut(client_pid) + .unwrap() + .add_challenge_sent( + data_4, + MIN_CLIENT_INITIAL_LEN, + time::Instant::now(), + ); + assert_eq!( + client_path_mgr + .get(client_pid) + .unwrap() + .in_flight_challenges + .len(), + 4 + ); + + // If we receive multiple challenges, we can store them up to our queue + // size. + server_path.on_challenge_received(data); + assert_eq!(server_path.received_challenges.len(), 1); + server_path.on_challenge_received(data_2); + assert_eq!(server_path.received_challenges.len(), 2); + server_path.on_challenge_received(data_3); + assert_eq!(server_path.received_challenges.len(), 3); + server_path.on_challenge_received(data_4); + assert_eq!(server_path.received_challenges.len(), 3); + + // Response for first probe. + client_path_mgr.on_response_received(data).unwrap(); + assert_eq!( + client_path_mgr + .get(client_pid) + .unwrap() + .in_flight_challenges + .len(), + 3 + ); + + // Response for second probe. + client_path_mgr.on_response_received(data_2).unwrap(); + assert_eq!( + client_path_mgr + .get(client_pid) + .unwrap() + .in_flight_challenges + .len(), + 2 + ); + + // Response for third probe. + client_path_mgr.on_response_received(data_3).unwrap(); + assert_eq!( + client_path_mgr + .get(client_pid) + .unwrap() + .in_flight_challenges + .len(), + 1 + ); + + // There will never be a response for fourth probe... + } + + #[test] + fn path_priority() { + use crate::DEFAULT_MAX_PATH_CHALLENGE_RX_QUEUE_LEN; + let client_addr = "127.0.0.1:1234".parse().unwrap(); + let client_addr_2 = "127.0.0.1:2345".parse().unwrap(); + let client_addr_3 = "127.0.0.1:3456".parse().unwrap(); + let server_addr = "127.0.0.1:4321".parse().unwrap(); + + let config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + let recovery_config = RecoveryConfig::from_config(&config); + let path_challenge_recv_max_queue_len = DEFAULT_MAX_PATH_CHALLENGE_RX_QUEUE_LEN; + + let path = Path::new(client_addr, server_addr, &recovery_config, path_challenge_recv_max_queue_len, true); + let mut paths = PathMap::new(path, 3, true); + let pid = paths + .path_id_from_addrs(&(client_addr, server_addr)) + .unwrap(); + + let path_2 = + Path::new(client_addr_2, server_addr, &recovery_config, path_challenge_recv_max_queue_len, false); + let pid_2 = paths.insert_path(path_2, false).unwrap(); + let path_3 = + Path::new(client_addr_3, server_addr, &recovery_config, path_challenge_recv_max_queue_len, false); + let pid_3 = paths.insert_path(path_3, false).unwrap(); + + assert_eq!(paths.set_path_status(pid_2, PathStatus::Standby), Ok(())); + assert_eq!( + paths + .iter() + .filter_map(|(pid, p)| p.is_standby().then(|| pid)) + .collect::>(), + vec![pid_2] + ); + assert_eq!( + paths + .iter() + .filter_map(|(pid, p)| (!p.is_standby()).then(|| pid)) + .collect::>(), + vec![pid, pid_3] + ); + assert_eq!( + paths.set_path_status(42, PathStatus::Standby), + Err(Error::InvalidState) + ); + + // Fake sending of PATH_STATUS frame. + paths.advertise_path_status(pid_2).unwrap(); + + // We can also fake send for another non-backup path. + paths.advertise_path_status(pid_3).unwrap(); + + assert_eq!(paths.has_path_status(), true); + assert_eq!(paths.path_status(), Some((pid_2, 0, false))); + paths.on_path_status_sent(); + assert_eq!(paths.has_path_status(), true); + assert_eq!(paths.path_status(), Some((pid_3, 1, true))); + paths.on_path_status_sent(); + assert_eq!(paths.has_path_status(), false); + assert_eq!(paths.path_status(), None); + + assert_eq!(paths.set_path_status(pid_3, PathStatus::Standby), Ok(())); + assert_eq!(paths.set_path_status(pid_2, PathStatus::Available), Ok(())); + paths.advertise_path_status(pid_2).unwrap(); + paths.advertise_path_status(pid_3).unwrap(); + assert_eq!(paths.has_path_status(), true); + assert_eq!(paths.path_status(), Some((pid_2, 2, true))); + paths.on_path_status_sent(); + assert_eq!(paths.has_path_status(), true); + assert_eq!(paths.path_status(), Some((pid_3, 3, false))); + paths.on_path_status_sent(); + assert_eq!(paths.has_path_status(), false); + assert_eq!(paths.path_status(), None); + } } diff --git a/quiche/src/ranges.rs b/quiche/src/ranges.rs index a7e17b56eb..0b530c7cd1 100644 --- a/quiche/src/ranges.rs +++ b/quiche/src/ranges.rs @@ -236,7 +236,7 @@ impl InlineRangeSet { } } - // Merge any newly overlaping ranges + // Merge any newly overlapping ranges while let Some((s, e)) = self.inner.get(pos + 1).copied() { if end < s { // We are done, since the next range is completely disjoint diff --git a/quiche/src/recovery/bbr/mod.rs b/quiche/src/recovery/bbr/mod.rs index 07683dc0ae..c70897735d 100644 --- a/quiche/src/recovery/bbr/mod.rs +++ b/quiche/src/recovery/bbr/mod.rs @@ -417,7 +417,7 @@ mod tests { // Send 5 packets. for pn in 0..5 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -452,6 +452,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -487,7 +488,7 @@ mod tests { // Send 5 packets. for pn in 0..5 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -523,6 +524,7 @@ mod tests { // 2 acked, 2 x MSS lost. assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -556,7 +558,7 @@ mod tests { // Stop right before filled_pipe=true. for _ in 0..3 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -592,6 +594,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -607,7 +610,7 @@ mod tests { // Stop at right before filled_pipe=true. for _ in 0..5 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -646,6 +649,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -679,7 +683,7 @@ mod tests { // smaller than BBRInFlight(1). for (pn, _) in (0..4).enumerate() { let pkt = Sent { - pkt_num: pn as u64, + pkt_num: SpacedPktNum(0, pn as u64), frames: smallvec![], time_sent: now, time_acked: None, @@ -712,6 +716,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -750,7 +755,7 @@ mod tests { // smaller than BBRInFlight(1). for _ in 0..4 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -785,6 +790,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -804,7 +810,7 @@ mod tests { let now = now + RTPROP_FILTER_LEN; let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -841,6 +847,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, diff --git a/quiche/src/recovery/bbr2/mod.rs b/quiche/src/recovery/bbr2/mod.rs index 46ed13baf1..2bfa77f579 100644 --- a/quiche/src/recovery/bbr2/mod.rs +++ b/quiche/src/recovery/bbr2/mod.rs @@ -729,7 +729,7 @@ mod tests { // Send 5 packets. for pn in 0..5 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -764,6 +764,7 @@ mod tests { assert!(r .on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -798,7 +799,7 @@ mod tests { // Send 5 packets. for pn in 0..5 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -834,6 +835,7 @@ mod tests { // 2 acked, 2 x MSS lost. assert!(r .on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -870,7 +872,7 @@ mod tests { // Stop right before filled_pipe=true. for _ in 0..3 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -906,6 +908,7 @@ mod tests { assert!(r .on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -920,7 +923,7 @@ mod tests { // Stop at right before filled_pipe=true. for _ in 0..5 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -959,6 +962,7 @@ mod tests { assert!(r .on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -992,7 +996,7 @@ mod tests { // smaller than BBRInFlight(1). for _ in 0..4 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -1027,6 +1031,7 @@ mod tests { assert!(r .on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -1045,7 +1050,7 @@ mod tests { let now = now + PROBE_RTT_INTERVAL; let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -1082,6 +1087,7 @@ mod tests { assert!(r .on_ack_received( + 0, &acked, 25, packet::Epoch::Application, diff --git a/quiche/src/recovery/cubic.rs b/quiche/src/recovery/cubic.rs index 43babcbfad..b391ac4ab7 100644 --- a/quiche/src/recovery/cubic.rs +++ b/quiche/src/recovery/cubic.rs @@ -475,7 +475,7 @@ mod tests { let now = Instant::now(); let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -527,7 +527,7 @@ mod tests { let now = Instant::now(); let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -606,7 +606,7 @@ mod tests { let prev_cwnd = r.cwnd(); let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -650,7 +650,7 @@ mod tests { } let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -694,7 +694,7 @@ mod tests { // 5 ACKs to increase cwnd by 1 MSS. for _ in 0..5 { let mut acked = vec![Acked { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), time_sent: now, size: r.max_datagram_size, delivered: 0, @@ -726,7 +726,7 @@ mod tests { // Trigger congestion event to update ssthresh let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -758,7 +758,7 @@ mod tests { ); let mut acked = vec![Acked { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), // To exit from recovery time_sent: now + Duration::from_millis(1), size: r.max_datagram_size, @@ -791,7 +791,7 @@ mod tests { let epoch = packet::Epoch::Application; let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -810,18 +810,19 @@ mod tests { // 1st round. let n_rtt_sample = hystart::N_RTT_SAMPLE; - let mut send_pn = 0; - let mut ack_pn = 0; + let mut send_pn = recovery::SpacedPktNum(0, 0); + let mut ack_pn = recovery::SpacedPktNum(0, 0); let rtt_1st = Duration::from_millis(50); // Send 1st round packets. for _ in 0..n_rtt_sample { r.on_packet_sent_cc(p.size, now); - send_pn += 1; + send_pn.1 += 1; } - r.hystart.start_round(send_pn - 1); + r.hystart + .start_round(recovery::SpacedPktNum(send_pn.0, send_pn.1 - 1)); // Receiving Acks. let now = now + rtt_1st; @@ -842,7 +843,7 @@ mod tests { }]; r.on_packets_acked(&mut acked, epoch, now); - ack_pn += 1; + ack_pn.1 += 1; } // Not in CSS yet. @@ -855,9 +856,10 @@ mod tests { // Send 2nd round packets. for _ in 0..n_rtt_sample { r.on_packet_sent_cc(p.size, now); - send_pn += 1; + send_pn.1 += 1; } - r.hystart.start_round(send_pn - 1); + r.hystart + .start_round(recovery::SpacedPktNum(send_pn.0, send_pn.1 - 1)); // Receiving Acks. // Last ack will cause to exit to CSS. @@ -881,7 +883,7 @@ mod tests { }]; r.on_packets_acked(&mut acked, epoch, now); - ack_pn += 1; + ack_pn.1 += 1; // Keep increasing RTT so that hystart exits to CSS. rtt_2nd += rtt_2nd.saturating_add(Duration::from_millis(4)); @@ -900,9 +902,10 @@ mod tests { // Send 3nd round packets. for _ in 0..n_rtt_sample { r.on_packet_sent_cc(p.size, now); - send_pn += 1; + send_pn.1 += 1; } - r.hystart.start_round(send_pn - 1); + r.hystart + .start_round(recovery::SpacedPktNum(send_pn.0, send_pn.1 - 1)); // Receiving Acks. // Last ack will cause to exit to SS. @@ -923,7 +926,7 @@ mod tests { }]; r.on_packets_acked(&mut acked, epoch, now); - ack_pn += 1; + ack_pn.1 += 1; } // Now we are back in Slow Start. @@ -947,7 +950,7 @@ mod tests { let epoch = packet::Epoch::Application; let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -966,18 +969,19 @@ mod tests { // 1st round. let n_rtt_sample = hystart::N_RTT_SAMPLE; - let mut send_pn = 0; - let mut ack_pn = 0; + let mut send_pn = recovery::SpacedPktNum(0, 0); + let mut ack_pn = recovery::SpacedPktNum(0, 0); let rtt_1st = Duration::from_millis(50); // Send 1st round packets. for _ in 0..n_rtt_sample { r.on_packet_sent_cc(p.size, now); - send_pn += 1; + send_pn.1 += 1; } - r.hystart.start_round(send_pn - 1); + r.hystart + .start_round(recovery::SpacedPktNum(send_pn.0, send_pn.1 - 1)); // Receiving Acks. let now = now + rtt_1st; @@ -998,7 +1002,7 @@ mod tests { }]; r.on_packets_acked(&mut acked, epoch, now); - ack_pn += 1; + ack_pn.1 += 1; } // Not in CSS yet. @@ -1011,9 +1015,10 @@ mod tests { // Send 2nd round packets. for _ in 0..n_rtt_sample { r.on_packet_sent_cc(p.size, now); - send_pn += 1; + send_pn.1 += 1; } - r.hystart.start_round(send_pn - 1); + r.hystart + .start_round(recovery::SpacedPktNum(send_pn.0, send_pn.1 - 1)); // Receiving Acks. // Last ack will cause to exit to CSS. @@ -1037,7 +1042,7 @@ mod tests { }]; r.on_packets_acked(&mut acked, epoch, now); - ack_pn += 1; + ack_pn.1 += 1; // Keep increasing RTT so that hystart exits to CSS. rtt_2nd += rtt_2nd.saturating_add(Duration::from_millis(4)); @@ -1055,9 +1060,10 @@ mod tests { // Send a round of packets. for _ in 0..n_rtt_sample { r.on_packet_sent_cc(p.size, now); - send_pn += 1; + send_pn.1 += 1; } - r.hystart.start_round(send_pn - 1); + r.hystart + .start_round(recovery::SpacedPktNum(send_pn.0, send_pn.1 - 1)); // Receiving Acks. for _ in 0..n_rtt_sample { @@ -1077,7 +1083,7 @@ mod tests { }]; r.on_packets_acked(&mut acked, epoch, now); - ack_pn += 1; + ack_pn.1 += 1; } } @@ -1101,7 +1107,7 @@ mod tests { // Trigger congestion event to update ssthresh let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -1132,7 +1138,7 @@ mod tests { let rtt = Duration::from_millis(100); let mut acked = vec![Acked { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), // To exit from recovery time_sent: now + rtt, size: r.max_datagram_size, @@ -1162,7 +1168,7 @@ mod tests { // Trigger another congestion event. let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -1194,7 +1200,7 @@ mod tests { let rtt = Duration::from_millis(100); let mut acked = vec![Acked { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), // To exit from recovery time_sent: now + rtt, size: r.max_datagram_size, @@ -1237,7 +1243,7 @@ mod tests { // Trigger congestion event to update ssthresh let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -1279,7 +1285,7 @@ mod tests { // 5 ACKs to increase cwnd by 1 MSS. for _ in 0..5 { let mut acked = vec![Acked { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), time_sent: now, size: r.max_datagram_size, delivered: 0, @@ -1303,7 +1309,7 @@ mod tests { // cwnd is not fully recovered to w_max, w_max will be // further reduced. let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, diff --git a/quiche/src/recovery/delivery_rate.rs b/quiche/src/recovery/delivery_rate.rs index 590bac22cb..838fe9ccbe 100644 --- a/quiche/src/recovery/delivery_rate.rs +++ b/quiche/src/recovery/delivery_rate.rs @@ -34,6 +34,7 @@ use std::time::Instant; use crate::recovery::Acked; use crate::recovery::Sent; +use crate::recovery::SpacedPktNum; #[derive(Debug)] pub struct Rate { @@ -44,13 +45,13 @@ pub struct Rate { first_sent_time: Instant, // Packet number of the last sent packet with app limited. - end_of_app_limited: u64, + end_of_app_limited: SpacedPktNum, // Packet number of the last sent packet. - last_sent_packet: u64, + last_sent_packet: SpacedPktNum, // Packet number of the largest acked packet. - largest_acked: u64, + largest_acked: SpacedPktNum, // Sample of rate estimation. rate_sample: RateSample, @@ -67,11 +68,11 @@ impl Default for Rate { first_sent_time: now, - end_of_app_limited: 0, + end_of_app_limited: SpacedPktNum(0, 0), - last_sent_packet: 0, + last_sent_packet: SpacedPktNum(0, 0), - largest_acked: 0, + largest_acked: SpacedPktNum(0, 0), rate_sample: RateSample::default(), } @@ -157,11 +158,15 @@ impl Rate { } pub fn update_app_limited(&mut self, v: bool) { - self.end_of_app_limited = if v { self.last_sent_packet.max(1) } else { 0 } + self.end_of_app_limited = if v { + self.last_sent_packet.max(SpacedPktNum(0, 1)) + } else { + SpacedPktNum(0, 0) + } } pub fn app_limited(&mut self) -> bool { - self.end_of_app_limited != 0 + self.end_of_app_limited.1 != 0 } pub fn delivered(&self) -> usize { @@ -229,7 +234,7 @@ mod tests { // Send 2 packets. for pn in 0..2 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -261,7 +266,7 @@ mod tests { // Ack 2 packets. for pn in 0..2 { let acked = Acked { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), time_sent: now, size: mss, rtt, @@ -297,7 +302,7 @@ mod tests { // Send 10 packets to fill cwnd. for pn in 0..10 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -338,7 +343,7 @@ mod tests { // Send 5 packets. for pn in 0..5 { let pkt = Sent { - pkt_num: pn, + pkt_num: SpacedPktNum(0, pn), frames: smallvec![], time_sent: now, time_acked: None, @@ -372,6 +377,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, diff --git a/quiche/src/recovery/hystart.rs b/quiche/src/recovery/hystart.rs index 4d2ead57f1..b58c48231a 100644 --- a/quiche/src/recovery/hystart.rs +++ b/quiche/src/recovery/hystart.rs @@ -52,7 +52,7 @@ pub const CSS_ROUNDS: usize = 5; pub struct Hystart { enabled: bool, - window_end: Option, + window_end: Option, last_round_min_rtt: Duration, @@ -114,7 +114,7 @@ impl Hystart { self.css_start_time().is_some() } - pub fn start_round(&mut self, pkt_num: u64) { + pub fn start_round(&mut self, pkt_num: recovery::SpacedPktNum) { if self.window_end.is_none() { self.window_end = Some(pkt_num); @@ -214,7 +214,7 @@ mod tests { #[test] fn start_round() { let mut hspp = Hystart::default(); - let pkt_num = 100; + let pkt_num = recovery::SpacedPktNum(0, 100); hspp.start_round(pkt_num); @@ -235,7 +235,7 @@ mod tests { #[test] fn congestion_event() { let mut hspp = Hystart::default(); - let pkt_num = 100; + let pkt_num = recovery::SpacedPktNum(0, 100); hspp.start_round(pkt_num); diff --git a/quiche/src/recovery/mod.rs b/quiche/src/recovery/mod.rs index a7f0b35ae7..c34a0b25ca 100644 --- a/quiche/src/recovery/mod.rs +++ b/quiche/src/recovery/mod.rs @@ -73,6 +73,19 @@ const PACING_MULTIPLIER: f64 = 1.25; // an ACK. pub(super) const MAX_OUTSTANDING_NON_ACK_ELICITING: usize = 24; +pub type SpaceId = u32; +pub type PktNum = u64; + +/// A packet number preceded by its space identifier. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct SpacedPktNum(SpaceId, PktNum); + +impl SpacedPktNum { + pub fn new(space_id: SpaceId, pkt_num: PktNum) -> Self { + Self(space_id, pkt_num) + } +} + pub struct Recovery { loss_detection_timer: Option, @@ -81,9 +94,9 @@ pub struct Recovery { time_of_last_sent_ack_eliciting_pkt: [Option; packet::Epoch::count()], - largest_acked_pkt: [u64; packet::Epoch::count()], + largest_acked_pkt: [SpacedPktNum; packet::Epoch::count()], - largest_sent_pkt: [u64; packet::Epoch::count()], + largest_sent_pkt: [SpacedPktNum; packet::Epoch::count()], latest_rtt: Duration, @@ -113,6 +126,8 @@ pub struct Recovery { in_flight_count: [usize; packet::Epoch::count()], + pub rtt_update_count: usize, + app_limited: bool, delivery_rate: delivery_rate::Rate, @@ -134,7 +149,7 @@ pub struct Recovery { bytes_acked_ca: usize, - bytes_sent: usize, + pub bytes_sent: usize, pub bytes_lost: u64, @@ -210,9 +225,10 @@ impl Recovery { time_of_last_sent_ack_eliciting_pkt: [None; packet::Epoch::count()], - largest_acked_pkt: [u64::MAX; packet::Epoch::count()], + largest_acked_pkt: [SpacedPktNum(SpaceId::MAX, PktNum::MAX); + packet::Epoch::count()], - largest_sent_pkt: [0; packet::Epoch::count()], + largest_sent_pkt: [SpacedPktNum(0, 0); packet::Epoch::count()], latest_rtt: Duration::ZERO, @@ -253,6 +269,8 @@ impl Recovery { bytes_in_flight: 0, + rtt_update_count: 0, + ssthresh: usize::MAX, bytes_acked_sl: 0, @@ -436,11 +454,11 @@ impl Recovery { #[allow(clippy::too_many_arguments)] pub fn on_ack_received( - &mut self, ranges: &ranges::RangeSet, ack_delay: u64, + &mut self, space_id: SpaceId, ranges: &ranges::RangeSet, ack_delay: u64, epoch: packet::Epoch, handshake_status: HandshakeStatus, now: Instant, trace_id: &str, newly_acked: &mut Vec, ) -> Result<(usize, usize)> { - let largest_acked = ranges.last().unwrap(); + let largest_acked = SpacedPktNum(space_id, ranges.last().unwrap()); // While quiche used to consider ACK frames acknowledging packet numbers // larger than the largest sent one as invalid, this is not true anymore @@ -449,7 +467,9 @@ impl Recovery { // a validating path, then receives an acknowledgment for that packet on // the active one. - if self.largest_acked_pkt[epoch] == u64::MAX { + if self.largest_acked_pkt[epoch] == + SpacedPktNum(SpaceId::MAX, PktNum::MAX) + { self.largest_acked_pkt[epoch] = largest_acked; } else { self.largest_acked_pkt[epoch] = @@ -458,7 +478,7 @@ impl Recovery { let mut has_ack_eliciting = false; - let mut largest_newly_acked_pkt_num = 0; + let mut largest_newly_acked_pkt_num = SpacedPktNum(0, 0); let mut largest_newly_acked_sent_time = now; let mut undo_cwnd = false; @@ -470,8 +490,8 @@ impl Recovery { // Detect and mark acked packets, without removing them from the sent // packets list. for r in ranges.iter() { - let lowest_acked_in_block = r.start; - let largest_acked_in_block = r.end - 1; + let lowest_acked_in_block = SpacedPktNum(space_id, r.start); + let largest_acked_in_block = SpacedPktNum(space_id, r.end - 1); let first_unacked = if sent .front() @@ -501,7 +521,7 @@ impl Recovery { if unacked.time_lost.is_some() { // Calculate new packet reordering threshold. let pkt_thresh = - self.largest_acked_pkt[epoch] - unacked.pkt_num + 1; + self.largest_acked_pkt[epoch].1 - unacked.pkt_num.1 + 1; let pkt_thresh = cmp::min(MAX_PACKET_THRESHOLD, pkt_thresh); self.pkt_thresh = cmp::max(self.pkt_thresh, pkt_thresh); @@ -562,7 +582,7 @@ impl Recovery { lost: unacked.lost, }); - trace!("{} packet newly acked {}", trace_id, unacked.pkt_num); + trace!("{} packet newly acked {:?}", trace_id, unacked.pkt_num); } } @@ -779,6 +799,7 @@ impl Recovery { fn update_rtt( &mut self, latest_rtt: Duration, ack_delay: Duration, now: Instant, ) { + self.rtt_update_count += 1; self.latest_rtt = latest_rtt; match self.smoothed_rtt { @@ -901,6 +922,52 @@ impl Recovery { self.loss_detection_timer = timeout; } + pub fn mark_all_inflight_as_lost( + &mut self, now: Instant, trace_id: &str, + ) -> (usize, usize) { + let mut lost_packets = 0; + let mut lost_bytes = 0; + for &e in packet::Epoch::epochs( + packet::Epoch::Initial..=packet::Epoch::Application, + ) { + let mut epoch_lost_bytes = 0; + let mut largest_lost_pkt = None; + for sent in self.sent[e].drain(..) { + if sent.time_acked.is_none() { + self.lost[e].extend_from_slice(&sent.frames); + if sent.in_flight { + epoch_lost_bytes += sent.size; + + self.in_flight_count[e] = + self.in_flight_count[e].saturating_sub(1); + + trace!( + "{} packet {:?} lost on epoch {}", + trace_id, + sent.pkt_num, + e + ); + + // Frames have already been removed from the packet. + largest_lost_pkt = Some(sent); + } + + lost_packets += 1; + self.lost_count += 1; + } + } + + self.bytes_lost += epoch_lost_bytes as u64; + lost_bytes += epoch_lost_bytes; + + if let Some(pkt) = largest_lost_pkt { + self.on_packets_lost(lost_bytes, &pkt, e, now); + } + } + + (lost_packets, lost_bytes) + } + fn detect_lost_packets( &mut self, epoch: packet::Epoch, now: Instant, trace_id: &str, ) -> (usize, usize) { @@ -932,7 +999,7 @@ impl Recovery { for unacked in unacked_iter { // Mark packet as lost, or set time when it should be marked. if unacked.time_sent <= lost_send_time || - largest_acked >= unacked.pkt_num + self.pkt_thresh + largest_acked.1 >= unacked.pkt_num.1 + self.pkt_thresh { self.lost[epoch].extend(unacked.frames.drain(..)); @@ -949,7 +1016,7 @@ impl Recovery { self.in_flight_count[epoch].saturating_sub(1); trace!( - "{} packet {} lost on epoch {}", + "{} packet {:?} lost on epoch {}", trace_id, unacked.pkt_num, epoch @@ -1037,7 +1104,9 @@ impl Recovery { } } - fn in_persistent_congestion(&mut self, _largest_lost_pkt_num: u64) -> bool { + fn in_persistent_congestion( + &mut self, _largest_lost_pkt_num: SpacedPktNum, + ) -> bool { let _congestion_period = self.pto() * PERSISTENT_CONGESTION_THRESHOLD; // TODO: properly detect persistent congestion @@ -1243,7 +1312,7 @@ impl std::fmt::Debug for Recovery { #[derive(Clone)] pub struct Sent { - pub pkt_num: u64, + pub pkt_num: SpacedPktNum, pub frames: SmallVec<[frame::Frame; 1]>, @@ -1286,6 +1355,8 @@ impl std::fmt::Debug for Sent { write!(f, "tx_in_flight={} ", self.tx_in_flight)?; write!(f, "lost={} ", self.lost)?; write!(f, "has_data={} ", self.has_data)?; + write!(f, "time_acked={:?} ", self.time_acked)?; + write!(f, "time_lost={:?} ", self.time_lost)?; Ok(()) } @@ -1293,7 +1364,7 @@ impl std::fmt::Debug for Sent { #[derive(Clone)] pub struct Acked { - pub pkt_num: u64, + pub pkt_num: SpacedPktNum, pub time_sent: Instant, @@ -1502,7 +1573,7 @@ mod tests { // Start by sending a few packets. let p = Sent { - pkt_num: 0, + pkt_num: SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -1530,7 +1601,7 @@ mod tests { assert_eq!(r.bytes_in_flight, 1000); let p = Sent { - pkt_num: 1, + pkt_num: SpacedPktNum(0, 1), frames: smallvec![], time_sent: now, time_acked: None, @@ -1558,7 +1629,7 @@ mod tests { assert_eq!(r.bytes_in_flight, 2000); let p = Sent { - pkt_num: 2, + pkt_num: SpacedPktNum(0, 2), frames: smallvec![], time_sent: now, time_acked: None, @@ -1586,7 +1657,7 @@ mod tests { assert_eq!(r.bytes_in_flight, 3000); let p = Sent { - pkt_num: 3, + pkt_num: SpacedPktNum(0, 3), frames: smallvec![], time_sent: now, time_acked: None, @@ -1622,6 +1693,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -1647,7 +1719,7 @@ mod tests { assert_eq!(r.pto_count, 1); let p = Sent { - pkt_num: 4, + pkt_num: SpacedPktNum(0, 4), frames: smallvec![], time_sent: now, time_acked: None, @@ -1675,7 +1747,7 @@ mod tests { assert_eq!(r.bytes_in_flight, 3000); let p = Sent { - pkt_num: 5, + pkt_num: SpacedPktNum(0, 5), frames: smallvec![], time_sent: now, time_acked: None, @@ -1712,6 +1784,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -1749,7 +1822,7 @@ mod tests { // Start by sending a few packets. let p = Sent { - pkt_num: 0, + pkt_num: SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -1777,7 +1850,7 @@ mod tests { assert_eq!(r.bytes_in_flight, 1000); let p = Sent { - pkt_num: 1, + pkt_num: SpacedPktNum(0, 1), frames: smallvec![], time_sent: now, time_acked: None, @@ -1805,7 +1878,7 @@ mod tests { assert_eq!(r.bytes_in_flight, 2000); let p = Sent { - pkt_num: 2, + pkt_num: SpacedPktNum(0, 2), frames: smallvec![], time_sent: now, time_acked: None, @@ -1833,7 +1906,7 @@ mod tests { assert_eq!(r.bytes_in_flight, 3000); let p = Sent { - pkt_num: 3, + pkt_num: SpacedPktNum(0, 3), frames: smallvec![], time_sent: now, time_acked: None, @@ -1870,6 +1943,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -1918,7 +1992,7 @@ mod tests { // Start by sending a few packets. let p = Sent { - pkt_num: 0, + pkt_num: SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -1946,7 +2020,7 @@ mod tests { assert_eq!(r.bytes_in_flight, 1000); let p = Sent { - pkt_num: 1, + pkt_num: SpacedPktNum(0, 1), frames: smallvec![], time_sent: now, time_acked: None, @@ -1974,7 +2048,7 @@ mod tests { assert_eq!(r.bytes_in_flight, 2000); let p = Sent { - pkt_num: 2, + pkt_num: SpacedPktNum(0, 2), frames: smallvec![], time_sent: now, time_acked: None, @@ -2002,7 +2076,7 @@ mod tests { assert_eq!(r.bytes_in_flight, 3000); let p = Sent { - pkt_num: 3, + pkt_num: SpacedPktNum(0, 3), frames: smallvec![], time_sent: now, time_acked: None, @@ -2038,6 +2112,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -2049,7 +2124,7 @@ mod tests { Ok((1, 1000)) ); - now += Duration::from_millis(10); + now += Duration::from_millis(9); let mut acked = ranges::RangeSet::default(); acked.insert(0..2); @@ -2058,6 +2133,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 25, packet::Epoch::Application, @@ -2100,7 +2176,7 @@ mod tests { // send out first packet (a full initcwnd). let p = Sent { - pkt_num: 0, + pkt_num: SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -2140,6 +2216,7 @@ mod tests { assert_eq!( r.on_ack_received( + 0, &acked, 10, packet::Epoch::Application, @@ -2160,7 +2237,7 @@ mod tests { // Send out second packet. let p = Sent { - pkt_num: 1, + pkt_num: SpacedPktNum(0, 1), frames: smallvec![], time_sent: now, time_acked: None, @@ -2193,7 +2270,7 @@ mod tests { // Send the third packet out. let p = Sent { - pkt_num: 2, + pkt_num: SpacedPktNum(0, 2), frames: smallvec![], time_sent: now, time_acked: None, @@ -2223,7 +2300,7 @@ mod tests { // Send the third packet out. let p = Sent { - pkt_num: 3, + pkt_num: SpacedPktNum(0, 3), frames: smallvec![], time_sent: now, time_acked: None, @@ -2262,6 +2339,25 @@ mod tests { now + Duration::from_secs_f64(12000.0 / pacing_rate as f64) ); } + + #[test] + fn spaced_pkt_num_ordering() { + // In same space. + let a = SpacedPktNum(42, 42); + let b = SpacedPktNum(42, 120); + let c = SpacedPktNum(42, 10); + + assert_eq!(std::cmp::max(a, b), b); + assert_eq!(std::cmp::max(a, c), a); + + // Different spaces. + let d = SpacedPktNum(42, 9999999); + let e = SpacedPktNum(43, 1); + let f = SpacedPktNum(9999, 2); + + assert_eq!(std::cmp::max(d, e), e); + assert_eq!(std::cmp::max(e, f), f); + } } mod bbr; diff --git a/quiche/src/recovery/reno.rs b/quiche/src/recovery/reno.rs index c3e93dee18..40ef2b3f46 100644 --- a/quiche/src/recovery/reno.rs +++ b/quiche/src/recovery/reno.rs @@ -205,7 +205,7 @@ mod tests { let now = Instant::now(); let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -258,7 +258,7 @@ mod tests { let now = Instant::now(); let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -339,7 +339,7 @@ mod tests { let now = Instant::now(); let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -380,7 +380,7 @@ mod tests { r.on_packet_sent_cc(20000, now); let p = recovery::Sent { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), frames: smallvec![], time_sent: now, time_acked: None, @@ -413,7 +413,7 @@ mod tests { let rtt = Duration::from_millis(100); let mut acked = vec![Acked { - pkt_num: 0, + pkt_num: recovery::SpacedPktNum(0, 0), // To exit from recovery time_sent: now + rtt, // More than cur_cwnd to increase cwnd diff --git a/quiche/src/stream/mod.rs b/quiche/src/stream/mod.rs index 1267d43cc4..0f1cbcec1e 100644 --- a/quiche/src/stream/mod.rs +++ b/quiche/src/stream/mod.rs @@ -711,7 +711,11 @@ impl Stream { /// Returns true if the stream has data to send and is allowed to send at /// least some of it. pub fn is_flushable(&self) -> bool { - self.send.ready() && self.send.off_front() < self.send.max_off() + let off_front = self.send.off_front(); + + !self.send.is_empty() && + off_front < self.send.off_back() && + off_front < self.send.max_off() } /// Returns true if the stream is complete. @@ -784,7 +788,7 @@ impl Eq for StreamPriorityKey {} impl PartialOrd for StreamPriorityKey { // Priority ordering is complex, disable Clippy warning. - #[allow(clippy::incorrect_partial_ord_impl_on_ord_type)] + #[allow(clippy::non_canonical_partial_ord_impl)] fn partial_cmp(&self, other: &Self) -> Option { // Ignore priority if ID matches. if self.id == other.id { @@ -1454,6 +1458,11 @@ mod tests { assert_eq!(&buf[..written], b""); } + fn stream_send_ready(stream: &Stream) -> bool { + !stream.send.is_empty() && + stream.send.off_front() < stream.send.off_back() + } + #[test] fn send_emit() { let mut buf = [0; 5]; @@ -1469,39 +1478,39 @@ mod tests { assert!(stream.is_flushable()); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false))); assert_eq!(stream.send.off_front(), 4); assert_eq!(&buf[..4], b"hell"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false))); assert_eq!(stream.send.off_front(), 8); assert_eq!(&buf[..4], b"owor"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..2]), Ok((2, false))); assert_eq!(stream.send.off_front(), 10); assert_eq!(&buf[..2], b"ld"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..1]), Ok((1, false))); assert_eq!(stream.send.off_front(), 11); assert_eq!(&buf[..1], b"o"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false))); assert_eq!(stream.send.off_front(), 16); assert_eq!(&buf[..5], b"llehd"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((4, true))); assert_eq!(stream.send.off_front(), 20); assert_eq!(&buf[..4], b"lrow"); assert!(!stream.is_flushable()); - assert!(!stream.send.ready()); + assert!(!stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((0, true))); assert_eq!(stream.send.off_front(), 20); } @@ -1521,12 +1530,12 @@ mod tests { assert!(stream.is_flushable()); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false))); assert_eq!(stream.send.off_front(), 4); assert_eq!(&buf[..4], b"hell"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false))); assert_eq!(stream.send.off_front(), 8); assert_eq!(&buf[..4], b"owor"); @@ -1534,7 +1543,7 @@ mod tests { stream.send.ack_and_drop(0, 5); assert_eq!(stream.send.bufs_count(), 3); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..2]), Ok((2, false))); assert_eq!(stream.send.off_front(), 10); assert_eq!(&buf[..2], b"ld"); @@ -1542,12 +1551,12 @@ mod tests { stream.send.ack_and_drop(7, 5); assert_eq!(stream.send.bufs_count(), 3); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..1]), Ok((1, false))); assert_eq!(stream.send.off_front(), 11); assert_eq!(&buf[..1], b"o"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false))); assert_eq!(stream.send.off_front(), 16); assert_eq!(&buf[..5], b"llehd"); @@ -1555,14 +1564,14 @@ mod tests { stream.send.ack_and_drop(5, 7); assert_eq!(stream.send.bufs_count(), 2); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((4, true))); assert_eq!(stream.send.off_front(), 20); assert_eq!(&buf[..4], b"lrow"); assert!(!stream.is_flushable()); - assert!(!stream.send.ready()); + assert!(!stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((0, true))); assert_eq!(stream.send.off_front(), 20); @@ -1588,12 +1597,12 @@ mod tests { assert!(stream.is_flushable()); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false))); assert_eq!(stream.send.off_front(), 4); assert_eq!(&buf[..4], b"hell"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false))); assert_eq!(stream.send.off_front(), 8); assert_eq!(&buf[..4], b"owor"); @@ -1601,12 +1610,12 @@ mod tests { stream.send.retransmit(3, 3); assert_eq!(stream.send.off_front(), 3); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..3]), Ok((3, false))); assert_eq!(stream.send.off_front(), 8); assert_eq!(&buf[..3], b"low"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..2]), Ok((2, false))); assert_eq!(stream.send.off_front(), 10); assert_eq!(&buf[..2], b"ld"); @@ -1615,52 +1624,52 @@ mod tests { stream.send.retransmit(8, 2); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..2]), Ok((2, false))); assert_eq!(stream.send.off_front(), 10); assert_eq!(&buf[..2], b"ld"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..1]), Ok((1, false))); assert_eq!(stream.send.off_front(), 11); assert_eq!(&buf[..1], b"o"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false))); assert_eq!(stream.send.off_front(), 16); assert_eq!(&buf[..5], b"llehd"); stream.send.retransmit(12, 2); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..2]), Ok((2, false))); assert_eq!(stream.send.off_front(), 16); assert_eq!(&buf[..2], b"le"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((4, true))); assert_eq!(stream.send.off_front(), 20); assert_eq!(&buf[..4], b"lrow"); assert!(!stream.is_flushable()); - assert!(!stream.send.ready()); + assert!(!stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((0, true))); assert_eq!(stream.send.off_front(), 20); stream.send.retransmit(7, 12); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false))); assert_eq!(stream.send.off_front(), 12); assert_eq!(&buf[..5], b"rldol"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false))); assert_eq!(stream.send.off_front(), 17); assert_eq!(&buf[..5], b"lehdl"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((2, false))); assert_eq!(stream.send.off_front(), 20); assert_eq!(&buf[..2], b"ro"); @@ -1669,17 +1678,17 @@ mod tests { stream.send.retransmit(7, 12); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false))); assert_eq!(stream.send.off_front(), 12); assert_eq!(&buf[..5], b"rldol"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false))); assert_eq!(stream.send.off_front(), 17); assert_eq!(&buf[..5], b"lehdl"); - assert!(stream.send.ready()); + assert!(stream_send_ready(&stream)); assert_eq!(stream.send.emit(&mut buf[..5]), Ok((2, false))); assert_eq!(stream.send.off_front(), 20); assert_eq!(&buf[..2], b"ro"); diff --git a/quiche/src/stream/send_buf.rs b/quiche/src/stream/send_buf.rs index f0688157af..a9bcc6c243 100644 --- a/quiche/src/stream/send_buf.rs +++ b/quiche/src/stream/send_buf.rs @@ -172,11 +172,17 @@ impl SendBuf { let mut next_off = out_off; - while out_len > 0 && - self.ready() && - self.off_front() == next_off && - self.off_front() < self.max_data - { + while out_len > 0 { + let off_front = self.off_front(); + + if self.is_empty() || + off_front >= self.off || + off_front != next_off || + off_front >= self.max_data + { + break; + } + let buf = match self.data.get_mut(self.pos) { Some(v) => v, @@ -448,9 +454,9 @@ impl SendBuf { self.shutdown } - /// Returns true if there is data to be written. - pub fn ready(&self) -> bool { - !self.data.is_empty() && self.off_front() < self.off + /// Returns true if there is no data. + pub fn is_empty(&self) -> bool { + self.data.is_empty() } /// Returns the highest contiguously acked offset. diff --git a/quiche/src/tls.rs b/quiche/src/tls.rs index 05f27b8b57..48f1886e72 100644 --- a/quiche/src/tls.rs +++ b/quiche/src/tls.rs @@ -53,49 +53,71 @@ const INTERNAL_ERROR: u64 = 0x01; #[allow(non_camel_case_types)] #[repr(transparent)] -struct SSL_METHOD(c_void); +struct SSL_METHOD { + _unused: c_void, +} #[allow(non_camel_case_types)] #[repr(transparent)] -struct SSL_CTX(c_void); +struct SSL_CTX { + _unused: c_void, +} #[allow(non_camel_case_types)] #[repr(transparent)] -struct SSL(c_void); +struct SSL { + _unused: c_void, +} #[allow(non_camel_case_types)] #[repr(transparent)] -struct SSL_CIPHER(c_void); +struct SSL_CIPHER { + _unused: c_void, +} #[allow(non_camel_case_types)] #[repr(transparent)] -struct SSL_SESSION(c_void); +struct SSL_SESSION { + _unused: c_void, +} #[allow(non_camel_case_types)] #[repr(transparent)] -struct X509_VERIFY_PARAM(c_void); +struct X509_VERIFY_PARAM { + _unused: c_void, +} #[allow(non_camel_case_types)] #[repr(transparent)] #[cfg(windows)] -struct X509_STORE(c_void); +struct X509_STORE { + _unused: c_void, +} #[allow(non_camel_case_types)] #[repr(transparent)] -struct X509_STORE_CTX(c_void); +struct X509_STORE_CTX { + _unused: c_void, +} #[allow(non_camel_case_types)] #[repr(transparent)] #[cfg(windows)] -struct X509(c_void); +struct X509 { + _unused: c_void, +} #[allow(non_camel_case_types)] #[repr(transparent)] -struct STACK_OF(c_void); +struct STACK_OF { + _unused: c_void, +} #[allow(non_camel_case_types)] #[repr(transparent)] -struct CRYPTO_BUFFER(c_void); +struct CRYPTO_BUFFER { + _unused: c_void, +} #[repr(C)] #[allow(non_camel_case_types)] @@ -898,6 +920,13 @@ impl Handshake { } } } + + #[cfg(feature = "boringssl-boring-crate")] + pub(crate) fn ssl_mut(&mut self) -> &mut boring::ssl::SslRef { + use foreign_types_shared::ForeignTypeRef; + + unsafe { boring::ssl::SslRef::from_ptr_mut(self.as_mut_ptr() as _) } + } } // NOTE: These traits are not automatically implemented for Handshake due to the @@ -917,7 +946,7 @@ impl Drop for Handshake { pub struct ExData<'a> { pub application_protos: &'a Vec>, - pub pkt_num_spaces: &'a mut [packet::PktNumSpace; packet::Epoch::count()], + pub pkt_num_spaces: &'a mut packet::PktNumSpaceMap, pub session: &'a mut Option>, @@ -964,14 +993,22 @@ extern fn set_read_secret( trace!("{} set read secret lvl={:?}", ex_data.trace_id, level); let space = match level { - crypto::Level::Initial => - &mut ex_data.pkt_num_spaces[packet::Epoch::Initial], - crypto::Level::ZeroRTT => - &mut ex_data.pkt_num_spaces[packet::Epoch::Application], - crypto::Level::Handshake => - &mut ex_data.pkt_num_spaces[packet::Epoch::Handshake], - crypto::Level::OneRTT => - &mut ex_data.pkt_num_spaces[packet::Epoch::Application], + crypto::Level::Initial => ex_data + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Initial), + crypto::Level::ZeroRTT => ex_data + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Application), + crypto::Level::Handshake => ex_data + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Handshake), + crypto::Level::OneRTT => ex_data + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Application), }; let aead = match get_cipher_from_ptr(cipher) { @@ -984,7 +1021,7 @@ extern fn set_read_secret( if level != crypto::Level::ZeroRTT || ex_data.is_server { let secret = unsafe { slice::from_raw_parts(secret, secret_len) }; - let open = match crypto::Open::from_secret(aead, secret) { + let open = match crypto::Open::from_secret(aead, secret.to_vec()) { Ok(v) => v, Err(_) => return 0, @@ -1015,14 +1052,22 @@ extern fn set_write_secret( trace!("{} set write secret lvl={:?}", ex_data.trace_id, level); let space = match level { - crypto::Level::Initial => - &mut ex_data.pkt_num_spaces[packet::Epoch::Initial], - crypto::Level::ZeroRTT => - &mut ex_data.pkt_num_spaces[packet::Epoch::Application], - crypto::Level::Handshake => - &mut ex_data.pkt_num_spaces[packet::Epoch::Handshake], - crypto::Level::OneRTT => - &mut ex_data.pkt_num_spaces[packet::Epoch::Application], + crypto::Level::Initial => ex_data + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Initial), + crypto::Level::ZeroRTT => ex_data + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Application), + crypto::Level::Handshake => ex_data + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Handshake), + crypto::Level::OneRTT => ex_data + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Application), }; let aead = match get_cipher_from_ptr(cipher) { @@ -1035,7 +1080,7 @@ extern fn set_write_secret( if level != crypto::Level::ZeroRTT || !ex_data.is_server { let secret = unsafe { slice::from_raw_parts(secret, secret_len) }; - let seal = match crypto::Seal::from_secret(aead, secret) { + let seal = match crypto::Seal::from_secret(aead, secret.to_vec()) { Ok(v) => v, Err(_) => return 0, @@ -1067,13 +1112,19 @@ extern fn add_handshake_data( let buf = unsafe { slice::from_raw_parts(data, len) }; let space = match level { - crypto::Level::Initial => - &mut ex_data.pkt_num_spaces[packet::Epoch::Initial], + crypto::Level::Initial => ex_data + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Initial), crypto::Level::ZeroRTT => unreachable!(), - crypto::Level::Handshake => - &mut ex_data.pkt_num_spaces[packet::Epoch::Handshake], - crypto::Level::OneRTT => - &mut ex_data.pkt_num_spaces[packet::Epoch::Application], + crypto::Level::Handshake => ex_data + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Handshake), + crypto::Level::OneRTT => ex_data + .pkt_num_spaces + .crypto + .get_mut(packet::Epoch::Application), }; if space.crypto_stream.send.write(buf, false).is_err() { diff --git a/tools/http3_test/Cargo.toml b/tools/http3_test/Cargo.toml index ee59ff0876..1fbb6d7755 100644 --- a/tools/http3_test/Cargo.toml +++ b/tools/http3_test/Cargo.toml @@ -10,7 +10,7 @@ test_resets = [] [dependencies] docopt = "1" -env_logger = "0.6" +env_logger = "0.10" mio = { version = "0.8", features = ["net", "os-poll"] } url = "1" log = "0.4" diff --git a/tools/http3_test/tests/httpbin_tests.rs b/tools/http3_test/tests/httpbin_tests.rs index 7bd45957c0..9ed36472f0 100644 --- a/tools/http3_test/tests/httpbin_tests.rs +++ b/tools/http3_test/tests/httpbin_tests.rs @@ -70,27 +70,14 @@ mod httpbin_tests { fn verify_peer() -> bool { match std::env::var_os("VERIFY_PEER") { - Some(val) => match val.to_str().unwrap() { - "false" => { - return false; - }, - - _ => { - return true; - }, - }, - - None => { - return true; - }, - }; + Some(val) => !matches!(val.to_str().unwrap(), "false"), + None => true, + } } fn idle_timeout() -> u64 { match std::env::var_os("IDLE_TIMEOUT") { - Some(val) => - u64::from_str_radix(&val.into_string().unwrap(), 10).unwrap(), - + Some(val) => val.into_string().unwrap().parse::().unwrap(), None => 60000, } } @@ -103,7 +90,7 @@ mod httpbin_tests { return Some(parsed.as_object().unwrap().clone()); } - return None; + None } fn expect_req_headers() -> Option> @@ -115,42 +102,25 @@ mod httpbin_tests { return Some(parsed.as_object().unwrap().clone()); } - return None; + None } fn max_data() -> u64 { match std::env::var_os("MAX_DATA") { - Some(val) => - u64::from_str_radix(&val.into_string().unwrap(), 10).unwrap(), - + Some(val) => val.into_string().unwrap().parse::().unwrap(), None => 1_000_000, } } fn early_data() -> bool { match std::env::var_os("EARLY_DATA") { - Some(val) => match val.to_str().unwrap() { - "true" => { - return true; - }, - - _ => { - return false; - }, - }, - - None => { - return false; - }, - }; + Some(val) => matches!(val.to_str().unwrap(), "true"), + None => false, + } } fn session_file() -> Option { - if let Some(val) = std::env::var_os("SESSION_FILE") { - Some(val.into_string().unwrap()) - } else { - None - } + std::env::var_os("SESSION_FILE").map(|val| val.into_string().unwrap()) } // A rudimentary structure to hold httpbin response data @@ -175,17 +145,13 @@ mod httpbin_tests { } fn jsonify(data: &[u8]) -> HttpBinResponseBody { - serde_json::from_slice(&data).unwrap() + serde_json::from_slice(data).unwrap() } fn do_test( reqs: Vec, assert: Http3Assert, concurrent: bool, ) -> std::result::Result<(), Http3TestError> { - INIT.call_once(|| { - env_logger::builder() - .default_format_timestamp_nanos(true) - .init() - }); + INIT.call_once(|| env_logger::builder().format_timestamp_nanos().init()); let mut test = Http3Test::new(endpoint(None), reqs, assert, concurrent); runner::run( @@ -203,11 +169,7 @@ mod httpbin_tests { reqs: Vec, stream_data: Vec, assert: Http3Assert, concurrent: bool, ) -> std::result::Result<(), Http3TestError> { - INIT.call_once(|| { - env_logger::builder() - .default_format_timestamp_nanos(true) - .init() - }); + INIT.call_once(|| env_logger::builder().format_timestamp_nanos().init()); let mut test = Http3Test::with_stream_data( endpoint(None), @@ -1248,7 +1210,7 @@ mod httpbin_tests { for size in &sizes { let expect_hdrs = Some(vec![Header::new(b":status", b"200")]); - let testpoint = format!("{}/{}", "stream", size.to_string()); + let testpoint = format!("{}/{}", "stream", size); let url = endpoint(Some(&testpoint)); @@ -1418,7 +1380,7 @@ mod httpbin_tests { Header::new(b"content-length", size.to_string().as_bytes()), ]); - let testpoint = format!("{}/{}", "bytes", size.to_string()); + let testpoint = format!("{}/{}", "bytes", size); let url = endpoint(Some(&testpoint)); reqs.push(Http3Req::new("GET", &url, None, expect_hdrs)); @@ -1436,7 +1398,7 @@ mod httpbin_tests { for size in &sizes { let expect_hdrs = Some(vec![Header::new(b":status", b"200")]); - let testpoint = format!("{}/{}", "stream-bytes", size.to_string()); + let testpoint = format!("{}/{}", "stream-bytes", size); let url = endpoint(Some(&testpoint)); reqs.push(Http3Req::new("GET", &url, None, expect_hdrs)); @@ -1493,7 +1455,7 @@ mod httpbin_tests { format }; - let url = endpoint(Some(&testpoint)); + let url = endpoint(Some(testpoint)); let mut req = Http3Req::new("GET", &url, None, expect_hdrs); req.hdrs.push(Header::new(b"accept", format.as_bytes())); reqs.push(req);