From c97ca83a863b16df589d462706e94cffd2ba4eae Mon Sep 17 00:00:00 2001 From: hypnosis Date: Sun, 19 Jul 2026 15:11:18 +0300 Subject: [PATCH] tcp: expose advertised MSS via --tcp-mss ipstack sends its SYN-ACK without a MSS option, so the peer OS falls back to its own default (512 on macOS) and fragments large first segments such as a TLS ClientHello. Downstream consumers that sniff the SNI from the first read then miss the domain. Wire a new optional Args.tcp_mss into ipstack::TcpConfig.options (mirrors how --tcp-timeout sets TcpConfig.timeout). Default None keeps the previous behaviour, so this is a runtime no-op unless the flag is set. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 1 + src/args.rs | 9 +++++++++ src/lib.rs | 3 +++ 3 files changed, 13 insertions(+) diff --git a/README.md b/README.md index 7907b15..10565e9 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,7 @@ Options: -b, --bypass IPs used in routing setup which should bypass the tunnel, in the form of IP or IP/CIDR. Multiple IPs can be specified, e.g. --bypass 3.4.5.0/24 --bypass 5.6.7.8 --tcp-timeout TCP timeout in seconds [default: 600] + --tcp-mss MSS advertised in the SYN-ACK to the kernel side [default: none] --udp-timeout UDP timeout in seconds [default: 10] -v, --verbosity Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace] --daemonize Daemonize for unix family or run as Windows service diff --git a/src/args.rs b/src/args.rs index 27bffe3..4981677 100644 --- a/src/args.rs +++ b/src/args.rs @@ -116,6 +116,14 @@ pub struct Args { #[arg(long, value_name = "seconds", default_value = "600")] pub tcp_timeout: u64, + /// MSS advertised in the SYN-ACK to the kernel side. By default the userspace + /// stack sends no MSS option, so the peer OS falls back to its own default + /// (e.g. 512 on macOS), fragmenting large first segments such as a TLS + /// ClientHello. Set this to the tunnel's (MTU - 40) to let the peer send big + /// segments. `None` keeps the previous behaviour (no option advertised). + #[arg(long, value_name = "bytes")] + pub tcp_mss: Option, + /// UDP timeout in seconds #[arg(long, value_name = "seconds", default_value = "10")] pub udp_timeout: u64, @@ -191,6 +199,7 @@ impl Default for Args { bypass: vec![], mtu: tun::DEFAULT_MTU, tcp_timeout: 600, + tcp_mss: None, udp_timeout: 10, verbosity: ArgVerbosity::Info, virtual_dns_pool: IpCidr::from_str("198.18.0.0/15").unwrap(), diff --git a/src/lib.rs b/src/lib.rs index ade5305..60da91c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -243,6 +243,9 @@ where ipstack_config.mtu(mtu)?; let mut tcp_cfg = ipstack::TcpConfig::default(); tcp_cfg.timeout = std::time::Duration::from_secs(args.tcp_timeout); + if let Some(mss) = args.tcp_mss { + tcp_cfg.options = Some(vec![ipstack::TcpOptions::MaximumSegmentSize(mss)]); + } ipstack_config.with_tcp_config(tcp_cfg); ipstack_config.udp_timeout(std::time::Duration::from_secs(args.udp_timeout));