Skip to content

support TFO on Linux, FreeBSD, macOS and Windows #487

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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