diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a6f7205..2527d0a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v0.15.0] - 2024-07-18 + +### Added +- Support building for the `aarch64-apple-ios-sim` target +- Support customized connection id generators +- Add `quic_packet_header_info()` to extract cid-related info from quic packets +- Add `quic_conn_path_stats` to get path level stats +- Add configuration for pacing granularity +- Tweak packet number encoding + +### Fixed +- Replace the hashlru crate with the lru crate + + ## [v0.14.0] - 2024-07-11 ### Added @@ -262,6 +276,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Provide example clients and servers. +[v0.15.0]: https://github.com/tencent/tquic/compare/v0.14.0...v0.15.0 [v0.14.0]: https://github.com/tencent/tquic/compare/v0.13.0...v0.14.0 [v0.13.0]: https://github.com/tencent/tquic/compare/v0.12.0...v0.13.0 [v0.12.0]: https://github.com/tencent/tquic/compare/v0.11.0...v0.12.0 diff --git a/Cargo.toml b/Cargo.toml index cc22db06..9b0159e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tquic" -version = "0.14.0" +version = "0.15.0" edition = "2021" rust-version = "1.70.0" license = "Apache-2.0" @@ -42,7 +42,7 @@ strum = "0.24" strum_macros = "0.24" rand = "0.8.5" smallvec = { version = "1.10", features = ["serde", "union"] } -hashlru = "0.11" +lru = "0.12" serde = { version = "1.0.139", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } serde_derive = "1.0" diff --git a/cbindgen.toml b/cbindgen.toml index d9f8670b..54751f03 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -19,7 +19,7 @@ sys_includes = ["sys/socket.h", "sys/types.h"] includes = ["openssl/ssl.h", "tquic_def.h"] [export] -exclude = ["MAX_CID_LEN", "MIN_CLIENT_INITIAL_LEN", "VINT_MAX"] +exclude = ["MIN_CLIENT_INITIAL_LEN", "VINT_MAX"] [export.rename] "Config" = "quic_config_t" diff --git a/include/tquic.h b/include/tquic.h index f2eebfd6..cc19923c 100644 --- a/include/tquic.h +++ b/include/tquic.h @@ -22,6 +22,12 @@ */ #define QUIC_VERSION_V1 1 +/** + * The Connection ID MUST NOT exceed 20 bytes in QUIC version 1. + * See RFC 9000 Section 17.2 + */ +#define MAX_CID_LEN 20 + /** * Available congestion control algorithms. */ @@ -224,6 +230,34 @@ typedef struct quic_packet_send_methods_t { typedef void *quic_packet_send_context_t; +/** + * Connection Id is an identifier used to identify a QUIC connection + * at an endpoint. + */ +typedef struct ConnectionId { + /** + * length of cid + */ + uint8_t len; + /** + * octets of cid + */ + uint8_t data[MAX_CID_LEN]; +} ConnectionId; + +typedef struct ConnectionIdGeneratorMethods { + /** + * Generate a new CID + */ + struct ConnectionId (*generate)(void *gctx); + /** + * Return the length of a CID + */ + uint8_t (*cid_len)(void *gctx); +} ConnectionIdGeneratorMethods; + +typedef void *ConnectionIdGeneratorContext; + /** * Meta information of an incoming packet. */ @@ -241,6 +275,100 @@ typedef struct quic_path_address_t { socklen_t remote_addr_len; } quic_path_address_t; +/** + * Statistics about path + */ +typedef struct PathStats { + /** + * The number of QUIC packets received. + */ + uint64_t recv_count; + /** + * The number of received bytes. + */ + uint64_t recv_bytes; + /** + * The number of QUIC packets sent. + */ + uint64_t sent_count; + /** + * The number of sent bytes. + */ + uint64_t sent_bytes; + /** + * The number of QUIC packets lost. + */ + uint64_t lost_count; + /** + * The number of lost bytes. + */ + uint64_t lost_bytes; + /** + * Total number of bytes acked. + */ + uint64_t acked_bytes; + /** + * Total number of packets acked. + */ + uint64_t acked_count; + /** + * Initial congestion window in bytes. + */ + uint64_t init_cwnd; + /** + * Final congestion window in bytes. + */ + uint64_t final_cwnd; + /** + * Maximum congestion window in bytes. + */ + uint64_t max_cwnd; + /** + * Minimum congestion window in bytes. + */ + uint64_t min_cwnd; + /** + * Maximum inflight data in bytes. + */ + uint64_t max_inflight; + /** + * Total loss events. + */ + uint64_t loss_event_count; + /** + * Total congestion window limited events. + */ + uint64_t cwnd_limited_count; + /** + * Total duration of congestion windowlimited events in microseconds. + */ + uint64_t cwnd_limited_duration; + /** + * Minimum roundtrip time in microseconds. + */ + uint64_t min_rtt; + /** + * Maximum roundtrip time in microseconds. + */ + uint64_t max_rtt; + /** + * Smoothed roundtrip time in microseconds. + */ + uint64_t srtt; + /** + * Roundtrip time variation in microseconds. + */ + uint64_t rttvar; + /** + * Whether the congestion controller is in slow start status. + */ + bool in_slow_start; + /** + * Pacing rate estimated by congestion control algorithm. + */ + uint64_t pacing_rate; +} PathStats; + /** * Statistics about a QUIC connection. */ @@ -478,6 +606,18 @@ void quic_config_set_bbr_probe_bw_cwnd_gain(struct quic_config_t *config, double */ void quic_config_set_initial_rtt(struct quic_config_t *config, uint64_t v); +/** + * Enable pacing to smooth the flow of packets sent onto the network. + * The default value is true. + */ +void quic_config_enable_pacing(struct quic_config_t *config, bool v); + +/** + * Set clock granularity used by the pacer. + * The default value is 10 milliseconds. + */ +void quic_config_set_pacing_granularity(struct quic_config_t *config, uint64_t v); + /** * Set the linear factor for calculating the probe timeout. * The endpoint do not backoff the first `v` consecutive probe timeouts. @@ -581,6 +721,7 @@ void quic_config_set_send_batch_size(struct quic_config_t *config, uint16_t v); /** * Set the buffer size for disordered zerortt packets on the server. + * The default value is `1000`. A value of 0 will be treated as default value. * Applicable to Server only. */ void quic_config_set_zerortt_buffer_size(struct quic_config_t *config, uint16_t v); @@ -707,6 +848,14 @@ struct quic_endpoint_t *quic_endpoint_new(struct quic_config_t *config, */ void quic_endpoint_free(struct quic_endpoint_t *endpoint); +/** + * Set the connection id generator for the endpoint. + * By default, the random connection id generator is used. + */ +void quic_endpoint_set_cid_generator(struct quic_endpoint_t *endpoint, + const struct ConnectionIdGeneratorMethods *cid_gen_methods, + ConnectionIdGeneratorContext cid_gen_ctx); + /** * Create a client connection. * If success, the output parameter `index` carrys the index of the connection. @@ -868,6 +1017,15 @@ bool quic_conn_path_iter_next(struct quic_path_address_iter_t *iter, struct quic */ bool quic_conn_active_path(const struct quic_conn_t *conn, struct quic_path_address_t *a); +/** + * Return the latest statistics about the specified path. + */ +const struct PathStats *quic_conn_path_stats(struct quic_conn_t *conn, + const struct sockaddr *local, + socklen_t local_len, + const struct sockaddr *remote, + socklen_t remote_len); + /** * Return statistics about the connection. */ @@ -1069,6 +1227,17 @@ int quic_stream_set_context(struct quic_conn_t *conn, uint64_t stream_id, void * */ void *quic_stream_context(struct quic_conn_t *conn, uint64_t stream_id); +/** + * Extract the header form, version and destination connection id from the + * QUIC packet. + */ +int quic_packet_header_info(uint8_t *buf, + size_t buf_len, + uint8_t dcid_len, + bool *long_header, + uint32_t *version, + struct ConnectionId *dcid); + /** * Create default config for HTTP3. */ diff --git a/src/build.rs b/src/build.rs index 999c9e73..17d072d1 100644 --- a/src/build.rs +++ b/src/build.rs @@ -23,23 +23,43 @@ const CMAKE_PARAMS_ANDROID_NDK: &[(&str, &[(&str, &str)])] = &[ /// Additional parameters for iOS const CMAKE_PARAMS_IOS: &[(&str, &[(&str, &str)])] = &[ ( - "aarch64", + "aarch64-apple-ios", &[ ("CMAKE_OSX_ARCHITECTURES", "arm64"), ("CMAKE_OSX_SYSROOT", "iphoneos"), + ("CMAKE_ASM_FLAGS", "-fembed-bitcode -target arm64-apple-ios"), ], ), ( - "x86_64", + "aarch64-apple-ios-sim", + &[ + ("CMAKE_OSX_ARCHITECTURES", "arm64"), + ("CMAKE_OSX_SYSROOT", "iphonesimulator"), + ( + "CMAKE_ASM_FLAGS", + "-fembed-bitcode -target arm64-apple-ios-simulator", + ), + ("CMAKE_THREAD_LIBS_INIT", "-lpthread"), + ("CMAKE_HAVE_THREADS_LIBRARY", "1"), + ("THREADS_PREFER_PTHREAD_FLAG", "ON"), + ], + ), + ( + "x86_64-apple-ios", &[ ("CMAKE_OSX_ARCHITECTURES", "x86_64"), ("CMAKE_OSX_SYSROOT", "iphonesimulator"), + ( + "CMAKE_ASM_FLAGS", + "-fembed-bitcode -target x86_64-apple-ios-simulator", + ), ], ), ]; /// Create a cmake::Config for building BoringSSL. fn new_boringssl_cmake_config() -> cmake::Config { + let target = std::env::var("TARGET").unwrap(); let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); @@ -68,21 +88,17 @@ fn new_boringssl_cmake_config() -> cmake::Config { } "ios" => { - for (ios_arch, params) in CMAKE_PARAMS_IOS { - if *ios_arch == arch { + for (ios_target, params) in CMAKE_PARAMS_IOS { + if *ios_target == target { for (name, value) in *params { boringssl_cmake.define(name, value); + if *name == "CMAKE_ASM_FLAGS" { + boringssl_cmake.cflag(value); + } } break; } } - - let mut cflag = "-fembed-bitcode".to_string(); - if arch == "x86_64" { - cflag.push_str(" -target x86_64-apple-ios-simulator"); - } - boringssl_cmake.define("CMAKE_ASM_FLAGS", &cflag); - boringssl_cmake.cflag(&cflag); } _ => (), diff --git a/src/congestion_control/pacing.rs b/src/congestion_control/pacing.rs index 8a202896..e1344fc9 100644 --- a/src/congestion_control/pacing.rs +++ b/src/congestion_control/pacing.rs @@ -43,28 +43,40 @@ pub struct Pacer { /// Bucket capacity (bytes). Bytes that could burst during a pacing granularity capacity: u64, - /// last congestion window, bytes - last_cwnd: u64, - /// available tokens, bytes tokens: u64, + /// last congestion window, bytes + last_cwnd: u64, + /// last schedule time last_sched_time: Instant, + + /// Pacing granularity + granularity: Duration, } impl Pacer { /// Generate a pacer (for each path) - pub fn new(enabled: bool, srtt: Duration, cwnd: u64, mtu: u64, now: Instant) -> Self { - let capacity = Self::calc_capacity(cwnd, srtt, mtu); - - Self { + pub fn new( + enabled: bool, + srtt: Duration, + cwnd: u64, + mtu: u64, + now: Instant, + granularity: Duration, + ) -> Self { + let mut pacer = Pacer { enabled, - capacity, + capacity: 0, + tokens: 0, last_cwnd: cwnd, - tokens: capacity, last_sched_time: now, - } + granularity, + }; + pacer.update_capacity(cwnd, srtt, mtu); + pacer.tokens = pacer.capacity; + pacer } /// Build a pacer controller. @@ -76,6 +88,7 @@ impl Pacer { .saturating_mul(conf.max_datagram_size as u64), conf.max_datagram_size as u64, Instant::now(), + conf.pacing_granularity, ) } @@ -111,7 +124,7 @@ impl Pacer { // Update capacity and tokens if necessary if cwnd != self.last_cwnd { - self.capacity = Self::calc_capacity(cwnd, srtt, mtu); + self.update_capacity(cwnd, srtt, mtu); self.tokens = self.capacity.min(self.tokens); self.last_cwnd = cwnd; } @@ -139,14 +152,13 @@ impl Pacer { Some(self.last_sched_time + Duration::from_nanos(time_to_wait)) } - fn calc_capacity(cwnd: u64, srtt: Duration, mtu: u64) -> u64 { + fn update_capacity(&mut self, cwnd: u64, srtt: Duration, mtu: u64) { // Note: the bound operation would limit the average pacing rate to // [MIN_BURST_PACKET_NUM * mtu / srtt, MAX_BURST_PACKET_NUM * mtu / srtt] // the minimal pacing rate may be too large in some cases. let capacity = - (cwnd as u128 * PACING_GRANULARITY.as_nanos() / srtt.as_nanos().max(1_000_000)) as u64; - - capacity.clamp(MIN_BURST_PACKET_NUM * mtu, MAX_BURST_PACKET_NUM * mtu) + (cwnd as u128 * self.granularity.as_nanos() / srtt.as_nanos().max(1_000_000)) as u64; + self.capacity = capacity.clamp(MIN_BURST_PACKET_NUM * mtu, MAX_BURST_PACKET_NUM * mtu) } } @@ -162,7 +174,7 @@ mod tests { let now = Instant::now(); let cwnd: u64 = 20 * mtu; - let p = Pacer::new(enabled, srtt, cwnd, mtu, now); + let p = Pacer::new(enabled, srtt, cwnd, mtu, now, PACING_GRANULARITY); assert!(p.enabled() == true); assert_eq!(p.capacity, p.tokens); assert_eq!( @@ -171,13 +183,13 @@ mod tests { ); let cwnd: u64 = 1 * mtu; - let p = Pacer::new(enabled, srtt, cwnd, mtu, now); + let p = Pacer::new(enabled, srtt, cwnd, mtu, now, PACING_GRANULARITY); assert!(p.enabled() == true); assert_eq!(p.capacity, p.tokens); assert_eq!(p.capacity, MIN_BURST_PACKET_NUM * mtu); let cwnd: u64 = 200 * mtu; - let p = Pacer::new(enabled, srtt, cwnd, mtu, now); + let p = Pacer::new(enabled, srtt, cwnd, mtu, now, PACING_GRANULARITY); assert!(p.enabled() == true); assert_eq!(p.capacity, p.tokens); assert_eq!(p.capacity, MAX_BURST_PACKET_NUM * mtu); @@ -193,7 +205,7 @@ mod tests { let bytes_to_send: u64 = 1000; let pacing_rate: u64 = 1000000; - let mut p = Pacer::new(enabled, srtt, cwnd, mtu, now); + let mut p = Pacer::new(enabled, srtt, cwnd, mtu, now, PACING_GRANULARITY); assert_eq!(p.enabled(), false); assert_eq!(p.capacity, 20 * 1500); @@ -216,7 +228,7 @@ mod tests { // Abnormal input assert_eq!( - Pacer::new(enabled, srtt, cwnd, mtu, now).schedule( + Pacer::new(enabled, srtt, cwnd, mtu, now, PACING_GRANULARITY).schedule( bytes_to_send, pacing_rate, Duration::ZERO, @@ -227,7 +239,7 @@ mod tests { None ); assert_eq!( - Pacer::new(enabled, srtt, cwnd, mtu, now).schedule( + Pacer::new(enabled, srtt, cwnd, mtu, now, PACING_GRANULARITY).schedule( bytes_to_send, pacing_rate, srtt, @@ -239,7 +251,7 @@ mod tests { ); // Congestion window changes - let mut p = Pacer::new(enabled, srtt, cwnd, mtu, now); + let mut p = Pacer::new(enabled, srtt, cwnd, mtu, now, PACING_GRANULARITY); assert_eq!(p.capacity, cwnd); assert_eq!(p.capacity, p.tokens); @@ -251,7 +263,7 @@ mod tests { assert_eq!(p.tokens, cwnd); // do not change tokens // Schedule and wait cases - let mut p = Pacer::new(enabled, srtt, cwnd, mtu, now); + let mut p = Pacer::new(enabled, srtt, cwnd, mtu, now, PACING_GRANULARITY); assert_eq!(p.capacity, 10 * mtu); assert_eq!(p.tokens, 10 * mtu); diff --git a/src/connection/connection.rs b/src/connection/connection.rs index 3c03134b..5679aafd 100644 --- a/src/connection/connection.rs +++ b/src/connection/connection.rs @@ -70,6 +70,7 @@ use crate::FourTupleIter; use crate::MultipathConfig; use crate::PacketInfo; use crate::PathEvent; +use crate::PathStats; use crate::RecoveryConfig; use crate::Result; use crate::Shutdown; @@ -1513,12 +1514,14 @@ impl Connection { // Prepare and encode packet header (except for the Length and Packet Number field) let space_id = self.get_space_id(pkt_type, path_id)?; - let pkt_num = self - .spaces - .get_mut(space_id) - .ok_or(Error::InternalError)? - .next_pkt_num; - let pkt_num_len = packet::packet_num_len(pkt_num)?; + let (pkt_num, pkt_num_len) = { + let space = self.spaces.get_mut(space_id).ok_or(Error::InternalError)?; + let largest_acked = space.get_largest_acked_pkt(); + let pkt_num = space.next_pkt_num; + let pkt_num_len = packet::packet_num_len(pkt_num, largest_acked); + (pkt_num, pkt_num_len) + }; + let dcid_seq = self .paths .get(path_id)? @@ -1573,7 +1576,7 @@ impl Connection { } // Encode packet number - let len = packet::encode_packet_num(pkt_num, &mut out[pkt_num_offset..left])?; + let len = packet::encode_packet_num(pkt_num, pkt_num_len, &mut out[pkt_num_offset..left])?; let payload_offset = pkt_num_offset + len; // Write frames into the packet payload @@ -3582,6 +3585,19 @@ impl Connection { self.paths.get_active() } + /// Return an mutable reference to the specified path + pub fn get_path_stats( + &mut self, + local_addr: SocketAddr, + remote_addr: SocketAddr, + ) -> Result<&crate::PathStats> { + let pid = self + .paths + .get_path_id(&(local_addr, remote_addr)) + .ok_or(Error::InvalidOperation("not found".into()))?; + Ok(self.paths.get_mut(pid)?.stats()) + } + /// Migrates the connection to the specified path. #[doc(hidden)] pub fn migrate_path(&mut self, local_addr: SocketAddr, remote_addr: SocketAddr) -> Result<()> { @@ -4266,8 +4282,8 @@ pub(crate) mod tests { server_config: &mut Config, server_name: &str, ) -> Result { - let mut cli_cid_gen = RandomConnectionIdGenerator::new(client_config.cid_len, None); - let mut srv_cid_gen = RandomConnectionIdGenerator::new(server_config.cid_len, None); + let mut cli_cid_gen = RandomConnectionIdGenerator::new(client_config.cid_len); + let mut srv_cid_gen = RandomConnectionIdGenerator::new(server_config.cid_len); let client_scid = cli_cid_gen.generate(); let server_scid = srv_cid_gen.generate(); let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9443); diff --git a/src/connection/recovery.rs b/src/connection/recovery.rs index d11337e4..111b375a 100644 --- a/src/connection/recovery.rs +++ b/src/connection/recovery.rs @@ -106,6 +106,9 @@ pub struct Recovery { /// Cache pkt size pub cache_pkt_size: usize, + /// The time for last congestion window event + last_cwnd_limited_time: Option, + /// Path level Statistics. pub stats: PathStats, @@ -135,6 +138,7 @@ impl Recovery { pacer: Pacer::build_pacer_controller(conf), pacer_timer: None, cache_pkt_size: conf.max_datagram_size, + last_cwnd_limited_time: None, stats: PathStats::default(), last_metrics: RecoveryMetrics::default(), trace_id: String::from(""), @@ -903,29 +907,30 @@ impl Recovery { pub(crate) fn stat_cwnd_limited(&mut self) { let is_cwnd_limited = !self.can_send(); let now = Instant::now(); - if let Some(last_cwnd_limited_time) = self.stats.last_cwnd_limited_time { + if let Some(last_cwnd_limited_time) = self.last_cwnd_limited_time { // Update duration timely, in case it stays in cwnd limited all the time. let duration = now.saturating_duration_since(last_cwnd_limited_time); + let duration = duration.as_millis() as u64; self.stats.cwnd_limited_duration = self.stats.cwnd_limited_duration.saturating_add(duration); if is_cwnd_limited { - self.stats.last_cwnd_limited_time = Some(now); + self.last_cwnd_limited_time = Some(now); } else { - self.stats.last_cwnd_limited_time = None; + self.last_cwnd_limited_time = None; } } else if is_cwnd_limited { // A new cwnd limited event self.stats.cwnd_limited_count = self.stats.cwnd_limited_count.saturating_add(1); - self.stats.last_cwnd_limited_time = Some(now); + self.last_cwnd_limited_time = Some(now); } } /// Update with the latest values from recovery. pub(crate) fn stat_lazy_update(&mut self) { - self.stats.min_rtt = self.rtt.min_rtt(); - self.stats.max_rtt = self.rtt.max_rtt(); - self.stats.srtt = self.rtt.smoothed_rtt(); - self.stats.rttvar = self.rtt.rttvar(); + self.stats.min_rtt = self.rtt.min_rtt().as_micros() as u64; + self.stats.max_rtt = self.rtt.max_rtt().as_micros() as u64; + self.stats.srtt = self.rtt.smoothed_rtt().as_micros() as u64; + self.stats.rttvar = self.rtt.rttvar().as_micros() as u64; self.stats.in_slow_start = self.congestion.in_slow_start(); self.stats.pacing_rate = self.congestion.pacing_rate().unwrap_or_default(); } diff --git a/src/connection/space.rs b/src/connection/space.rs index c8f4da92..b5c6588d 100644 --- a/src/connection/space.rs +++ b/src/connection/space.rs @@ -199,6 +199,15 @@ impl PacketNumSpace { pub fn need_send_buffered_frames(&self) -> bool { !self.buffered.is_empty() } + + /// Return the largest packet number acknowledged in the packet number space. + pub fn get_largest_acked_pkt(&self) -> Option { + if self.largest_acked_pkt != u64::MAX { + Some(self.largest_acked_pkt) + } else { + None + } + } } /// All packet number spaces on a QUIC connection diff --git a/src/connection/stream.rs b/src/connection/stream.rs index d171bc7a..1957f8bb 100644 --- a/src/connection/stream.rs +++ b/src/connection/stream.rs @@ -1192,7 +1192,6 @@ impl StreamMap { // An endpoint that receives a MAX_STREAM_DATA frame for a receive-only stream // MUST terminate the connection with error STREAM_STATE_ERROR. if !is_local(stream_id, self.is_server) && !is_bidi(stream_id) { - // 针对StreamStateError做扩展,支持记录ID,以及首次发现异常的帧 return Err(Error::StreamStateError); } diff --git a/src/endpoint.rs b/src/endpoint.rs index 48498dee..3ef2e53b 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -112,7 +112,6 @@ impl Endpoint { ) -> Self { let cid_gen = Box::new(crate::RandomConnectionIdGenerator { cid_len: config.cid_len, - cid_lifetime: None, }); let trace_id = if is_server { "SERVER" } else { "CLIENT" }; let buffer = PacketBuffer::new(config.zerortt_buffer_size); @@ -802,6 +801,12 @@ impl Endpoint { self.conns.clear(); } + /// Set the connection id generator + /// By default, the RandomConnectionIdGenerator is used. + pub fn set_cid_generator(&mut self, cid_gen: Box) { + self.cid_gen = cid_gen; + } + /// Set the unique trace id for the endpoint pub fn set_trace_id(&mut self, trace_id: String) { self.trace_id = trace_id @@ -996,13 +1001,14 @@ const MAX_ZERORTT_PACKETS_PER_CONN: usize = 10; /// PacketBuffer is used for buffering early incoming ZeroRTT packets on the server. /// Buffered packets are indexed by odcid. struct PacketBuffer { - packets: hashlru::Cache, PacketInfo)>>, + packets: lru::LruCache, PacketInfo)>>, } impl PacketBuffer { fn new(cache_size: usize) -> Self { + let size = std::num::NonZeroUsize::new(cache_size).unwrap(); Self { - packets: hashlru::Cache::new(cache_size / MAX_ZERORTT_PACKETS_PER_CONN), + packets: lru::LruCache::new(size), } } @@ -1017,12 +1023,12 @@ impl PacketBuffer { let mut v = Vec::with_capacity(MAX_ZERORTT_PACKETS_PER_CONN); v.push((buffer, info)); - self.packets.insert(dcid, v); + self.packets.put(dcid, v); } /// Remove all packets for the specified connection fn del(&mut self, dcid: &ConnectionId) -> Option, PacketInfo)>> { - self.packets.remove(dcid) + self.packets.pop(dcid) } } diff --git a/src/ffi.rs b/src/ffi.rs index 86562f3a..874b9746 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -294,6 +294,20 @@ pub extern "C" fn quic_config_set_initial_rtt(config: &mut Config, v: u64) { config.set_initial_rtt(v); } +/// Enable pacing to smooth the flow of packets sent onto the network. +/// The default value is true. +#[no_mangle] +pub extern "C" fn quic_config_enable_pacing(config: &mut Config, v: bool) { + config.enable_pacing(v); +} + +/// Set clock granularity used by the pacer. +/// The default value is 10 milliseconds. +#[no_mangle] +pub extern "C" fn quic_config_set_pacing_granularity(config: &mut Config, v: u64) { + config.set_pacing_granularity(v); +} + /// Set the linear factor for calculating the probe timeout. /// The endpoint do not backoff the first `v` consecutive probe timeouts. /// The default value is `0`. @@ -442,6 +456,7 @@ pub extern "C" fn quic_config_set_send_batch_size(config: &mut Config, v: u16) { } /// Set the buffer size for disordered zerortt packets on the server. +/// The default value is `1000`. A value of 0 will be treated as default value. /// Applicable to Server only. #[no_mangle] pub extern "C" fn quic_config_set_zerortt_buffer_size(config: &mut Config, v: u16) { @@ -725,6 +740,21 @@ pub extern "C" fn quic_endpoint_free(endpoint: *mut Endpoint) { }; } +/// Set the connection id generator for the endpoint. +/// By default, the random connection id generator is used. +#[no_mangle] +pub extern "C" fn quic_endpoint_set_cid_generator( + endpoint: &mut Endpoint, + cid_gen_methods: *const ConnectionIdGeneratorMethods, + cid_gen_ctx: ConnectionIdGeneratorContext, +) { + let cid_generator = Box::new(ConnectionIdGenerator { + methods: cid_gen_methods, + context: cid_gen_ctx, + }); + endpoint.set_cid_generator(cid_generator); +} + /// Create a client connection. /// If success, the output parameter `index` carrys the index of the connection. /// Note: The `config` specific to the endpoint or server is irrelevant and will be disregarded. @@ -1070,6 +1100,23 @@ pub extern "C" fn quic_conn_active_path(conn: &Connection, a: &mut PathAddress) false } +/// Return the latest statistics about the specified path. +#[no_mangle] +pub extern "C" fn quic_conn_path_stats<'a>( + conn: &'a mut Connection, + local: &sockaddr, + local_len: socklen_t, + remote: &sockaddr, + remote_len: socklen_t, +) -> Option<&'a PathStats> { + let local_addr = sock_addr_from_c(local, local_len); + let remote_addr = sock_addr_from_c(remote, remote_len); + if let Ok(stats) = conn.get_path_stats(local_addr, remote_addr) { + return Some(stats); + } + None +} + /// Return statistics about the connection. #[no_mangle] pub extern "C" fn quic_conn_stats(conn: &mut Connection) -> &ConnectionStats { @@ -1773,6 +1820,62 @@ pub struct PacketOutSpec { dst_addr_len: socklen_t, } +#[repr(C)] +pub struct ConnectionIdGeneratorMethods { + /// Generate a new CID + pub generate: fn(gctx: *mut c_void) -> ConnectionId, + + /// Return the length of a CID + pub cid_len: fn(gctx: *mut c_void) -> u8, +} + +#[repr(transparent)] +pub struct ConnectionIdGeneratorContext(*mut c_void); + +/// cbindgen:no-export +#[repr(C)] +pub struct ConnectionIdGenerator { + pub methods: *const ConnectionIdGeneratorMethods, + pub context: ConnectionIdGeneratorContext, +} + +impl crate::ConnectionIdGenerator for ConnectionIdGenerator { + /// Generate a new CID + fn generate(&mut self) -> ConnectionId { + unsafe { ((*self.methods).generate)(self.context.0) } + } + + /// Return the length of a CID + fn cid_len(&self) -> usize { + let cid_len = unsafe { ((*self.methods).cid_len)(self.context.0) }; + cid_len as usize + } +} + +/// Extract the header form, version and destination connection id from the +/// QUIC packet. +#[no_mangle] +pub extern "C" fn quic_packet_header_info( + buf: *mut u8, + buf_len: size_t, + dcid_len: u8, + long_header: &mut bool, + version: &mut u32, + dcid: &mut ConnectionId, +) -> c_int { + let buf = unsafe { slice::from_raw_parts_mut(buf, buf_len) }; + + match crate::PacketHeader::header_info(buf, dcid_len as usize) { + Ok((long, ver, cid)) => { + *long_header = long; + *version = ver; + *dcid = cid; + return 0; + } + Err(e) => return e.to_errno() as i32, + } +} + /// Create default config for HTTP3. #[no_mangle] pub extern "C" fn http3_config_new() -> *mut Http3Config { diff --git a/src/lib.rs b/src/lib.rs index c9d47d22..31fac9a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -155,6 +155,7 @@ pub type Result = std::result::Result; /// Connection Id is an identifier used to identify a QUIC connection /// at an endpoint. +#[repr(C)] #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] pub struct ConnectionId { /// length of cid @@ -214,9 +215,6 @@ pub trait ConnectionIdGenerator { /// Return the length of a CID fn cid_len(&self) -> usize; - /// Return the lifetime of CID - fn cid_lifetime(&self) -> Option; - /// Generate a new CID and associated reset token. fn generate_cid_and_token(&mut self, reset_token_key: &hmac::Key) -> (ConnectionId, u128) { let scid = self.generate(); @@ -229,14 +227,12 @@ pub trait ConnectionIdGenerator { #[derive(Debug, Clone, Copy)] pub struct RandomConnectionIdGenerator { cid_len: usize, - cid_lifetime: Option, } impl RandomConnectionIdGenerator { - pub fn new(cid_len: usize, cid_lifetime: Option) -> Self { + pub fn new(cid_len: usize) -> Self { Self { cid_len: cmp::min(cid_len, MAX_CID_LEN), - cid_lifetime, } } } @@ -251,10 +247,6 @@ impl ConnectionIdGenerator for RandomConnectionIdGenerator { fn cid_len(&self) -> usize { self.cid_len } - - fn cid_lifetime(&self) -> Option { - self.cid_lifetime - } } /// Meta information about a packet. @@ -567,11 +559,18 @@ impl Config { } /// Enable pacing to smooth the flow of packets sent onto the network. - /// default value is true. + /// The default value is true. pub fn enable_pacing(&mut self, v: bool) { self.recovery.enable_pacing = v; } + /// Set clock granularity used by the pacer. + /// The default value is 1 milliseconds. + pub fn set_pacing_granularity(&mut self, millis: u64) { + self.recovery.pacing_granularity = + cmp::max(Duration::from_millis(millis), TIMER_GRANULARITY); + } + /// Set the linear factor for calculating the probe timeout. /// The endpoint do not backoff the first `v` consecutive probe timeouts. /// The default value is `0`. @@ -693,9 +692,14 @@ impl Config { } /// Set the buffer size for disordered zerortt packets on the server. + /// The default value is `1000`. A value of 0 will be treated as default value. /// Applicable to Server only. pub fn set_zerortt_buffer_size(&mut self, v: usize) { - self.zerortt_buffer_size = v; + if v > 0 { + self.zerortt_buffer_size = v; + } else { + self.zerortt_buffer_size = 1000; + } } /// Set TLS config. @@ -787,6 +791,9 @@ pub struct RecoveryConfig { /// Enable pacing to smooth the flow of packets sent onto the network. pub enable_pacing: bool, + /// Clock granularity used by the pacer. + pub pacing_granularity: Duration, + /// Linear factor for calculating the probe timeout. pub pto_linear_factor: u64, @@ -811,6 +818,7 @@ impl Default for RecoveryConfig { bbr_probe_bw_cwnd_gain: 2.0, initial_rtt: INITIAL_RTT, enable_pacing: true, + pacing_granularity: time::Duration::from_millis(1), pto_linear_factor: DEFAULT_PTO_LINEAR_FACTOR, max_pto: MAX_PTO, } @@ -1044,24 +1052,21 @@ pub struct PathStats { /// Total congestion window limited events. pub cwnd_limited_count: u64, - /// Total duration of congestion windowlimited events. - pub cwnd_limited_duration: Duration, - - /// The time for last congestion window event - last_cwnd_limited_time: Option, + /// Total duration of congestion windowlimited events in microseconds. + pub cwnd_limited_duration: u64, /* Note: the following fields are lazily updated from Recovery */ - /// Minimum roundtrip time. - pub min_rtt: Duration, + /// Minimum roundtrip time in microseconds. + pub min_rtt: u64, - /// Maximum roundtrip time. - pub max_rtt: Duration, + /// Maximum roundtrip time in microseconds. + pub max_rtt: u64, - /// Smoothed roundtrip time. - pub srtt: Duration, + /// Smoothed roundtrip time in microseconds. + pub srtt: u64, - /// Roundtrip time variation. - pub rttvar: Duration, + /// Roundtrip time variation in microseconds. + pub rttvar: u64, /// Whether the congestion controller is in slow start status. pub in_slow_start: bool, @@ -1085,11 +1090,9 @@ mod tests { #[test] fn connection_id() { - let lifetime = Duration::from_secs(3600); - let mut cid_gen = RandomConnectionIdGenerator::new(8, Some(lifetime)); + let mut cid_gen = RandomConnectionIdGenerator::new(8); let cid = cid_gen.generate(); assert_eq!(cid.len(), cid_gen.cid_len()); - assert_eq!(Some(lifetime), cid_gen.cid_lifetime()); let cid = ConnectionId { len: 4, @@ -1158,6 +1161,7 @@ pub use crate::connection::Connection; pub use crate::endpoint::Endpoint; pub use crate::error::Error; pub use crate::multipath_scheduler::MultipathAlgorithm; +pub use crate::packet::PacketHeader; pub use crate::tls::TlsConfig; pub use crate::tls::TlsConfigSelector; diff --git a/src/packet.rs b/src/packet.rs index 73a0e6ad..74043379 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -351,6 +351,29 @@ impl PacketHeader { )) } + /// Extract the header form, version and destination connection id. + /// + /// Return (true, version, cid) for the quic packet with a long header + /// Return (false, 0, cid) for the quic packet with a short header + /// See RFC 8999 Section 5 + pub fn header_info(mut buf: &[u8], dcid_len: usize) -> Result<(bool, u32, ConnectionId)> { + let first = buf.read_u8()?; + + // Decode in short header form for 1-RTT. + if !PacketHeader::long_header(first) { + let dcid = buf.read(dcid_len)?; + let dcid = ConnectionId::new(&dcid); + return Ok((false, 0, dcid)); + } + + // Decode in long header form. + let version = buf.read_u32()?; + let dcid_len = buf.read_u8()?; + let dcid = buf.read(dcid_len as usize)?; + let dcid = ConnectionId::new(&dcid); + Ok((true, version, dcid)) + } + /// Return true if the packet has a long header. fn long_header(header_first_byte: u8) -> bool { header_first_byte & HEADER_LONG_FORM_BIT != 0 @@ -595,9 +618,18 @@ pub(crate) fn decode_packet_num(largest_pn: u64, truncated_pn: u64, pkt_num_len: candidate_pn } -/// Encode the truncated packet number. -pub(crate) fn encode_packet_num(pkt_num: u64, mut buf: &mut [u8]) -> Result { - let len = packet_num_len(pkt_num)?; +/// Encode the full packet number. +/// +/// Packet numbers are encoded in 1 to 4 bytes. The number of bits required to +/// represent the packet number is reduced by including only the least +/// significant bits of the packet number. +/// +/// The `pkt_num` is the full packet number of the packet being sent. +/// The `len` is the length of encoded packet number. +/// See RFC 9000 Section A.2 Sample Packet Number Encoding Algorithm +pub(crate) fn encode_packet_num(pkt_num: u64, len: usize, mut buf: &mut [u8]) -> Result { + // Encode the integer value and truncate to the num_bytes least significant + // bytes. match len { 1 => buf.write_u8(pkt_num as u8)?, 2 => buf.write_u16(pkt_num as u16)?, @@ -611,23 +643,22 @@ pub(crate) fn encode_packet_num(pkt_num: u64, mut buf: &mut [u8]) -> Result Result { - let len = if pkt_num < u64::from(u8::MAX) { - 1 - } else if pkt_num < u64::from(u16::MAX) { - 2 - } else if pkt_num < 16_777_215u64 { - 3 - } else if pkt_num < u64::from(u32::MAX) { - 4 +/// The `pkt_num` is the full packet number of the packet being sent. +/// The `largest_acked` is the largest packet number that has been acknowledged +/// by the peer in the current packet number space, if any. +/// See RFC 9000 Section A.2 Sample Packet Number Encoding Algorithm +pub(crate) fn packet_num_len(pkt_num: u64, largest_acked: Option) -> usize { + // The number of bits must be at least one more than the base-2 logarithm + // of the number of contiguous unacknowledged packet numbers, including the + // new packet + let num_unacked = if let Some(largest_acked) = largest_acked { + pkt_num.saturating_sub(largest_acked) } else { - return Err(Error::InvalidPacket); + pkt_num.saturating_add(1) }; - Ok(len) + + let min_bits = u64::BITS - num_unacked.leading_zeros(); // log(num_unacked, 2) + 1 + ((min_bits + 7) / 8) as usize // ceil(min_bits / 8) } /// Encode a Version Negotiation packet to the given buffer @@ -825,7 +856,13 @@ mod tests { token=040404040404040404040404040404040404040404040404" ); let len = initial_hdr.to_bytes(&mut buf)?; - assert_eq!((initial_hdr, len), PacketHeader::from_bytes(&mut buf, 20)?); + assert_eq!( + (initial_hdr.clone(), len), + PacketHeader::from_bytes(&mut buf, 20)? + ); + + let info = PacketHeader::header_info(&mut buf, 20)?; + assert_eq!(info, (true, initial_hdr.version, initial_hdr.dcid)); Ok(()) } @@ -856,7 +893,13 @@ mod tests { let mut buf = [0; 128]; let len = hsk_hdr.to_bytes(&mut buf)?; - assert_eq!((hsk_hdr, len), PacketHeader::from_bytes(&mut buf, 20)?); + assert_eq!( + (hsk_hdr.clone(), len), + PacketHeader::from_bytes(&mut buf, 20)? + ); + + let info = PacketHeader::header_info(&mut buf, 20)?; + assert_eq!(info, (true, hsk_hdr.version, hsk_hdr.dcid)); Ok(()) } @@ -887,7 +930,13 @@ mod tests { let mut buf = [0; 128]; let len = zero_rtt_hdr.to_bytes(&mut buf)?; - assert_eq!((zero_rtt_hdr, len), PacketHeader::from_bytes(&mut buf, 20)?); + assert_eq!( + (zero_rtt_hdr.clone(), len), + PacketHeader::from_bytes(&mut buf, 20)? + ); + + let info = PacketHeader::header_info(&mut buf, 20)?; + assert_eq!(info, (true, zero_rtt_hdr.version, zero_rtt_hdr.dcid)); Ok(()) } @@ -931,6 +980,8 @@ mod tests { // Note: key phase is encrypted and not parsed by from_bytes() assert_eq!(PacketHeader::from_bytes(&mut buf, 20)?.0.key_phase, false); + let info = PacketHeader::header_info(&mut buf, 20)?; + assert_eq!(info, (false, one_rtt_hdr.version, one_rtt_hdr.dcid)); Ok(()) } @@ -961,6 +1012,9 @@ mod tests { scid=0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c" ); + let info = PacketHeader::header_info(&mut buf, 20)?; + assert_eq!(info, (true, hdr.version, hdr.dcid)); + let mut br = &buf[hdr_len..]; let ver = br.read_u32()?; assert_eq!(ver, crate::QUIC_VERSION_V1); @@ -1014,6 +1068,9 @@ mod tests { token=71756963c0a8010a0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e" ); + let info = PacketHeader::header_info(&mut buf, 20)?; + assert_eq!(info, (true, hdr.version, hdr.dcid)); + verify_retry_integrity_tag(&mut buf[..len], &odcid, crate::QUIC_VERSION_V1)?; Ok(()) } @@ -1089,21 +1146,40 @@ mod tests { #[test] fn packet_num() -> Result<()> { let test_cases = [ - (0, Ok(1)), - (254, Ok(1)), - (255, Ok(2)), - (65534, Ok(2)), - (65535, Ok(3)), - (16777214, Ok(3)), - (16777215, Ok(4)), + (0, None, 1), + (254, Some(0), 1), + (255, Some(0), 1), + (256, Some(0), 2), + (65534, Some(0), 2), + (65535, Some(0), 2), + (65536, Some(0), 3), + (16777214, Some(0), 3), + (16777215, Some(0), 3), + (16777216, Some(0), 4), + (4294967295, Some(0), 4), + (4294967296, Some(1), 4), + (4294967296, Some(4294967295), 1), + (4611686018427387903, Some(4611686018427387902), 1), ]; - let mut buf = [0; 4]; for case in test_cases { let pkt_num = case.0; - let len = encode_packet_num(pkt_num, &mut buf[..]); - assert_eq!(len, case.1); + let largest_acked = case.1; + let pkt_num_len = packet_num_len(pkt_num, largest_acked); + assert_eq!(pkt_num_len, case.2); + + let len = encode_packet_num(pkt_num, pkt_num_len, &mut buf[..])?; + assert_eq!(len, pkt_num_len); } + + // Test case in A.2. Sample Packet Number Encoding Algorithm + let pkt_num_len = packet_num_len(0xac5c02, Some(0xabe8b3)); + assert_eq!(pkt_num_len, 2); + + // Test case in RFC 9000 A.3. Sample Packet Number Decoding Algorithm + let pkt_num = decode_packet_num(0xa82f30ea, 0x9b32, 2); + assert_eq!(pkt_num, 0xa82f9b32); + Ok(()) } @@ -1404,7 +1480,7 @@ mod tests { dcid: ConnectionId::random(), scid: ConnectionId::default(), pkt_num: 10, - pkt_num_len: packet_num_len(10)?, + pkt_num_len: packet_num_len(10, Some(1)), token: None, key_phase: false, }; @@ -1415,7 +1491,7 @@ mod tests { // encode the packet header and payload let mut written = pkt_hdr.to_bytes(&mut out)?; - written += encode_packet_num(pkt_hdr.pkt_num, &mut out[written..])?; + written += encode_packet_num(pkt_hdr.pkt_num, 1, &mut out[written..])?; let (payload_off, payload_end) = (written, written + pkt_payload.len()); out[payload_off..payload_end].copy_from_slice(&pkt_payload); diff --git a/tools/Cargo.toml b/tools/Cargo.toml index c004404d..4d350a19 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tquic_tools" -version = "0.14.0" +version = "0.15.0" edition = "2021" rust-version = "1.70.0" license = "Apache-2.0" @@ -23,7 +23,7 @@ rand = "0.8.5" statrs = "0.16" jemallocator = { version = "0.5", package = "tikv-jemallocator" } signal-hook = "0.3.17" -tquic = { path = "..", version = "0.14.0"} +tquic = { path = "..", version = "0.15.0"} [lib] crate-type = ["lib"]