Skip to content

Commit 403376a

Browse files
committed
support TFO on Linux, FreeBSD, macOS and Windows
- ref #184
1 parent 1d3e6b7 commit 403376a

File tree

35 files changed

+1761
-642
lines changed

35 files changed

+1761
-642
lines changed

.github/workflows/build-and-test.yml

+6
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,16 @@ jobs:
3737
components: clippy
3838
- name: Build & Test (Default)
3939
run: cargo test --verbose --no-fail-fast
40+
- name: Build & Test (Default) - shadowsocks
41+
run: cargo test --manifest-path ./crates/shadowsocks/Cargo.toml --verbose --no-fail-fast
4042
- name: Build & Test (--no-default-features)
4143
run: cargo test --verbose --no-default-features --no-fail-fast
44+
- name: Build & Test (--no-default-features) - shadowsocks
45+
run: cargo test --manifest-path ./crates/shadowsocks/Cargo.toml --verbose --no-default-features --no-fail-fast
4246
- name: Build with All Features Enabled
4347
run: cargo build --verbose --features "local-http-rustls local-redir local-dns dns-over-tls dns-over-https stream-cipher"
48+
- name: Build with All Features Enabled - shadowsocks
49+
run: cargo build --manifest-path ./crates/shadowsocks/Cargo.toml --verbose --features "stream-cipher"
4450
- name: Clippy Check
4551
uses: actions-rs/clippy-check@v1
4652
with:

Cargo.lock

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "shadowsocks-rust"
3-
version = "1.10.5"
3+
version = "1.11.0"
44
authors = ["Shadowsocks Contributors"]
55
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
66
repository = "https://github.com/shadowsocks/shadowsocks-rust"

