Skip to content

Commit

Permalink
support TFO on Linux, FreeBSD, macOS and Windows (#487)
Browse files Browse the repository at this point in the history
- ref #184
  • Loading branch information
zonyitoo authored Apr 25, 2021
1 parent 3d2ff1f commit 8a27bb2
Show file tree
Hide file tree
Showing 35 changed files with 1,761 additions and 642 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,16 @@ jobs:
components: clippy
- name: Build & Test (Default)
run: cargo test --verbose --no-fail-fast
- name: Build & Test (Default) - shadowsocks
run: cargo test --manifest-path ./crates/shadowsocks/Cargo.toml --verbose --no-fail-fast
- name: Build & Test (--no-default-features)
run: cargo test --verbose --no-default-features --no-fail-fast
- name: Build & Test (--no-default-features) - shadowsocks
run: cargo test --manifest-path ./crates/shadowsocks/Cargo.toml --verbose --no-default-features --no-fail-fast
- name: Build with All Features Enabled
run: cargo build --verbose --features "local-http-rustls local-redir local-dns dns-over-tls dns-over-https stream-cipher"
- name: Build with All Features Enabled - shadowsocks
run: cargo build --manifest-path ./crates/shadowsocks/Cargo.toml --verbose --features "stream-cipher"
- name: Clippy Check
uses: actions-rs/clippy-check@v1
with:
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "shadowsocks-rust"
version = "1.10.9"
version = "1.11.0"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"
Expand Down
6 changes: 5 additions & 1 deletion bin/sslocal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ fn main() {
(@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\"")

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

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


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

if matches.is_present("TCP_FAST_OPEN") {
config.fast_open = true;
}

#[cfg(any(target_os = "linux", target_os = "android"))]
if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") {
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));
Expand Down
6 changes: 5 additions & 1 deletion bin/ssmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ fn main() {
(@arg BIND_ADDR: -b --("bind-addr") +takes_value {validator::validate_ip_addr} "Bind address, outbound socket will bind this address")
(@arg SERVER_HOST: -s --("server-host") +takes_value "Host name or IP address of your remote server")


(@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")
(@arg ENCRYPT_METHOD: -m --("encrypt-method") +takes_value possible_values(available_ciphers()) "Default encryption method")
(@arg TIMEOUT: --timeout +takes_value {validator::validate_u64} "Default timeout seconds for TCP relay")
Expand All @@ -60,6 +59,7 @@ fn main() {
(@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\"")

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

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

if matches.is_present("TCP_FAST_OPEN") {
config.fast_open = true;
}

#[cfg(any(target_os = "linux", target_os = "android"))]
if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") {
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));
Expand Down
5 changes: 5 additions & 0 deletions bin/ssserver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ fn main() {
(@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\"")

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

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

if matches.is_present("TCP_FAST_OPEN") {
config.fast_open = true;
}

#[cfg(any(target_os = "linux", target_os = "android"))]
if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") {
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));
Expand Down
2 changes: 1 addition & 1 deletion crates/shadowsocks-service/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "shadowsocks-service"
version = "1.10.6"
version = "1.11.0"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"
Expand Down
18 changes: 15 additions & 3 deletions crates/shadowsocks-service/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@
//!
//! These defined server will be used with a load balancing algorithm.

