diff --git a/.github/workflows/tquic-features.yml b/.github/workflows/tquic-features.yml index 54270377..25dfd71a 100644 --- a/.github/workflows/tquic-features.yml +++ b/.github/workflows/tquic-features.yml @@ -25,6 +25,14 @@ jobs: run: cargo build --all --no-default-features -F ffi && cargo test --no-default-features -F ffi - name: Build with feature(s) qlog run: cargo build --all --no-default-features -F qlog && cargo test --no-default-features -F qlog + - name: Build with feature(s) h3 + run: cargo build --all --no-default-features -F h3 && cargo test --no-default-features -F h3 - name: Build with feature(s) ffi,qlog run: cargo build --all --no-default-features -F ffi,qlog && cargo test --no-default-features -F ffi,qlog + - name: Build with feature(s) ffi,h3 + run: cargo build --all --no-default-features -F ffi,h3 && cargo test --no-default-features -F ffi,h3 + - name: Build with feature(s) qlog,h3 + run: cargo build --all --no-default-features -F qlog,h3 && cargo test --no-default-features -F qlog,h3 + - name: Build with feature(s) ffi,qlog,h3 + run: cargo build --all --no-default-features -F ffi,qlog,h3 && cargo test --no-default-features -F ffi,qlog,h3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dc934cb..f9a0130f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,16 @@ 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). +## [v1.5.0] - 2024-10-28 + +### Added +- Add h3 feature flag to reduce the size of complied library +- Add tquic_time_cwnd.py for analyzing TQUIC debug logs and produce a time-cwnd figure + +### Fixed +- Fix the typos detected by the latest typo-cli + + ## [v1.4.0] - 2024-10-28 ### Added @@ -165,7 +175,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix the issue where bbr3 cannot exit slow start due to high packet loss rate ### Security -- Limit memory consuption for tracking closed stream ids +- Limit memory consumption for tracking closed stream ids ## [v0.9.0] - 2024-04-10 @@ -339,6 +349,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Provide example clients and servers. +[v1.5.0]: https://github.com/tencent/tquic/compare/v1.4.0...v1.5.0 [v1.4.0]: https://github.com/tencent/tquic/compare/v1.3.1...v1.4.0 [v1.3.1]: https://github.com/tencent/tquic/compare/v1.3.0...v1.3.1 [v1.3.0]: https://github.com/tencent/tquic/compare/v1.2.0...v1.3.0 diff --git a/Cargo.toml b/Cargo.toml index eae6b676..76309564 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tquic" -version = "1.4.0" +version = "1.5.0" edition = "2021" rust-version = "1.70.0" license = "Apache-2.0" @@ -27,10 +27,10 @@ include = [ [package.metadata.docs.rs] no-default-features = true -features = ["qlog"] +features = ["qlog", "h3"] [features] -default = ["qlog"] +default = ["qlog", "h3"] # build the FFI API ffi = [] @@ -38,6 +38,9 @@ ffi = [] # enable support for the qlog qlog = ["dep:serde", "dep:serde_json", "dep:serde_derive", "dep:serde_with"] +# enable support for h3 +h3 = ["dep:sfv"] + [dependencies] bytes = "1" rustc-hash = "1.1" @@ -58,7 +61,7 @@ serde_derive = { version = "1.0", optional=true } serde_with = { version="3.0.0", optional=true } hex = "0.4" priority-queue = "1.3.2" -sfv = { version = "0.9" } +sfv = { version = "0.9", optional=true } [target."cfg(windows)".dependencies] winapi = { version = "0.3", features = ["wincrypt", "ws2def", "ws2ipdef", "ws2tcpip"] } diff --git a/include/tquic.h b/include/tquic.h index f155b497..992cb6af 100644 --- a/include/tquic.h +++ b/include/tquic.h @@ -1223,7 +1223,7 @@ ssize_t quic_stream_write(struct quic_conn_t *conn, * Create a new quic stream with the given id and priority. * This is a low-level API for stream creation. It is recommended to use * `quic_stream_bidi_new` for bidirectional streams or `quic_stream_uni_new` - * for unidrectional streams. + * for undirectional streams. */ int quic_stream_new(struct quic_conn_t *conn, uint64_t stream_id, diff --git a/src/connection/connection.rs b/src/connection/connection.rs index 08efa977..7f3c7af0 100644 --- a/src/connection/connection.rs +++ b/src/connection/connection.rs @@ -2032,7 +2032,7 @@ impl Connection { Ok(()) } - /// Write PATH_RESPONSE/PATH_CHALLENGE frams if needed. + /// Write PATH_RESPONSE/PATH_CHALLENGE frames if needed. fn try_write_path_validation_frames( &mut self, out: &mut [u8], @@ -3885,7 +3885,7 @@ impl Connection { /// Create a new stream with given stream id and priority. /// This is a low-level API for stream creation. It is recommended to use /// `stream_bidi_new` for bidirectional streams or `stream_uni_new` for - /// unidrectional streams. + /// undirectional streams. pub fn stream_new(&mut self, stream_id: u64, urgency: u8, incremental: bool) -> Result<()> { self.stream_set_priority(stream_id, urgency, incremental) } @@ -3897,7 +3897,7 @@ impl Connection { self.streams.stream_bidi_new(urgency, incremental) } - /// Create a new unidrectional stream with given stream priority. + /// Create a new undirectional stream with given stream priority. /// Return id of the created stream upon success. pub fn stream_uni_new(&mut self, urgency: u8, incremental: bool) -> Result { self.mark_tickable(true); diff --git a/src/connection/stream.rs b/src/connection/stream.rs index e267bf7f..0043d22e 100644 --- a/src/connection/stream.rs +++ b/src/connection/stream.rs @@ -214,7 +214,7 @@ impl StreamMap { } } - /// Create a new unidrectional stream with given stream priority. + /// Create a new undirectional stream with given stream priority. /// Return id of the created stream upon success. pub fn stream_uni_new(&mut self, urgency: u8, incremental: bool) -> Result { let stream_id = self.next_stream_id_uni; diff --git a/src/endpoint.rs b/src/endpoint.rs index 8d49b6b2..1dc5035e 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -97,7 +97,7 @@ pub struct Endpoint { /// The endpoint is shutdown. closed: bool, - /// The unique trace id for the enpdoint + /// The unique trace id for the endpoint trace_id: String, } diff --git a/src/ffi.rs b/src/ffi.rs index e178ad59..d9665a39 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -108,11 +108,17 @@ pub struct iovec { use crate::codec::Decoder; use crate::connection::ConnectionStats; use crate::error::Error; +#[cfg(feature = "h3")] use crate::h3::connection::Http3Connection; +#[cfg(feature = "h3")] use crate::h3::connection::Http3Priority; +#[cfg(feature = "h3")] use crate::h3::Http3Config; +#[cfg(feature = "h3")] use crate::h3::Http3Event; +#[cfg(feature = "h3")] use crate::h3::Http3Headers; +#[cfg(feature = "h3")] use crate::h3::NameValue; #[cfg(feature = "qlog")] use crate::qlog::events; @@ -1510,7 +1516,7 @@ pub extern "C" fn quic_stream_write( /// Create a new quic stream with the given id and priority. /// This is a low-level API for stream creation. It is recommended to use /// `quic_stream_bidi_new` for bidirectional streams or `quic_stream_uni_new` -/// for unidrectional streams. +/// for undirectional streams. #[no_mangle] pub extern "C" fn quic_stream_new( conn: &mut Connection, @@ -2064,6 +2070,7 @@ pub extern "C" fn quic_packet_header_info( } /// Create default config for HTTP3. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_config_new() -> *mut Http3Config { match Http3Config::new() { @@ -2073,6 +2080,7 @@ pub extern "C" fn http3_config_new() -> *mut Http3Config { } /// Destroy the HTTP3 config. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_config_free(config: *mut Http3Config) { unsafe { @@ -2082,6 +2090,7 @@ pub extern "C" fn http3_config_free(config: *mut Http3Config) { /// Set the `SETTINGS_MAX_FIELD_SECTION_SIZE` setting. /// By default no limit is enforced. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_config_set_max_field_section_size(config: &mut Http3Config, v: u64) { config.set_max_field_section_size(v); @@ -2089,6 +2098,7 @@ pub extern "C" fn http3_config_set_max_field_section_size(config: &mut Http3Conf /// Set the `SETTINGS_QPACK_MAX_TABLE_CAPACITY` setting. /// The default value is `0`. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_config_set_qpack_max_table_capacity(config: &mut Http3Config, v: u64) { config.set_qpack_max_table_capacity(v); @@ -2096,6 +2106,7 @@ pub extern "C" fn http3_config_set_qpack_max_table_capacity(config: &mut Http3Co /// Set the `SETTINGS_QPACK_BLOCKED_STREAMS` setting. /// The default value is `0`. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_config_set_qpack_blocked_streams(config: &mut Http3Config, v: u64) { config.set_qpack_blocked_streams(v); @@ -2104,6 +2115,7 @@ pub extern "C" fn http3_config_set_qpack_blocked_streams(config: &mut Http3Confi /// Create an HTTP/3 connection using the given QUIC connection. It also /// initiate the HTTP/3 handshake by opening all control streams and sending /// the local settings. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_conn_new( quic_conn: &mut Connection, @@ -2116,6 +2128,7 @@ pub extern "C" fn http3_conn_new( } /// Destroy the HTTP/3 connection. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_conn_free(conn: *mut Http3Connection) { unsafe { @@ -2124,6 +2137,7 @@ pub extern "C" fn http3_conn_free(conn: *mut Http3Connection) { } /// Send goaway with the given id. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_send_goaway( conn: &mut Http3Connection, @@ -2137,6 +2151,7 @@ pub extern "C" fn http3_send_goaway( } /// Set HTTP/3 connection events handler. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_conn_set_events_handler( conn: &mut Http3Connection, @@ -2148,6 +2163,7 @@ pub extern "C" fn http3_conn_set_events_handler( } /// Process HTTP/3 settings. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_for_each_setting( conn: &Http3Connection, @@ -2171,6 +2187,7 @@ pub extern "C" fn http3_for_each_setting( } /// Process internal events of all streams of the specified HTTP/3 connection. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_conn_process_streams( conn: &mut Http3Connection, @@ -2183,6 +2200,7 @@ pub extern "C" fn http3_conn_process_streams( } /// Process HTTP/3 headers. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_for_each_header( headers: &Http3Headers, @@ -2212,6 +2230,7 @@ pub extern "C" fn http3_for_each_header( } /// Return true if all the data has been read from the stream. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_stream_read_finished(conn: &mut Connection, stream_id: u64) -> bool { conn.stream_finished(stream_id) @@ -2219,6 +2238,7 @@ pub extern "C" fn http3_stream_read_finished(conn: &mut Connection, stream_id: u /// Create a new HTTP/3 request stream. /// On success the stream ID is returned. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_stream_new(conn: &mut Http3Connection, quic_conn: &mut Connection) -> i64 { match conn.stream_new_with_priority(quic_conn, &Http3Priority::default()) { @@ -2229,6 +2249,7 @@ pub extern "C" fn http3_stream_new(conn: &mut Http3Connection, quic_conn: &mut C /// Create a new HTTP/3 request stream with the given priority. /// On success the stream ID is returned. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_stream_new_with_priority( conn: &mut Http3Connection, @@ -2242,6 +2263,7 @@ pub extern "C" fn http3_stream_new_with_priority( } /// Close the given HTTP/3 stream. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_stream_close( conn: &mut Http3Connection, @@ -2255,6 +2277,7 @@ pub extern "C" fn http3_stream_close( } /// Set priority for an HTTP/3 stream. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_stream_set_priority( conn: &mut Http3Connection, @@ -2268,6 +2291,7 @@ pub extern "C" fn http3_stream_set_priority( } } +#[cfg(feature = "h3")] #[repr(C)] pub struct Header { name: *mut u8, @@ -2277,6 +2301,7 @@ pub struct Header { } /// Send HTTP/3 request or response headers on the given stream. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_send_headers( conn: &mut Http3Connection, @@ -2295,6 +2320,7 @@ pub extern "C" fn http3_send_headers( } /// Send HTTP/3 request or response body on the given stream. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_send_body( conn: &mut Http3Connection, @@ -2316,6 +2342,7 @@ pub extern "C" fn http3_send_body( } /// Read request/response body from the given stream. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_recv_body( conn: &mut Http3Connection, @@ -2336,6 +2363,7 @@ pub extern "C" fn http3_recv_body( } /// Parse HTTP/3 priority data. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_parse_extensible_priority( priority: *const u8, @@ -2356,6 +2384,7 @@ pub extern "C" fn http3_parse_extensible_priority( /// Send a PRIORITY_UPDATE frame on the control stream with specified /// request stream ID and priority. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_send_priority_update_for_request( conn: &mut Http3Connection, @@ -2370,6 +2399,7 @@ pub extern "C" fn http3_send_priority_update_for_request( } /// Take the last PRIORITY_UPDATE for the given stream. +#[cfg(feature = "h3")] #[no_mangle] pub extern "C" fn http3_take_priority_update( conn: &mut Http3Connection, @@ -2395,6 +2425,7 @@ pub extern "C" fn http3_take_priority_update( } /// Convert HTTP/3 header. +#[cfg(feature = "h3")] fn headers_from_ptr<'a>(ptr: *const Header, len: size_t) -> Vec> { let headers = unsafe { slice::from_raw_parts(ptr, len) }; @@ -2433,6 +2464,7 @@ pub extern "C" fn quic_set_logger( } } +#[cfg(feature = "h3")] #[repr(C)] pub struct Http3Methods { /// Called when the stream got headers. @@ -2455,18 +2487,24 @@ pub struct Http3Methods { pub on_conn_goaway: Option, } +#[cfg(feature = "h3")] #[repr(transparent)] pub struct Http3Context(*mut c_void); +#[cfg(feature = "h3")] #[repr(C)] pub struct Http3Handler { pub methods: *const Http3Methods, pub context: Http3Context, } +#[cfg(feature = "h3")] unsafe impl Send for Http3Handler {} + +#[cfg(feature = "h3")] unsafe impl Sync for Http3Handler {} +#[cfg(feature = "h3")] impl crate::h3::Http3Handler for Http3Handler { fn on_stream_headers(&self, stream_id: u64, ev: &mut Http3Event) { unsafe { diff --git a/src/lib.rs b/src/lib.rs index a533ed70..99472278 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,8 +46,9 @@ //! TQUIC defines several feature flags to reduce the amount of compiled code //! and dependencies: //! -//! * `ffi`: Build and expose the FFI API. -//! * `qlog`: Enable support for the qlog. +//! * `ffi`: Build and expose the FFI API +//! * `qlog`: Enable support for qlog +//! * `h3`: Enable support for HTTP/3 #![allow(unused_imports)] #![allow(dead_code)] @@ -1236,6 +1237,7 @@ mod multipath_scheduler; #[path = "tls/tls.rs"] mod tls; +#[cfg(feature = "h3")] #[path = "h3/h3.rs"] pub mod h3; diff --git a/tools/Cargo.toml b/tools/Cargo.toml index d02d49b4..b4b90ca0 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tquic_tools" -version = "1.4.0" +version = "1.5.0" edition = "2021" rust-version = "1.70.0" license = "Apache-2.0" @@ -22,7 +22,7 @@ slab = "0.4" rand = "0.8.5" statrs = "0.16" signal-hook = "0.3.17" -tquic = { path = "..", version = "1.4.0"} +tquic = { path = "..", version = "1.5.0"} [target."cfg(unix)".dependencies] jemallocator = { version = "0.5", package = "tikv-jemallocator" } diff --git a/tools/script/tquic_time_cwnd.py b/tools/script/tquic_time_cwnd.py new file mode 100755 index 00000000..9a0bbd8a --- /dev/null +++ b/tools/script/tquic_time_cwnd.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +# This tool is used to analyze TQUIC debug logs and produce a time-cwnd figure +# for the specified QUIC connection. + +import re + +from datetime import datetime +import argparse +import matplotlib.pyplot as plt + + +def parse_log(log_file, id): + with open(log_file, "r") as file: + log_data = file.readlines() + + timestamps = [] + inflights = [] + cwnds = [] + + # Refine the regular expression to match timestamps and cwnds + timestamp_pattern = re.compile(r"\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3})Z") + cwnd_format = r"{} [a-zA-Z]* BEGIN_ACK inflight=(\d+) cwnd=(\d+)" + cwnd_pattern = re.compile(cwnd_format.format(id)) + + for line in log_data: + timestamp_match = timestamp_pattern.search(line) + if not timestamp_match: + continue + + cwnd_match = cwnd_pattern.search(line) + if not cwnd_match: + continue + + timestamp_str = timestamp_match.group(1) + timestamp = datetime.strptime(timestamp_str, "%Y-%m-%dT%H:%M:%S.%f") + inflight = int(cwnd_match.group(1)) + cwnd = int(cwnd_match.group(2)) + timestamps.append(timestamp) + inflights.append(inflight) + cwnds.append(cwnd) + + return timestamps, inflights, cwnds + + +def plot_offsets(timestamps, inflights, cwnds, connection_path_id): + # Set output file name + ids = connection_path_id.split("-") + cid = ids[1] + pid = ids[2] + output_file_name = "tquic_time_cwnd_{}_{}.png".format(cid, pid) + + plt.figure(figsize=(20, 6)) + plt.plot(timestamps, inflights, label="inflight", linestyle="-", linewidth=0.5) + plt.plot(timestamps, cwnds, label="cwnd", linestyle="-", linewidth=0.5) + plt.xlabel("Time") + plt.ylabel("Cwnd/Inflight") + plt.title(f"Congestion window by Time in Connection {cid} Path {pid}") + plt.legend() + plt.gca().xaxis.set_major_formatter( + plt.matplotlib.dates.DateFormatter("%H:%M:%S.%f") + ) + plt.savefig(output_file_name) + print("Found %d items, figure %s" % (len(timestamps), output_file_name)) + + +if __name__ == "__main__": + # Set up the command line argument parser + parser = argparse.ArgumentParser( + description="Analyze TQUIC logs to get the relationship between cwnd/inflight and time." + ) + parser.add_argument( + "-l", + "--log_file", + type=str, + help="path to the TQUIC debug log file", + required=True, + ) + parser.add_argument( + "-c", + "--connection_path_id", + type=str, + help="connection path id, eg. SERVER-c6d45bc005585f42-0", + required=True, + ) + args = parser.parse_args() + + # Calling with command-line arguments + timestamps, inflights, cwnds = parse_log(args.log_file, args.connection_path_id) + plot_offsets(timestamps, inflights, cwnds, args.connection_path_id) diff --git a/tools/script/tquic_time_offset.py b/tools/script/tquic_time_offset.py index c0bd8f0d..5aa122e3 100755 --- a/tools/script/tquic_time_offset.py +++ b/tools/script/tquic_time_offset.py @@ -9,8 +9,11 @@ import argparse import matplotlib.pyplot as plt -STREAM_SEND_FORMAT=r"{} sent packet OneRTT.*?STREAM id={} off=(\d+) len=\d+ fin=(?:true|false)" -STREAM_RECV_FORMAT=r"{} recv frame STREAM id={} off=(\d+) len=\d+ fin=(?:true|false)" +STREAM_SEND_FORMAT = ( + r"{} sent packet OneRTT.*?STREAM id={} off=(\d+) len=\d+ fin=(?:true|false)" +) +STREAM_RECV_FORMAT = r"{} recv frame STREAM id={} off=(\d+) len=\d+ fin=(?:true|false)" + def parse_log(log_file, cid, stream_id, recv): with open(log_file, "r") as file: @@ -68,21 +71,29 @@ def plot_offsets(timestamps, offsets, connection_trace_id, stream_id): "-l", "--log_file", type=str, - help="Path to the TQUIC debug log file", + help="path to the TQUIC debug log file", required=True, ) parser.add_argument( "-c", "--connection_trace_id", type=str, - help="Connection trace id, eg. SERVER-c6d45bc005585f42", + help="connection trace id, eg. SERVER-c6d45bc005585f42", required=True, ) parser.add_argument( - "-s", "--stream_id", type=int, help="Stream id, eg. 0", required=True + "-s", + "--stream_id", + type=int, + help="stream id (default 0), eg. 0", + default=0, ) parser.add_argument( - "-r", "--recv", type=bool, help="Recv side instead of send side", default=False + "-r", + "--recv", + type=bool, + help="recv side instead of send side (default false)", + default=False, ) args = parser.parse_args()