Skip to content

Commit df613b4

Browse files
authored
feat(connect): add TCP_USER_TIMEOUT option for Linux sockets (#213)
1 parent 39c4e85 commit df613b4

File tree

3 files changed

+55
-29
lines changed

3 files changed

+55
-29
lines changed

src/connect.rs

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,13 @@ pub enum TargetAddr {
3939
/// with an IPv6 CIDR and a fallback IP address.
4040
#[derive(Clone)]
4141
pub struct Connector {
42-
/// Optional IPv6 CIDR (Classless Inter-Domain Routing), used to optionally
43-
/// configure an IPv6 address.
4442
cidr: Option<IpCidr>,
45-
46-
/// Optional CIDR range for IP addresses.
4743
cidr_range: Option<u8>,
48-
49-
/// Optional IP address as a fallback option in case of connection failure.
5044
fallback: Option<Fallback>,
51-
52-
/// Connect timeout in milliseconds.
5345
connect_timeout: Duration,
54-
55-
/// Enable SO_REUSEADDR for outbond connection socket
46+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
47+
tcp_user_timeout: Option<Duration>,
5648
reuseaddr: Option<bool>,
57-
58-
/// Default http connector
5949
http: connect::HttpConnector,
6050
}
6151

@@ -134,6 +124,8 @@ impl Connector {
134124
cidr_range: Option<u8>,
135125
fallback: Option<Fallback>,
136126
connect_timeout: u64,
127+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
128+
tcp_user_timeout: Option<u64>,
137129
reuseaddr: Option<bool>,
138130
) -> Self {
139131
let connect_timeout = Duration::from_secs(connect_timeout);
@@ -147,8 +139,10 @@ impl Connector {
147139
cidr_range,
148140
fallback,
149141
connect_timeout,
150-
http: http_connector,
142+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
143+
tcp_user_timeout: tcp_user_timeout.map(Duration::from_secs),
151144
reuseaddr,
145+
http: http_connector,
152146
}
153147
}
154148

@@ -204,20 +198,28 @@ impl TcpConnector<'_> {
204198

205199
/// Creates a [`TcpSocket`] and binds it to an IP address within the provided CIDR range.
206200
async fn create_socket_with_cidr(&self, cidr: IpCidr) -> std::io::Result<TcpSocket> {
207-
match cidr {
201+
let socket = match cidr {
208202
IpCidr::V4(cidr) => {
209203
let socket = TcpSocket::new_v4()?;
210204
let addr = assign_ipv4_from_extension(cidr, self.inner.cidr_range, self.extension);
211205
socket.bind(SocketAddr::new(IpAddr::V4(addr), 0))?;
212-
Ok(socket)
206+
socket
213207
}
214208
IpCidr::V6(cidr) => {
215209
let socket = TcpSocket::new_v6()?;
216210
let addr = assign_ipv6_from_extension(cidr, self.inner.cidr_range, self.extension);
217211
socket.bind(SocketAddr::new(IpAddr::V6(addr), 0))?;
218-
Ok(socket)
212+
socket
219213
}
214+
};
215+
216+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
217+
if let Some(tcp_user_timeout) = self.inner.tcp_user_timeout {
218+
let socket_ref = socket2::SockRef::from(&socket);
219+
socket_ref.set_tcp_user_timeout(Some(tcp_user_timeout))?;
220220
}
221+
222+
Ok(socket)
221223
}
222224

223225
/// Creates a [`TcpSocket`] and binds it to the fallback address.
@@ -289,10 +291,17 @@ impl TcpConnector<'_> {
289291
};
290292

291293
socket.set_nodelay(true)?;
294+
292295
if let Some(reuseaddr) = self.inner.reuseaddr {
293296
socket.set_reuseaddr(reuseaddr)?;
294297
}
295298

299+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
300+
if let Some(tcp_user_timeout) = self.inner.tcp_user_timeout {
301+
let socket_ref = socket2::SockRef::from(&socket);
302+
socket_ref.set_tcp_user_timeout(Some(tcp_user_timeout))?;
303+
}
304+
296305
Ok(socket)
297306
}
298307

@@ -672,10 +681,16 @@ impl HttpConnector<'_> {
672681
}
673682