#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))]
use std::ffi::OsString;
#[cfg(any(unix, target_os = "android", feature = "local-flow-stat"))]
use std::path::PathBuf;
use std::{
Expand Down Expand Up @@ -130,6 +128,8 @@ struct SSConfig {
nofile: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
ipv6_first: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
fast_open: Option<bool>,
}

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

/// Set `TCP_NODELAY` socket option
pub no_delay: bool,
/// Set `TCP_FASTOPEN` socket option
pub fast_open: bool,
/// `RLIMIT_NOFILE` option for *nix systems
#[cfg(all(unix, not(target_os = "android")))]
pub nofile: Option<u64>,
Expand All @@ -729,7 +731,7 @@ pub struct Config {
pub outbound_fwmark: Option<u32>,
/// Set `SO_BINDTODEVICE` socket option for outbound sockets
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))]
pub outbound_bind_interface: Option<OsString>,
pub outbound_bind_interface: Option<String>,
/// Path to protect callback unix address, only for Android
#[cfg(target_os = "android")]
pub outbound_vpn_protect_path: Option<PathBuf>,
Expand Down Expand Up @@ -833,6 +835,7 @@ impl Config {
ipv6_first: false,

no_delay: false,
fast_open: false,
#[cfg(all(unix, not(target_os = "android")))]
nofile: None,

Expand Down Expand Up @@ -1280,6 +1283,11 @@ impl Config {
nconfig.no_delay = b;
}

// TCP fast open
if let Some(b) = config.fast_open {
nconfig.fast_open = b;
}

// UDP
nconfig.udp_timeout = config.udp_timeout.map(Duration::from_secs);

Expand Down Expand Up @@ -1755,6 +1763,10 @@ impl fmt::Display for Config {
jconf.no_delay = Some(self.no_delay);
}

if self.fast_open {
jconf.fast_open = Some(self.fast_open);
}

match self.dns {
DnsConfig::System => {}
#[cfg(feature = "trust-dns")]
Expand Down
8 changes: 4 additions & 4 deletions crates/shadowsocks-service/src/local/dns/upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use shadowsocks::{
use tokio::net::UnixStream;
use tokio::{
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
net::{TcpStream, UdpSocket},
net::UdpSocket,
time,
};
use trust_dns_resolver::proto::{
Expand All @@ -32,7 +32,7 @@ use crate::net::{FlowStat, MonProxySocket, MonProxyStream};
#[allow(clippy::large_enum_variant)]
pub enum DnsClient {
TcpLocal {
stream: TcpStream,
stream: ShadowTcpStream,
},
UdpLocal {
socket: UdpSocket,
Expand All @@ -43,7 +43,7 @@ pub enum DnsClient {
stream: UnixStream,
},
TcpRemote {
stream: ProxyClientStream<MonProxyStream<TcpStream>>,
stream: ProxyClientStream<MonProxyStream<ShadowTcpStream>>,
},
UdpRemote {
socket: MonProxySocket,
Expand All @@ -54,7 +54,7 @@ pub enum DnsClient {
impl DnsClient {
/// Connect to local provided TCP DNS server
pub async fn connect_tcp_local(ns: SocketAddr, connect_opts: &ConnectOpts) -> io::Result<DnsClient> {
let stream = ShadowTcpStream::connect_with_opts(&ns, connect_opts).await?.into();
let stream = ShadowTcpStream::connect_with_opts(&ns, connect_opts).await?;
Ok(DnsClient::TcpLocal { stream })
}

Expand Down
8 changes: 5 additions & 3 deletions crates/shadowsocks-service/src/local/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use futures::{
stream::{FuturesUnordered, StreamExt},
FutureExt,
};
use log::{error, trace, warn};
use log::{error, trace};
use shadowsocks::{
config::Mode,
net::{AcceptOpts, ConnectOpts},
Expand Down Expand Up @@ -53,7 +53,7 @@ pub async fn run(mut config: Config) -> io::Result<()> {
#[cfg(feature = "stream-cipher")]
for server in config.server.iter() {
if server.method().is_stream() {
warn!("stream cipher {} for server {} have inherent weaknesses (see discussion in https://github.com/shadowsocks/shadowsocks-org/issues/36). \
log::warn!("stream cipher {} for server {} have inherent weaknesses (see discussion in https://github.com/shadowsocks/shadowsocks-org/issues/36). \
DO NOT USE. It will be removed in the future.", server.method(), server.addr());
}
}
Expand All @@ -62,7 +62,7 @@ pub async fn run(mut config: Config) -> io::Result<()> {
if let Some(nofile) = config.nofile {
use crate::sys::set_nofile;
if let Err(err) = set_nofile(nofile) {
warn!("set_nofile {} failed, error: {}", nofile, err);
log::warn!("set_nofile {} failed, error: {}", nofile, err);
}
}

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

let mut accept_opts = AcceptOpts::default();
accept_opts.tcp.send_buffer_size = config.inbound_send_buffer_size;
accept_opts.tcp.recv_buffer_size = config.inbound_recv_buffer_size;
accept_opts.tcp.nodelay = config.no_delay;
accept_opts.tcp.fastopen = config.fast_open;

if let Some(resolver) = build_dns_resolver(config.dns, config.ipv6_first, context.connect_opts_ref()).await {
context.set_dns_resolver(Arc::new(resolver));
Expand Down
26 changes: 10 additions & 16 deletions crates/shadowsocks-service/src/local/net/tcp/auto_proxy_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@ use shadowsocks::{
tcprelay::proxy_stream::{ProxyClientStream, ProxyClientStreamReadHalf, ProxyClientStreamWriteHalf},
},
};
use tokio::{
io::{AsyncRead, AsyncWrite, ReadBuf},
net::{
tcp::{OwnedReadHalf, OwnedWriteHalf},
TcpStream as TokioTcpStream,
},
};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf, ReadHalf, WriteHalf};

use crate::{
local::{context::ServiceContext, loadbalancing::ServerIdent},
Expand All @@ -34,8 +28,8 @@ use super::auto_proxy_io::AutoProxyIo;
/// Unified stream for bypassed and proxied connections
#[pin_project(project = AutoProxyClientStreamProj)]
pub enum AutoProxyClientStream {
Proxied(#[pin] ProxyClientStream<MonProxyStream<TokioTcpStream>>),
Bypassed(#[pin] TokioTcpStream),
Proxied(#[pin] ProxyClientStream<MonProxyStream<TcpStream>>),
Bypassed(#[pin] TcpStream),
}

impl AutoProxyClientStream {
Expand Down Expand Up @@ -160,8 +154,8 @@ impl AsyncWrite for AutoProxyClientStream {
}
}

impl From<ProxyClientStream<MonProxyStream<TokioTcpStream>>> for AutoProxyClientStream {
fn from(s: ProxyClientStream<MonProxyStream<TokioTcpStream>>) -> Self {
impl From<ProxyClientStream<MonProxyStream<TcpStream>>> for AutoProxyClientStream {
fn from(s: ProxyClientStream<MonProxyStream<TcpStream>>) -> Self {
AutoProxyClientStream::Proxied(s)
}
}
Expand All @@ -177,7 +171,7 @@ impl AutoProxyClientStream {
)
}
AutoProxyClientStream::Bypassed(s) => {
let (r, w) = s.into_split();
let (r, w) = tokio::io::split(s);
(
AutoProxyClientStreamReadHalf::Bypassed(r),
AutoProxyClientStreamWriteHalf::Bypassed(w),
Expand All @@ -189,8 +183,8 @@ impl AutoProxyClientStream {

#[pin_project(project = AutoProxyClientStreamReadHalfProj)]
pub enum AutoProxyClientStreamReadHalf {
Proxied(#[pin] ProxyClientStreamReadHalf<MonProxyStream<TokioTcpStream>>),
Bypassed(#[pin] OwnedReadHalf),
Proxied(#[pin] ProxyClientStreamReadHalf<MonProxyStream<TcpStream>>),
Bypassed(#[pin] ReadHalf<TcpStream>),
}

impl AutoProxyIo for AutoProxyClientStreamReadHalf {
Expand All @@ -210,8 +204,8 @@ impl AsyncRead for AutoProxyClientStreamReadHalf {

#[pin_project(project = AutoProxyClientStreamWriteHalfProj)]
pub enum AutoProxyClientStreamWriteHalf {
Proxied(#[pin] ProxyClientStreamWriteHalf<MonProxyStream<TokioTcpStream>>),
Bypassed(#[pin] OwnedWriteHalf),
Proxied(#[pin] ProxyClientStreamWriteHalf<MonProxyStream<TcpStream>>),
Bypassed(#[pin] WriteHalf<TcpStream>),
}

impl AutoProxyIo for AutoProxyClientStreamWriteHalf {
Expand Down
2 changes: 2 additions & 0 deletions crates/shadowsocks-service/src/manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@ pub async fn run(config: Config) -> io::Result<()> {
connect_opts.tcp.send_buffer_size = config.outbound_send_buffer_size;
connect_opts.tcp.recv_buffer_size = config.outbound_recv_buffer_size;
connect_opts.tcp.nodelay = config.no_delay;
connect_opts.tcp.fastopen = config.fast_open;

let mut accept_opts = AcceptOpts::default();
accept_opts.tcp.send_buffer_size = config.inbound_send_buffer_size;
accept_opts.tcp.recv_buffer_size = config.inbound_recv_buffer_size;
accept_opts.tcp.nodelay = config.no_delay;
accept_opts.tcp.fastopen = config.fast_open;

if let Some(resolver) = build_dns_resolver(config.dns, config.ipv6_first, &connect_opts).await {
manager.set_dns_resolver(Arc::new(resolver));
Expand Down
8 changes: 5 additions & 3 deletions crates/shadowsocks-service/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::{io, sync::Arc};

use futures::{future, FutureExt};
use log::{trace, warn};
use log::trace;
use shadowsocks::net::{AcceptOpts, ConnectOpts};

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

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

let mut accept_opts = AcceptOpts::default();
accept_opts.tcp.send_buffer_size = config.inbound_send_buffer_size;
accept_opts.tcp.recv_buffer_size = config.inbound_recv_buffer_size;
accept_opts.tcp.nodelay = config.no_delay;
accept_opts.tcp.fastopen = config.fast_open;

let resolver = build_dns_resolver(config.dns, config.ipv6_first, &connect_opts)
.await
Expand Down
Loading

0 comments on commit 8a27bb2

Please sign in to comment.