bin/sslocal.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ fn main() {
6666
(@arg DNS: --dns +takes_value "DNS nameservers, formatted like [(tcp|udp)://]host[:port][,host[:port]]..., or unix:///path/to/dns, or predefined keys like \"google\", \"cloudflare\"")
6767

6868
(@arg TCP_NO_DELAY: --("tcp-no-delay") !takes_value alias("no-delay") "Set TCP_NODELAY option for socket")
69+
(@arg TCP_FAST_OPEN: --("tcp-fast-open") !takes_value alias("fast-open") "Enable TCP Fast Open (TFO)")
6970

7071
(@arg UDP_TIMEOUT: --("udp-timeout") +takes_value {validator::validate_u64} "Timeout seconds for UDP relay")
7172
(@arg UDP_MAX_ASSOCIATIONS: --("udp-max-associations") +takes_value {validator::validate_u64} "Maximum associations to be kept simultaneously for UDP relay")
7273

73-
7474
(@arg INBOUND_SEND_BUFFER_SIZE: --("inbound-send-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_SNDBUF option")
7575
(@arg INBOUND_RECV_BUFFER_SIZE: --("inbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_RCVBUF option")
7676
(@arg OUTBOUND_SEND_BUFFER_SIZE: --("outbound-send-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_SNDBUF option")
@@ -346,6 +346,10 @@ fn main() {
346346
config.no_delay = true;
347347
}
348348

349+
if matches.is_present("TCP_FAST_OPEN") {
350+
config.fast_open = true;
351+
}
352+
349353
#[cfg(any(target_os = "linux", target_os = "android"))]
350354
if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") {
351355
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));

bin/ssmanager.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ fn main() {
5151
(@arg BIND_ADDR: -b --("bind-addr") +takes_value {validator::validate_ip_addr} "Bind address, outbound socket will bind this address")
5252
(@arg SERVER_HOST: -s --("server-host") +takes_value "Host name or IP address of your remote server")
5353

54-
5554
(@arg MANAGER_ADDRESS: --("manager-address") +takes_value {validator::validate_manager_addr} "ShadowSocks Manager (ssmgr) address, could be ip:port, domain:port or /path/to/unix.sock")
5655
(@arg ENCRYPT_METHOD: -m --("encrypt-method") +takes_value possible_values(available_ciphers()) "Default encryption method")
5756
(@arg TIMEOUT: --timeout +takes_value {validator::validate_u64} "Default timeout seconds for TCP relay")
@@ -60,6 +59,7 @@ fn main() {
6059
(@arg DNS: --dns +takes_value "DNS nameservers, formatted like [(tcp|udp)://]host[:port][,host[:port]]..., or unix:///path/to/dns, or predefined keys like \"google\", \"cloudflare\"")
6160

6261
(@arg TCP_NO_DELAY: --("tcp-no-delay") !takes_value alias("no-delay") "Set TCP_NODELAY option for socket")
62+
(@arg TCP_FAST_OPEN: --("tcp-fast-open") !takes_value alias("fast-open") "Enable TCP Fast Open (TFO)")
6363

6464
(@arg UDP_TIMEOUT: --("udp-timeout") +takes_value {validator::validate_u64} "Timeout seconds for UDP relay")
6565
(@arg UDP_MAX_ASSOCIATIONS: --("udp-max-associations") +takes_value {validator::validate_u64} "Maximum associations to be kept simultaneously for UDP relay")
@@ -154,6 +154,10 @@ fn main() {
154154
config.no_delay = true;
155155
}
156156

157+
if matches.is_present("TCP_FAST_OPEN") {
158+
config.fast_open = true;
159+
}
160+
157161
#[cfg(any(target_os = "linux", target_os = "android"))]
158162
if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") {
159163
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));

bin/ssserver.rs

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ fn main() {
6161
(@arg DNS: --dns +takes_value "DNS nameservers, formatted like [(tcp|udp)://]host[:port][,host[:port]]..., or unix:///path/to/dns, or predefined keys like \"google\", \"cloudflare\"")
6262

6363
(@arg TCP_NO_DELAY: --("tcp-no-delay") !takes_value alias("no-delay") "Set TCP_NODELAY option for socket")
64+
(@arg TCP_FAST_OPEN: --("tcp-fast-open") !takes_value alias("fast-open") "Enable TCP Fast Open (TFO)")
6465

6566
(@arg UDP_TIMEOUT: --("udp-timeout") +takes_value {validator::validate_u64} "Timeout seconds for UDP relay")
6667
(@arg UDP_MAX_ASSOCIATIONS: --("udp-max-associations") +takes_value {validator::validate_u64} "Maximum associations to be kept simultaneously for UDP relay")
@@ -194,6 +195,10 @@ fn main() {
194195
config.no_delay = true;
195196
}
196197

198+
if matches.is_present("TCP_FAST_OPEN") {
199+
config.fast_open = true;
200+
}
201+
197202
#[cfg(any(target_os = "linux", target_os = "android"))]
198203
if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") {
199204
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));

crates/shadowsocks-service/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "shadowsocks-service"
3-
version = "1.10.4"
3+
version = "1.11.0"
44
authors = ["Shadowsocks Contributors"]
55
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
66
repository = "https://github.com/shadowsocks/shadowsocks-rust"

crates/shadowsocks-service/src/config.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@
4141
//!
4242
//! These defined server will be used with a load balancing algorithm.
4343
44-
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))]
45-
use std::ffi::OsString;
4644
#[cfg(any(unix, target_os = "android", feature = "local-flow-stat"))]
4745
use std::path::PathBuf;
4846
use std::{
@@ -130,6 +128,8 @@ struct SSConfig {
130128
nofile: Option<u64>,
131129
#[serde(skip_serializing_if = "Option::is_none")]
132130
ipv6_first: Option<bool>,
131+
#[serde(skip_serializing_if = "Option::is_none")]
132+
fast_open: Option<bool>,
133133
}
134134

135135
#[derive(Serialize, Deserialize, Debug, Default)]
@@ -720,6 +720,8 @@ pub struct Config {
720720

721721
/// Set `TCP_NODELAY` socket option
722722
pub no_delay: bool,
723+
/// Set `TCP_FASTOPEN` socket option
724+
pub fast_open: bool,
723725
/// `RLIMIT_NOFILE` option for *nix systems
724726
#[cfg(all(unix, not(target_os = "android")))]
725727
pub nofile: Option<u64>,
@@ -729,7 +731,7 @@ pub struct Config {
729731
pub outbound_fwmark: Option<u32>,
730732
/// Set `SO_BINDTODEVICE` socket option for outbound sockets
731733
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))]
732-
pub outbound_bind_interface: Option<OsString>,
734+
pub outbound_bind_interface: Option<String>,
733735
/// Path to protect callback unix address, only for Android
734736
#[cfg(target_os = "android")]
735737
pub outbound_vpn_protect_path: Option<PathBuf>,
@@ -833,6 +835,7 @@ impl Config {
833835
ipv6_first: false,
834836

835837
no_delay: false,
838+
fast_open: false,
836839
#[cfg(all(unix, not(target_os = "android")))]
837840
nofile: None,
838841

@@ -1280,6 +1283,11 @@ impl Config {
12801283
nconfig.no_delay = b;
12811284
}
12821285

1286+
// TCP fast open
1287+
if let Some(b) = config.fast_open {
1288+
nconfig.fast_open = b;
1289+
}
1290+
12831291
// UDP
12841292
nconfig.udp_timeout = config.udp_timeout.map(Duration::from_secs);
12851293

@@ -1762,6 +1770,10 @@ impl fmt::Display for Config {
17621770
jconf.no_delay = Some(self.no_delay);
17631771
}
17641772

1773+
if self.fast_open {
1774+
jconf.fast_open = Some(self.fast_open);
1775+
}
1776+
17651777
match self.dns {
17661778
DnsConfig::System => {}
17671779
#[cfg(feature = "trust-dns")]

crates/shadowsocks-service/src/local/dns/upstream.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use shadowsocks::{
1818
use tokio::net::UnixStream;
1919
use tokio::{
2020
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
21-
net::{TcpStream, UdpSocket},
21+
net::UdpSocket,
2222
time,
2323
};
2424
use trust_dns_resolver::proto::{
@@ -31,7 +31,7 @@ use crate::net::{FlowStat, MonProxySocket, MonProxyStream};
3131
/// Collection of various DNS connections
3232
pub enum DnsClient {
3333
TcpLocal {
34-
stream: TcpStream,
34+
stream: ShadowTcpStream,
3535
},
3636
UdpLocal {
3737
socket: UdpSocket,
@@ -42,7 +42,7 @@ pub enum DnsClient {
4242
stream: UnixStream,
4343
},
4444
TcpRemote {
45-
stream: ProxyClientStream<MonProxyStream<TcpStream>>,
45+
stream: ProxyClientStream<MonProxyStream<ShadowTcpStream>>,
4646
},
4747
UdpRemote {
4848
socket: MonProxySocket,
@@ -53,7 +53,7 @@ pub enum DnsClient {
5353
impl DnsClient {
5454
/// Connect to local provided TCP DNS server
5555
pub async fn connect_tcp_local(ns: SocketAddr, connect_opts: &ConnectOpts) -> io::Result<DnsClient> {
56-
let stream = ShadowTcpStream::connect_with_opts(&ns, connect_opts).await?.into();
56+
let stream = ShadowTcpStream::connect_with_opts(&ns, connect_opts).await?;
5757
Ok(DnsClient::TcpLocal { stream })
5858
}
5959

crates/shadowsocks-service/src/local/mod.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use futures::{
99
stream::{FuturesUnordered, StreamExt},
1010
FutureExt,
1111
};
12-
use log::{error, trace, warn};
12+
use log::{error, trace};
1313
use shadowsocks::{
1414
config::Mode,
1515
net::{AcceptOpts, ConnectOpts},
@@ -53,7 +53,7 @@ pub async fn run(mut config: Config) -> io::Result<()> {
5353
#[cfg(feature = "stream-cipher")]
5454
for server in config.server.iter() {
5555
if server.method().is_stream() {
56-
warn!("stream cipher {} for server {} have inherent weaknesses (see discussion in https://github.com/shadowsocks/shadowsocks-org/issues/36). \
56+
log::warn!("stream cipher {} for server {} have inherent weaknesses (see discussion in https://github.com/shadowsocks/shadowsocks-org/issues/36). \
5757
DO NOT USE. It will be removed in the future.", server.method(), server.addr());
5858
}
5959
}
@@ -62,7 +62,7 @@ pub async fn run(mut config: Config) -> io::Result<()> {
6262
if let Some(nofile) = config.nofile {
6363
use crate::sys::set_nofile;
6464
if let Err(err) = set_nofile(nofile) {
65-
warn!("set_nofile {} failed, error: {}", nofile, err);
65+
log::warn!("set_nofile {} failed, error: {}", nofile, err);
6666
}
6767
}
6868

@@ -82,12 +82,14 @@ pub async fn run(mut config: Config) -> io::Result<()> {
8282
};
8383
connect_opts.tcp.send_buffer_size = config.outbound_send_buffer_size;
8484
connect_opts.tcp.recv_buffer_size = config.outbound_recv_buffer_size;
85+
connect_opts.tcp.fastopen = config.fast_open;
8586
context.set_connect_opts(connect_opts);
8687

8788
let mut accept_opts = AcceptOpts::default();
8889
accept_opts.tcp.send_buffer_size = config.inbound_send_buffer_size;
8990
accept_opts.tcp.recv_buffer_size = config.inbound_recv_buffer_size;
9091
accept_opts.tcp.nodelay = config.no_delay;
92+
accept_opts.tcp.fastopen = config.fast_open;
9193

9294
if let Some(resolver) = build_dns_resolver(config.dns, config.ipv6_first, context.connect_opts_ref()).await {
9395
context.set_dns_resolver(Arc::new(resolver));

crates/shadowsocks-service/src/local/net/tcp/auto_proxy_stream.rs

+10-16
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,7 @@ use shadowsocks::{
1616
tcprelay::proxy_stream::{ProxyClientStream, ProxyClientStreamReadHalf, ProxyClientStreamWriteHalf},
1717
},
1818
};
19-
use tokio::{
20-
io::{AsyncRead, AsyncWrite, ReadBuf},
21-
net::{
22-
tcp::{OwnedReadHalf, OwnedWriteHalf},
23-
TcpStream as TokioTcpStream,
24-
},
25-
};
19+
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf, ReadHalf, WriteHalf};
2620

2721
use crate::{
2822
local::{context::ServiceContext, loadbalancing::ServerIdent},
@@ -34,8 +28,8 @@ use super::auto_proxy_io::AutoProxyIo;
3428
/// Unified stream for bypassed and proxied connections
3529
#[pin_project(project = AutoProxyClientStreamProj)]
3630
pub enum AutoProxyClientStream {
37-
Proxied(#[pin] ProxyClientStream<MonProxyStream<TokioTcpStream>>),
38-
Bypassed(#[pin] TokioTcpStream),
31+
Proxied(#[pin] ProxyClientStream<MonProxyStream<TcpStream>>),
32+
Bypassed(#[pin] TcpStream),
3933
}
4034

4135
impl AutoProxyClientStream {
@@ -160,8 +154,8 @@ impl AsyncWrite for AutoProxyClientStream {
160154
}
161155
}
162156

163-
impl From<ProxyClientStream<MonProxyStream<TokioTcpStream>>> for AutoProxyClientStream {
164-
fn from(s: ProxyClientStream<MonProxyStream<TokioTcpStream>>) -> Self {
157+
impl From<ProxyClientStream<MonProxyStream<TcpStream>>> for AutoProxyClientStream {
158+
fn from(s: ProxyClientStream<MonProxyStream<TcpStream>>) -> Self {
165159
AutoProxyClientStream::Proxied(s)
166160
}
167161
}
@@ -177,7 +171,7 @@ impl AutoProxyClientStream {
177171
)
178172
}
179173
AutoProxyClientStream::Bypassed(s) => {
180-
let (r, w) = s.into_split();
174+
let (r, w) = tokio::io::split(s);
181175
(
182176
AutoProxyClientStreamReadHalf::Bypassed(r),
183177
AutoProxyClientStreamWriteHalf::Bypassed(w),
@@ -189,8 +183,8 @@ impl AutoProxyClientStream {
189183

190184
#[pin_project(project = AutoProxyClientStreamReadHalfProj)]
191185
pub enum AutoProxyClientStreamReadHalf {
192-
Proxied(#[pin] ProxyClientStreamReadHalf<MonProxyStream<TokioTcpStream>>),
193-
Bypassed(#[pin] OwnedReadHalf),
186+
Proxied(#[pin] ProxyClientStreamReadHalf<MonProxyStream<TcpStream>>),
187+
Bypassed(#[pin] ReadHalf<TcpStream>),
194188
}
195189

196190
impl AutoProxyIo for AutoProxyClientStreamReadHalf {
@@ -210,8 +204,8 @@ impl AsyncRead for AutoProxyClientStreamReadHalf {
210204

211205
#[pin_project(project = AutoProxyClientStreamWriteHalfProj)]
212206
pub enum AutoProxyClientStreamWriteHalf {
213-
Proxied(#[pin] ProxyClientStreamWriteHalf<MonProxyStream<TokioTcpStream>>),
214-
Bypassed(#[pin] OwnedWriteHalf),
207+
Proxied(#[pin] ProxyClientStreamWriteHalf<MonProxyStream<TcpStream>>),
208+
Bypassed(#[pin] WriteHalf<TcpStream>),
215209
}
216210

217211
impl AutoProxyIo for AutoProxyClientStreamWriteHalf {

crates/shadowsocks-service/src/manager/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ pub async fn run(config: Config) -> io::Result<()> {
5050
connect_opts.tcp.send_buffer_size = config.outbound_send_buffer_size;
5151
connect_opts.tcp.recv_buffer_size = config.outbound_recv_buffer_size;
5252
connect_opts.tcp.nodelay = config.no_delay;
53+
connect_opts.tcp.fastopen = config.fast_open;
5354

5455
let mut accept_opts = AcceptOpts::default();
5556
accept_opts.tcp.send_buffer_size = config.inbound_send_buffer_size;
5657
accept_opts.tcp.recv_buffer_size = config.inbound_recv_buffer_size;
5758
accept_opts.tcp.nodelay = config.no_delay;
59+
accept_opts.tcp.fastopen = config.fast_open;
5860

5961
if let Some(resolver) = build_dns_resolver(config.dns, config.ipv6_first, &connect_opts).await {
6062
manager.set_dns_resolver(Arc::new(resolver));

crates/shadowsocks-service/src/server/mod.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::{io, sync::Arc};
44

55
use futures::{future, FutureExt};
6-
use log::{trace, warn};
6+
use log::trace;
77
use shadowsocks::net::{AcceptOpts, ConnectOpts};
88

99
use crate::{
@@ -30,7 +30,7 @@ pub async fn run(config: Config) -> io::Result<()> {
3030
#[cfg(feature = "stream-cipher")]
3131
for server in config.server.iter() {
3232
if server.method().is_stream() {
33-
warn!("stream cipher {} for server {} have inherent weaknesses (see discussion in https://github.com/shadowsocks/shadowsocks-org/issues/36). \
33+
log::warn!("stream cipher {} for server {} have inherent weaknesses (see discussion in https://github.com/shadowsocks/shadowsocks-org/issues/36). \
3434
DO NOT USE. It will be removed in the future.", server.method(), server.addr());
3535
}
3636
}
@@ -39,7 +39,7 @@ pub async fn run(config: Config) -> io::Result<()> {
3939
if let Some(nofile) = config.nofile {
4040
use crate::sys::set_nofile;
4141
if let Err(err) = set_nofile(nofile) {
42-
warn!("set_nofile {} failed, error: {}", nofile, err);
42+
log::warn!("set_nofile {} failed, error: {}", nofile, err);
4343
}
4444
}
4545

@@ -63,11 +63,13 @@ pub async fn run(config: Config) -> io::Result<()> {
6363
connect_opts.tcp.send_buffer_size = config.outbound_send_buffer_size;
6464
connect_opts.tcp.recv_buffer_size = config.outbound_recv_buffer_size;
6565
connect_opts.tcp.nodelay = config.no_delay;
66+
connect_opts.tcp.fastopen = config.fast_open;
6667

6768
let mut accept_opts = AcceptOpts::default();
6869
accept_opts.tcp.send_buffer_size = config.inbound_send_buffer_size;
6970
accept_opts.tcp.recv_buffer_size = config.inbound_recv_buffer_size;
7071
accept_opts.tcp.nodelay = config.no_delay;
72+
accept_opts.tcp.fastopen = config.fast_open;
7173

7274
let resolver = match build_dns_resolver(config.dns, config.ipv6_first, &connect_opts).await {
7375
Some(resolver) => Some(Arc::new(resolver)),

0 commit comments

Comments
 (0)