674683
connector.set_nodelay(true);
684+
675685
if let Some(reuseaddr) = self.inner.reuseaddr {
676686
connector.set_reuse_address(reuseaddr);
677687
}
678688

689+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
690+
if let Some(tcp_user_timeout) = self.inner.tcp_user_timeout {
691+
connector.set_tcp_user_timeout(Some(tcp_user_timeout));
692+
}
693+
679694
Client::builder(TokioExecutor::new())
680695
.timer(TokioTimer::new())
681696
.http1_title_case_headers(true)

src/main.rs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,6 @@ pub struct BootArgs {
160160
)]
161161
log: Level,
162162

163-
/// Worker thread count. Default: number of logical CPU cores.
164-
/// Too small limits concurrency; too large wastes context switches.
165-
#[arg(long, short = 'w', verbatim_doc_comment)]
166-
workers: Option<usize>,
167-
168163
/// Bind address (listen endpoint).
169164
/// e.g. 0.0.0.0:1080, [::]:1080, 192.168.1.100:1080
170165
#[arg(
@@ -175,6 +170,17 @@ pub struct BootArgs {
175170
)]
176171
bind: SocketAddr,
177172

173+
/// Maximum concurrent active connections.
174+
/// Protects resource exhaustion. Raise cautiously.
175+
/// e.g. 128.
176+
#[arg(long, short = 'c', default_value = "1024", verbatim_doc_comment)]
177+
concurrent: u32,
178+
179+
/// Worker thread count. Default: number of logical CPU cores.
180+
/// Too small limits concurrency; too large wastes context switches.
181+
#[arg(long, short = 'w', verbatim_doc_comment)]
182+
workers: Option<usize>,
183+
178184
/// Base CIDR block for outbound source address selection.
179185
/// Used for session, TTL and range extensions.
180186
/// e.g. 2001:db8::/32 or 10.0.0.0/24
@@ -200,14 +206,17 @@ pub struct BootArgs {
200206
#[arg(long, short = 't', default_value = "10", verbatim_doc_comment)]
201207
connect_timeout: u64,
202208

203-
/// Maximum concurrent active connections.
204-
/// Protects resource exhaustion. Raise cautiously.
205-
/// e.g. 128.
206-
#[arg(long, short = 'c', default_value = "1024", verbatim_doc_comment)]
207-
concurrent: u32,
208-
209-
/// Enable SO_REUSEADDR for outbound sockets.
210-
/// Helps mitigate TIME_WAIT port exhaustion.
209+
/// Outbound TCP sockets user timeout (seconds).
210+
/// Maximum time transmitted data may remain unacknowledged before aborting the connection.
211+
/// Not a keepalive: idle connections without in-flight data are unaffected.
212+
/// Linux only. Kernel expects milliseconds; this value is converted from seconds.
213+
/// e.g. 15.
214+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
215+
#[arg(long, default_value = "30", verbatim_doc_comment)]
216+
tcp_user_timeout: Option<u64>,
217+
218+
/// Outbound SO_REUSEADDR for TCP sockets.
219+
/// Helps mitigate TIME_WAIT port exhaustion and enables fast rebinding after restarts.
211220
/// e.g. true.
212221
#[arg(long, default_value = "true", verbatim_doc_comment)]
213222
reuseaddr: Option<bool>,

src/server.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ pub fn run(args: BootArgs) -> Result<()> {
9797
args.cidr_range,
9898
args.fallback,
9999
args.connect_timeout,
100+
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
101+
args.tcp_user_timeout,
100102
args.reuseaddr,
101103
),
102104
};

0 commit comments

Comments
 (0)