diff --git a/Cargo.lock b/Cargo.lock index c432be43..967e7637 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1694,7 +1694,7 @@ dependencies = [ [[package]] name = "shadowsocks-service" -version = "1.12.2" +version = "1.12.3" dependencies = [ "arc-swap 1.5.0", "async-trait", diff --git a/README.md b/README.md index 84889cf7..095bfe3d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/shadowsocks-rust) - This is a port of [shadowsocks](https://github.com/shadowsocks/shadowsocks). shadowsocks is a fast tunnel proxy that helps you bypass firewalls. @@ -33,37 +32,37 @@ Related Projects: ### Optional Features -* `trust-dns` - Uses [`trust-dns-resolver`](https://crates.io/crates/trust-dns-resolver) as DNS resolver instead of `tokio`'s builtin. +- `trust-dns` - Uses [`trust-dns-resolver`](https://crates.io/crates/trust-dns-resolver) as DNS resolver instead of `tokio`'s builtin. -* `local-http` - Allow using HTTP protocol for `sslocal` +- `local-http` - Allow using HTTP protocol for `sslocal` - * `local-http-native-tls` - Support HTTPS with [`native-tls`](https://crates.io/crates/native-tls) + - `local-http-native-tls` - Support HTTPS with [`native-tls`](https://crates.io/crates/native-tls) - * `local-http-rustls` - Support HTTPS with [`rustls`](https://crates.io/crates/rustls) + - `local-http-rustls` - Support HTTPS with [`rustls`](https://crates.io/crates/rustls) -* `local-tunnel` - Allow using tunnel protocol for `sslocal` +- `local-tunnel` - Allow using tunnel protocol for `sslocal` -* `local-socks4` - Allow using SOCKS4/4a protocol for `sslocal` +- `local-socks4` - Allow using SOCKS4/4a protocol for `sslocal` -* `local-redir` - Allow using redir (transparent proxy) protocol for `sslocal` +- `local-redir` - Allow using redir (transparent proxy) protocol for `sslocal` -* `local-dns` - Allow using dns protocol for `sslocal`, serves as a DNS server proxying queries to local or remote DNS servers by ACL rules +- `local-dns` - Allow using dns protocol for `sslocal`, serves as a DNS server proxying queries to local or remote DNS servers by ACL rules -* `local-tun` - [TUN](https://en.wikipedia.org/wiki/TUN/TAP) interface support for `sslocal` +- `local-tun` - [TUN](https://en.wikipedia.org/wiki/TUN/TAP) interface support for `sslocal` -* `stream-cipher` - Enable deprecated stream ciphers. WARN: stream ciphers are UNSAFE! +- `stream-cipher` - Enable deprecated stream ciphers. WARN: stream ciphers are UNSAFE! -* `aead-cipher-extra` - Enable non-standard AEAD ciphers +- `aead-cipher-extra` - Enable non-standard AEAD ciphers #### Memory Allocators This project uses system (libc) memory allocator (Rust's default). But it also allows you to use other famous allocators by features: -* `jemalloc` - Uses [jemalloc](http://jemalloc.net/) as global memory allocator -* `mimalloc` - Uses [mi-malloc](https://microsoft.github.io/mimalloc/) as global memory allocator -* `tcmalloc` - Uses [TCMalloc](https://google.github.io/tcmalloc/overview.html) as global memory allocator. It tries to link system-wide tcmalloc by default, use vendored from source with `tcmalloc-vendored`. -* `snmalloc` - Uses [snmalloc](https://github.com/microsoft/snmalloc) as global memory allocator -* `rpmalloc` - Uses [rpmalloc](https://github.com/mjansson/rpmalloc) as global memory allocator +- `jemalloc` - Uses [jemalloc](http://jemalloc.net/) as global memory allocator +- `mimalloc` - Uses [mi-malloc](https://microsoft.github.io/mimalloc/) as global memory allocator +- `tcmalloc` - Uses [TCMalloc](https://google.github.io/tcmalloc/overview.html) as global memory allocator. It tries to link system-wide tcmalloc by default, use vendored from source with `tcmalloc-vendored`. +- `snmalloc` - Uses [snmalloc](https://github.com/microsoft/snmalloc) as global memory allocator +- `rpmalloc` - Uses [rpmalloc](https://github.com/mjansson/rpmalloc) as global memory allocator ### **crates.io** @@ -86,9 +85,9 @@ then you can find `sslocal` and `ssserver` in `$CARGO_HOME/bin`. Download static-linked build [here](https://github.com/shadowsocks/shadowsocks-rust/releases). -* `build-windows`: Build for `x86_64-pc-windows-msvc` -* `build-linux`: Build for `x86_64-unknown-linux-gnu`, Debian 9 (Stretch), GLIBC 2.18 -* `build-docker`: Build for `x86_64-unknown-linux-musl`, `x86_64-pc-windows-gnu`, ... (statically linked) +- `build-windows`: Build for `x86_64-pc-windows-msvc` +- `build-linux`: Build for `x86_64-unknown-linux-gnu`, Debian 9 (Stretch), GLIBC 2.18 +- `build-docker`: Build for `x86_64-unknown-linux-musl`, `x86_64-pc-windows-gnu`, ... (statically linked) ### **Docker** @@ -161,7 +160,7 @@ export RUSTFLAGS="-C target-cpu=native" Requirements: -* Docker +- Docker ```bash ./build/build-release @@ -169,8 +168,8 @@ Requirements: Then `sslocal`, `ssserver`, `ssmanager` and `ssurl` will be packaged in -* `./build/shadowsocks-${VERSION}-stable.x86_64-unknown-linux-musl.tar.xz` -* `./build/shadowsocks-${VERSION}-stable.x86_64-pc-windows-gnu.zip` +- `./build/shadowsocks-${VERSION}-stable.x86_64-unknown-linux-musl.tar.xz` +- `./build/shadowsocks-${VERSION}-stable.x86_64-pc-windows-gnu.zip` Read `Cargo.toml` for more details. @@ -278,15 +277,15 @@ All parameters are the same as Socks5 client, except `--protocol http`. sslocal --protocol tunnel -b "127.0.0.1:3128" -f "127.0.0.1:8080" -s "[::1]:8388" -m "aes-256-gcm" -k "hello-kitty" ``` -* `--protocol tunnel` enables local client Tunnel mode -* `-f "127.0.0.1:8080` sets the tunnel target address +- `--protocol tunnel` enables local client Tunnel mode +- `-f "127.0.0.1:8080` sets the tunnel target address ### Transparent Proxy Local client **NOTE**: It currently only supports -* Linux (with `iptables` targets `REDIRECT` and `TPROXY`) -* BSDs (with `pf`), such as OS X 10.10+, FreeBSD, ... +- Linux (with `iptables` targets `REDIRECT` and `TPROXY`) +- BSDs (with `pf`), such as OS X 10.10+, FreeBSD, ... ```bash sslocal -b "127.0.0.1:60080" --protocol redir -s "[::1]:8388" -m "aes-256-gcm" -k "hello-kitty" --tcp-redir "redirect" --udp-redir "tproxy" @@ -294,16 +293,16 @@ sslocal -b "127.0.0.1:60080" --protocol redir -s "[::1]:8388" -m "aes-256-gcm" - Redirects connections with `iptables` configurations to the port that `sslocal` is listening on. -* `--protocol redir` enables local client Redir mode -* (optional) `--tcp-redir` sets TCP mode to `REDIRECT` (Linux) -* (optional) `--udp-redir` sets UDP mode to `TPROXY` (Linux) +- `--protocol redir` enables local client Redir mode +- (optional) `--tcp-redir` sets TCP mode to `REDIRECT` (Linux) +- (optional) `--udp-redir` sets UDP mode to `TPROXY` (Linux) ### Tun interface client **NOTE**: It currently only supports -* Linux, Android -* macOS, iOS +- Linux, Android +- macOS, iOS #### Linux @@ -351,10 +350,10 @@ ssserver -s "[::]:8388" -m "aes-256-gcm" -k "hello-kitty" --plugin "v2ray-plugin Supported [Manage Multiple Users](https://github.com/shadowsocks/shadowsocks/wiki/Manage-Multiple-Users) API: -* `add` - Starts a server instance -* `remove` - Deletes an existing server instance -* `list` - Lists all current running servers -* `ping` - Lists all servers' statistic data +- `add` - Starts a server instance +- `remove` - Deletes an existing server instance +- `list` - Lists all current running servers +- `ping` - Lists all servers' statistic data NOTE: `stat` command is not supported. Because servers are running in the same process with the manager itself. @@ -575,6 +574,9 @@ Example configuration: // Try to resolve domain name to IPv6 (AAAA) addresses first "ipv6_first": false, + // Set IPV6_V6ONLY for all IPv6 listener sockets + // Only valid for locals and servers listening on `::` + "ipv6_only": false, // Balancer customization "balancer": { @@ -598,28 +600,28 @@ Example configuration: ### AEAD Ciphers -* `chacha20-ietf-poly1305` -* `aes-128-gcm`, `aes-256-gcm` +- `chacha20-ietf-poly1305` +- `aes-128-gcm`, `aes-256-gcm` ### Stream Ciphers -* `plain` or `none` (No encryption, only used for debugging or with plugins that ensure transport security) +- `plain` or `none` (No encryption, only used for debugging or with plugins that ensure transport security)
Deprecated

-* `table` -* `aes-128-cfb`, `aes-128-cfb1`, `aes-128-cfb8`, `aes-128-cfb128` -* `aes-192-cfb`, `aes-192-cfb1`, `aes-192-cfb8`, `aes-192-cfb128` -* `aes-256-cfb`, `aes-256-cfb1`, `aes-256-cfb8`, `aes-256-cfb128` -* `aes-128-ctr` -* `aes-192-ctr` -* `aes-256-ctr` -* `camellia-128-cfb`, `camellia-128-cfb1`, `camellia-128-cfb8`, `camellia-128-cfb128` -* `camellia-192-cfb`, `camellia-192-cfb1`, `camellia-192-cfb8`, `camellia-192-cfb128` -* `camellia-256-cfb`, `camellia-256-cfb1`, `camellia-256-cfb8`, `camellia-256-cfb128` -* `rc4-md5` -* `chacha20-ietf` +- `table` +- `aes-128-cfb`, `aes-128-cfb1`, `aes-128-cfb8`, `aes-128-cfb128` +- `aes-192-cfb`, `aes-192-cfb1`, `aes-192-cfb8`, `aes-192-cfb128` +- `aes-256-cfb`, `aes-256-cfb1`, `aes-256-cfb8`, `aes-256-cfb128` +- `aes-128-ctr` +- `aes-192-ctr` +- `aes-256-ctr` +- `camellia-128-cfb`, `camellia-128-cfb1`, `camellia-128-cfb8`, `camellia-128-cfb128` +- `camellia-192-cfb`, `camellia-192-cfb1`, `camellia-192-cfb8`, `camellia-192-cfb128` +- `camellia-256-cfb`, `camellia-256-cfb1`, `camellia-256-cfb8`, `camellia-256-cfb128` +- `rc4-md5` +- `chacha20-ietf`

@@ -630,21 +632,21 @@ Example configuration: ### Available sections -* For local servers (`sslocal`, `ssredir`, ...) - * Modes: - * `[bypass_all]` - ACL runs in `BlackList` mode. Bypasses all addresses that didn't match any rules. - * `[proxy_all]` - ACL runs in `WhiteList` mode. Proxies all addresses that didn't match any rules. - * Rules: - * `[bypass_list]` - Rules for connecting directly - * `[proxy_list]` - Rules for connecting through proxies -* For remote servers (`ssserver`) - * Modes: - * `[reject_all]` - ACL runs in `BlackList` mode. Rejects all clients that didn't match any rules. - * `[accept_all]` - ACL runs in `WhiteList` mode. Accepts all clients that didn't match any rules. - * Rules: - * `[white_list]` - Rules for accepted clients - * `[black_list]` - Rules for rejected clients - * `[outbound_block_list]` - Rules for blocking outbound addresses. +- For local servers (`sslocal`, `ssredir`, ...) + - Modes: + - `[bypass_all]` - ACL runs in `BlackList` mode. Bypasses all addresses that didn't match any rules. + - `[proxy_all]` - ACL runs in `WhiteList` mode. Proxies all addresses that didn't match any rules. + - Rules: + - `[bypass_list]` - Rules for connecting directly + - `[proxy_list]` - Rules for connecting through proxies +- For remote servers (`ssserver`) + - Modes: + - `[reject_all]` - ACL runs in `BlackList` mode. Rejects all clients that didn't match any rules. + - `[accept_all]` - ACL runs in `WhiteList` mode. Accepts all clients that didn't match any rules. + - Rules: + - `[white_list]` - Rules for accepted clients + - `[black_list]` - Rules for rejected clients + - `[outbound_block_list]` - Rules for blocking outbound addresses. ### Example @@ -696,31 +698,31 @@ Example configuration: It supports the following features: -* [x] SOCKS5 CONNECT command -* [x] SOCKS5 UDP ASSOCIATE command (partial) -* [x] SOCKS4/4a CONNECT command -* [x] Various crypto algorithms -* [x] Load balancing (multiple servers) and server delay checking -* [x] [SIP004](https://github.com/shadowsocks/shadowsocks-org/issues/30) AEAD ciphers -* [x] [SIP003](https://github.com/shadowsocks/shadowsocks-org/issues/28) Plugins -* [x] [SIP002](https://github.com/shadowsocks/shadowsocks-org/issues/27) Extension ss URLs -* [x] HTTP Proxy Supports ([RFC 7230](http://tools.ietf.org/html/rfc7230) and [CONNECT](https://tools.ietf.org/html/draft-luotonen-web-proxy-tunneling-01)) -* [x] Defend against replay attacks, [shadowsocks/shadowsocks-org#44](https://github.com/shadowsocks/shadowsocks-org/issues/44) -* [x] Manager APIs, supporting [Manage Multiple Users](https://github.com/shadowsocks/shadowsocks/wiki/Manage-Multiple-Users) -* [x] ACL (Access Control List) -* [x] Support HTTP/HTTPS Proxy protocol +- [x] SOCKS5 CONNECT command +- [x] SOCKS5 UDP ASSOCIATE command (partial) +- [x] SOCKS4/4a CONNECT command +- [x] Various crypto algorithms +- [x] Load balancing (multiple servers) and server delay checking +- [x] [SIP004](https://github.com/shadowsocks/shadowsocks-org/issues/30) AEAD ciphers +- [x] [SIP003](https://github.com/shadowsocks/shadowsocks-org/issues/28) Plugins +- [x] [SIP002](https://github.com/shadowsocks/shadowsocks-org/issues/27) Extension ss URLs +- [x] HTTP Proxy Supports ([RFC 7230](http://tools.ietf.org/html/rfc7230) and [CONNECT](https://tools.ietf.org/html/draft-luotonen-web-proxy-tunneling-01)) +- [x] Defend against replay attacks, [shadowsocks/shadowsocks-org#44](https://github.com/shadowsocks/shadowsocks-org/issues/44) +- [x] Manager APIs, supporting [Manage Multiple Users](https://github.com/shadowsocks/shadowsocks/wiki/Manage-Multiple-Users) +- [x] ACL (Access Control List) +- [x] Support HTTP/HTTPS Proxy protocol ## TODO -* [x] Documentation -* [x] Extend configuration format -* [x] Improved logging format (waiting for the new official log crate) -* [x] Support more ciphers without depending on `libcrypto` (waiting for an acceptable Rust crypto lib implementation) -* [x] Windows support. -* [ ] Build with stable `rustc` (blocking by `crypto2`). -* [x] Support HTTP Proxy protocol -* [x] AEAD ciphers. (proposed in [SIP004](https://github.com/shadowsocks/shadowsocks-org/issues/30), still under discussion) -* [x] Choose server based on delay #152 +- [x] Documentation +- [x] Extend configuration format +- [x] Improved logging format (waiting for the new official log crate) +- [x] Support more ciphers without depending on `libcrypto` (waiting for an acceptable Rust crypto lib implementation) +- [x] Windows support. +- [ ] Build with stable `rustc` (blocking by `crypto2`). +- [x] Support HTTP Proxy protocol +- [x] AEAD ciphers. (proposed in [SIP004](https://github.com/shadowsocks/shadowsocks-org/issues/30), still under discussion) +- [x] Choose server based on delay #152 ## License diff --git a/crates/shadowsocks-service/Cargo.toml b/crates/shadowsocks-service/Cargo.toml index 1ff19685..99de06c1 100644 --- a/crates/shadowsocks-service/Cargo.toml +++ b/crates/shadowsocks-service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks-service" -version = "1.12.2" +version = "1.12.3" authors = ["Shadowsocks Contributors"] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/shadowsocks/shadowsocks-rust" diff --git a/crates/shadowsocks-service/src/config.rs b/crates/shadowsocks-service/src/config.rs index 0ec2f333..b38cfb24 100644 --- a/crates/shadowsocks-service/src/config.rs +++ b/crates/shadowsocks-service/src/config.rs @@ -110,53 +110,72 @@ struct SSConfig { server: Option, #[serde(skip_serializing_if = "Option::is_none")] server_port: Option, + #[serde(skip_serializing_if = "Option::is_none")] local_address: Option, #[serde(skip_serializing_if = "Option::is_none")] local_port: Option, + #[serde(skip_serializing_if = "Option::is_none")] protocol: Option, + #[serde(skip_serializing_if = "Option::is_none")] manager_address: Option, #[serde(skip_serializing_if = "Option::is_none")] manager_port: Option, + #[serde(skip_serializing_if = "Option::is_none")] password: Option, #[serde(skip_serializing_if = "Option::is_none")] method: Option, + #[serde(skip_serializing_if = "Option::is_none")] plugin: Option, #[serde(skip_serializing_if = "Option::is_none")] plugin_opts: Option, #[serde(skip_serializing_if = "Option::is_none")] plugin_args: Option>, + #[serde(skip_serializing_if = "Option::is_none")] timeout: Option, + #[serde(skip_serializing_if = "Option::is_none")] udp_timeout: Option, #[serde(skip_serializing_if = "Option::is_none")] udp_max_associations: Option, + #[serde(skip_serializing_if = "Option::is_none", alias = "shadowsocks")] servers: Option>, + #[serde(skip_serializing_if = "Option::is_none")] locals: Option>, + #[serde(skip_serializing_if = "Option::is_none")] dns: Option, + #[serde(skip_serializing_if = "Option::is_none")] mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] no_delay: Option, #[serde(skip_serializing_if = "Option::is_none")] keep_alive: Option, + #[cfg(all(unix, not(target_os = "android")))] #[serde(skip_serializing_if = "Option::is_none")] nofile: Option, + #[serde(skip_serializing_if = "Option::is_none")] ipv6_first: Option, + #[serde(skip_serializing_if = "Option::is_none")] + ipv6_only: Option, + #[serde(skip_serializing_if = "Option::is_none")] fast_open: Option, + #[serde(skip_serializing_if = "Option::is_none")] security: Option, + #[serde(skip_serializing_if = "Option::is_none")] balancer: Option, } @@ -236,24 +255,31 @@ struct SSServerExtConfig { server: String, #[serde(alias = "port")] server_port: u16, + password: String, method: String, + #[serde(skip_serializing_if = "Option::is_none")] disabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] plugin: Option, #[serde(skip_serializing_if = "Option::is_none")] plugin_opts: Option, #[serde(skip_serializing_if = "Option::is_none")] plugin_args: Option>, + #[serde(skip_serializing_if = "Option::is_none")] timeout: Option, + #[serde(skip_serializing_if = "Option::is_none", alias = "name")] remarks: Option, #[serde(skip_serializing_if = "Option::is_none")] id: Option, + #[serde(skip_serializing_if = "Option::is_none")] mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] tcp_weight: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -774,6 +800,9 @@ pub struct LocalConfig { /// Tun interface's file descriptor read from this Unix Domain Socket #[cfg(all(feature = "local-tun", unix))] pub tun_device_fd_from_path: Option, + + /// Set `IPV6_V6ONLY` for listener socket + pub ipv6_only: bool, } impl LocalConfig { @@ -808,6 +837,8 @@ impl LocalConfig { tun_device_fd: None, #[cfg(all(feature = "local-tun", unix))] tun_device_fd_from_path: None, + + ipv6_only: false, } } @@ -947,6 +978,8 @@ pub struct Config { /// /// Set to `true` if you want to query IPv6 addresses before IPv4 pub ipv6_first: bool, + /// Set `IPV6_V6ONLY` for listener sockets + pub ipv6_only: bool, /// Set `TCP_NODELAY` socket option pub no_delay: bool, @@ -1080,6 +1113,7 @@ impl Config { dns: DnsConfig::default(), ipv6_first: false, + ipv6_only: false, no_delay: false, fast_open: false, @@ -1638,6 +1672,11 @@ impl Config { nconfig.ipv6_first = f; } + // IPV6_V6ONLY + if let Some(o) = config.ipv6_only { + nconfig.ipv6_only = o; + } + // Security if let Some(sec) = config.security { if let Some(replay_attack) = sec.replay_attack { @@ -2205,6 +2244,10 @@ impl fmt::Display for Config { jconf.ipv6_first = Some(self.ipv6_first); } + if self.ipv6_only { + jconf.ipv6_only = Some(self.ipv6_only); + } + // Security if self.security.replay_attack.policy != ReplayAttackPolicy::default() { jconf.security = Some(SSSecurityConfig { diff --git a/crates/shadowsocks-service/src/local/mod.rs b/crates/shadowsocks-service/src/local/mod.rs index caffc014..946a14a7 100644 --- a/crates/shadowsocks-service/src/local/mod.rs +++ b/crates/shadowsocks-service/src/local/mod.rs @@ -117,6 +117,7 @@ pub async fn create(config: Config) -> io::Result { context.set_connect_opts(connect_opts); let mut accept_opts = AcceptOpts::default(); + accept_opts.ipv6_only = config.ipv6_only; 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; diff --git a/crates/shadowsocks-service/src/local/redir/redir_ext.rs b/crates/shadowsocks-service/src/local/redir/redir_ext.rs index bf13494a..071a2d1a 100644 --- a/crates/shadowsocks-service/src/local/redir/redir_ext.rs +++ b/crates/shadowsocks-service/src/local/redir/redir_ext.rs @@ -9,6 +9,7 @@ use std::{ }; use async_trait::async_trait; +use shadowsocks::net::AcceptOpts; use tokio::net::TcpListener; use crate::config::RedirType; @@ -19,7 +20,7 @@ pub trait TcpListenerRedirExt { // Create a TcpListener for transparent proxy // // Implementation is platform dependent - async fn bind_redir(ty: RedirType, addr: SocketAddr) -> io::Result; + async fn bind_redir(ty: RedirType, addr: SocketAddr, accept_opts: AcceptOpts) -> io::Result; } /// Extension function for `TcpStream` for reading original destination address diff --git a/crates/shadowsocks-service/src/local/redir/sys/mod.rs b/crates/shadowsocks-service/src/local/redir/sys/mod.rs index ed140703..7cb39d74 100644 --- a/crates/shadowsocks-service/src/local/redir/sys/mod.rs +++ b/crates/shadowsocks-service/src/local/redir/sys/mod.rs @@ -1,4 +1,7 @@ +use std::io; + use cfg_if::cfg_if; +use socket2::Socket; cfg_if! { if #[cfg(unix)] { @@ -6,3 +9,31 @@ cfg_if! { pub use self::unix::*; } } + +#[cfg(unix)] +pub fn set_ipv6_only(socket: &S, ipv6_only: bool) -> io::Result<()> +where + S: std::os::unix::io::AsRawFd, +{ + use std::os::unix::io::{FromRawFd, IntoRawFd}; + + let fd = socket.as_raw_fd(); + let sock = unsafe { Socket::from_raw_fd(fd) }; + let result = sock.set_only_v6(ipv6_only); + sock.into_raw_fd(); + result +} + +#[cfg(windows)] +pub fn set_ipv6_only(socket: &S, ipv6_only: bool) -> io::Result<()> +where + S: std::os::windows::io::AsRawSocket, +{ + use std::os::windows::io::{FromRawSocket, IntoRawSocket}; + + let handle = socket.as_raw_socket(); + let sock = unsafe { Socket::from_raw_socket(handle) }; + let result = sock.set_only_v6(ipv6_only); + sock.into_raw_socket(); + result +} diff --git a/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs b/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs index 1192c298..32ba3e19 100644 --- a/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs +++ b/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs @@ -71,10 +71,10 @@ pub async fn run_tcp_redir( redir_ty: RedirType, ) -> io::Result<()> { let listener = match *client_config { - ServerAddr::SocketAddr(ref saddr) => TcpListener::bind_redir(redir_ty, *saddr).await?, + ServerAddr::SocketAddr(ref saddr) => TcpListener::bind_redir(redir_ty, *saddr, context.accept_opts()).await?, ServerAddr::DomainName(ref dname, port) => { lookup_then!(context.context_ref(), dname, port, |addr| { - TcpListener::bind_redir(redir_ty, addr).await + TcpListener::bind_redir(redir_ty, addr, context.accept_opts()).await })? .1 } diff --git a/crates/shadowsocks-service/src/local/redir/tcprelay/sys/unix/bsd.rs b/crates/shadowsocks-service/src/local/redir/tcprelay/sys/unix/bsd.rs index ecbe3420..0c3e6432 100644 --- a/crates/shadowsocks-service/src/local/redir/tcprelay/sys/unix/bsd.rs +++ b/crates/shadowsocks-service/src/local/redir/tcprelay/sys/unix/bsd.rs @@ -4,17 +4,22 @@ use std::{ }; use async_trait::async_trait; +use log::warn; +use shadowsocks::net::{is_dual_stack_addr, set_tcp_fastopen, AcceptOpts}; use socket2::Protocol; -use tokio::net::{TcpListener, TcpStream}; +use tokio::net::{TcpListener, TcpSocket, TcpStream}; use crate::{ config::RedirType, - local::redir::redir_ext::{TcpListenerRedirExt, TcpStreamRedirExt}, + local::redir::{ + redir_ext::{TcpListenerRedirExt, TcpStreamRedirExt}, + sys::set_ipv6_only, + }, }; #[async_trait] impl TcpListenerRedirExt for TcpListener { - async fn bind_redir(ty: RedirType, addr: SocketAddr) -> io::Result { + async fn bind_redir(ty: RedirType, addr: SocketAddr, accept_opts: AcceptOpts) -> io::Result { match ty { #[cfg(any( target_os = "openbsd", @@ -43,7 +48,40 @@ impl TcpListenerRedirExt for TcpListener { } // BSD platform doesn't have any special logic - TcpListener::bind(addr).await + let socket = match addr { + SocketAddr::V4(..) => TcpSocket::new_v4()?, + SocketAddr::V6(..) => TcpSocket::new_v6()?, + }; + + // On platforms with Berkeley-derived sockets, this allows to quickly + // rebind a socket, without needing to wait for the OS to clean up the + // previous one. + // + // On Windows, this allows rebinding sockets which are actively in use, + // which allows “socket hijacking”, so we explicitly don't set it here. + // https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse + #[cfg(unix)] + socket.set_reuseaddr(true)?; + + let set_dual_stack = is_dual_stack_addr(&addr); + if set_dual_stack { + // Transparent socket shouldn't support dual-stack. + + if let Err(err) = set_ipv6_only(&socket, true) { + warn!("failed to set IPV6_V6ONLY, error: {}", err); + } + } + + socket.bind(addr)?; + + // mio's default backlog is 1024 + let listener = socket.listen(1024)?; + + if accept_opts.tcp.fastopen { + set_tcp_fastopen(&listener)?; + } + + Ok(listener) } } diff --git a/crates/shadowsocks-service/src/local/redir/tcprelay/sys/unix/linux.rs b/crates/shadowsocks-service/src/local/redir/tcprelay/sys/unix/linux.rs index 5200a7cb..d6387b22 100644 --- a/crates/shadowsocks-service/src/local/redir/tcprelay/sys/unix/linux.rs +++ b/crates/shadowsocks-service/src/local/redir/tcprelay/sys/unix/linux.rs @@ -6,25 +6,64 @@ use std::{ }; use async_trait::async_trait; +use log::warn; +use shadowsocks::net::{is_dual_stack_addr, set_tcp_fastopen, AcceptOpts}; use socket2::SockAddr; use tokio::net::{TcpListener, TcpSocket, TcpStream}; use crate::{ config::RedirType, - local::redir::redir_ext::{TcpListenerRedirExt, TcpStreamRedirExt}, + local::redir::{ + redir_ext::{TcpListenerRedirExt, TcpStreamRedirExt}, + sys::set_ipv6_only, + }, }; #[async_trait] impl TcpListenerRedirExt for TcpListener { - async fn bind_redir(ty: RedirType, addr: SocketAddr) -> io::Result { + async fn bind_redir(ty: RedirType, addr: SocketAddr, accept_opts: AcceptOpts) -> io::Result { match ty { RedirType::Redirect => { // REDIRECT rule doesn't need to set IP_TRANSPARENT - TcpListener::bind(addr).await + + let socket = match addr { + SocketAddr::V4(..) => TcpSocket::new_v4()?, + SocketAddr::V6(..) => TcpSocket::new_v6()?, + }; + + // On platforms with Berkeley-derived sockets, this allows to quickly + // rebind a socket, without needing to wait for the OS to clean up the + // previous one. + // + // On Windows, this allows rebinding sockets which are actively in use, + // which allows “socket hijacking”, so we explicitly don't set it here. + // https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse + #[cfg(unix)] + socket.set_reuseaddr(true)?; + + let set_dual_stack = is_dual_stack_addr(&addr); + if set_dual_stack { + // Transparent socket shouldn't support dual-stack. + + if let Err(err) = set_ipv6_only(&socket, true) { + warn!("failed to set IPV6_V6ONLY, error: {}", err); + } + } + + socket.bind(addr)?; + + // mio's default backlog is 1024 + let listener = socket.listen(1024)?; + + if accept_opts.tcp.fastopen { + set_tcp_fastopen(&listener)?; + } + + Ok(listener) } RedirType::TProxy => { // TPROXY rule requires IP_TRANSPARENT - create_redir_listener(addr).await + create_redir_listener(addr, accept_opts).await } _ => Err(Error::new( ErrorKind::InvalidInput, @@ -89,7 +128,7 @@ fn get_original_destination_addr(s: &TcpStream) -> io::Result { } } -async fn create_redir_listener(addr: SocketAddr) -> io::Result { +async fn create_redir_listener(addr: SocketAddr, accept_opts: AcceptOpts) -> io::Result { let socket = match addr { SocketAddr::V4(..) => TcpSocket::new_v4()?, SocketAddr::V6(..) => TcpSocket::new_v6()?, @@ -123,11 +162,34 @@ async fn create_redir_listener(addr: SocketAddr) -> io::Result { } } - // tokio requires allow reuse addr + // On platforms with Berkeley-derived sockets, this allows to quickly + // rebind a socket, without needing to wait for the OS to clean up the + // previous one. + // + // On Windows, this allows rebinding sockets which are actively in use, + // which allows “socket hijacking”, so we explicitly don't set it here. + // https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse + #[cfg(unix)] socket.set_reuseaddr(true)?; + let set_dual_stack = is_dual_stack_addr(&addr); + if set_dual_stack { + // Transparent socket shouldn't support dual-stack. + + if let Err(err) = set_ipv6_only(&socket, true) { + warn!("failed to set IPV6_V6ONLY, error: {}", err); + } + } + // bind, listen as original socket.bind(addr)?; + // listen backlogs = 1024 as mio's default - socket.listen(1024) + let listener = socket.listen(1024)?; + + if accept_opts.tcp.fastopen { + set_tcp_fastopen(&listener)?; + } + + Ok(listener) } diff --git a/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs b/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs index e1dd37f1..8951eb87 100644 --- a/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs +++ b/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs @@ -9,12 +9,16 @@ use std::{ use async_trait::async_trait; use futures::{future::poll_fn, ready}; +use shadowsocks::net::is_dual_stack_addr; use socket2::{Domain, Protocol, SockAddr, Socket, Type}; use tokio::io::unix::AsyncFd; use crate::{ config::RedirType, - local::redir::redir_ext::{RedirSocketOpts, UdpSocketRedirExt}, + local::redir::{ + redir_ext::{RedirSocketOpts, UdpSocketRedirExt}, + sys::set_ipv6_only, + }, }; pub fn check_support_tproxy() -> io::Result<()> { @@ -57,6 +61,14 @@ impl UdpRedirSocket { socket.set_reuse_port(true)?; } + if is_dual_stack_addr(&addr) { + // Transparent socket shouldn't support dual-stack. + + if let Err(err) = set_ipv6_only(&socket, true) { + warn!("failed to set IPV6_V6ONLY, error: {}", err); + } + } + socket.bind(&SockAddr::from(addr))?; let io = AsyncFd::new(socket.into())?; diff --git a/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/linux.rs b/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/linux.rs index 992faaba..593b7e4f 100644 --- a/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/linux.rs +++ b/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/linux.rs @@ -9,12 +9,17 @@ use std::{ use cfg_if::cfg_if; use futures::{future::poll_fn, ready}; +use log::warn; +use shadowsocks::net::is_dual_stack_addr; use socket2::{Domain, Protocol, SockAddr, Socket, Type}; use tokio::io::unix::AsyncFd; use crate::{ config::RedirType, - local::redir::redir_ext::{RedirSocketOpts, UdpSocketRedir}, + local::redir::{ + redir_ext::{RedirSocketOpts, UdpSocketRedir}, + sys::set_ipv6_only, + }, }; pub struct UdpRedirSocket { @@ -70,6 +75,14 @@ impl UdpRedirSocket { socket.set_reuse_port(true)?; } + if is_dual_stack_addr(&addr) { + // Transparent socket shouldn't support dual-stack. + + if let Err(err) = set_ipv6_only(&socket, true) { + warn!("failed to set IPV6_V6ONLY, error: {}", err); + } + } + socket.bind(&SockAddr::from(addr))?; let io = AsyncFd::new(socket.into())?; diff --git a/crates/shadowsocks-service/src/local/socks/server/socks5/udprelay.rs b/crates/shadowsocks-service/src/local/socks/server/socks5/udprelay.rs index 72197f78..29895e2d 100644 --- a/crates/shadowsocks-service/src/local/socks/server/socks5/udprelay.rs +++ b/crates/shadowsocks-service/src/local/socks/server/socks5/udprelay.rs @@ -69,10 +69,12 @@ impl Socks5UdpServer { pub async fn run(&self, client_config: &ServerAddr, balancer: PingBalancer) -> io::Result<()> { let socket = match *client_config { - ServerAddr::SocketAddr(ref saddr) => ShadowUdpSocket::listen(saddr).await?, + ServerAddr::SocketAddr(ref saddr) => { + ShadowUdpSocket::listen_with_opts(saddr, self.context.accept_opts()).await? + } ServerAddr::DomainName(ref dname, port) => { lookup_then!(self.context.context_ref(), dname, port, |addr| { - ShadowUdpSocket::listen(&addr).await + ShadowUdpSocket::listen_with_opts(&addr, self.context.accept_opts()).await })? .1 } diff --git a/crates/shadowsocks-service/src/local/tunnel/udprelay.rs b/crates/shadowsocks-service/src/local/tunnel/udprelay.rs index 0eba2f7e..cd84775e 100644 --- a/crates/shadowsocks-service/src/local/tunnel/udprelay.rs +++ b/crates/shadowsocks-service/src/local/tunnel/udprelay.rs @@ -93,10 +93,12 @@ impl UdpTunnel { forward_addr: &Address, ) -> io::Result<()> { let socket = match *client_config { - ServerAddr::SocketAddr(ref saddr) => ShadowUdpSocket::listen(saddr).await?, + ServerAddr::SocketAddr(ref saddr) => { + ShadowUdpSocket::listen_with_opts(saddr, self.context.accept_opts()).await? + } ServerAddr::DomainName(ref dname, port) => { lookup_then!(self.context.context_ref(), dname, port, |addr| { - ShadowUdpSocket::listen(&addr).await + ShadowUdpSocket::listen_with_opts(&addr, self.context.accept_opts()).await })? .1 } diff --git a/crates/shadowsocks-service/src/manager/mod.rs b/crates/shadowsocks-service/src/manager/mod.rs index d00a96a2..281b393b 100644 --- a/crates/shadowsocks-service/src/manager/mod.rs +++ b/crates/shadowsocks-service/src/manager/mod.rs @@ -53,6 +53,7 @@ pub async fn run(config: Config) -> io::Result<()> { connect_opts.tcp.keepalive = config.keep_alive.or(Some(SERVER_DEFAULT_KEEPALIVE_TIMEOUT)); let mut accept_opts = AcceptOpts::default(); + accept_opts.ipv6_only = config.ipv6_only; 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; diff --git a/crates/shadowsocks-service/src/server/mod.rs b/crates/shadowsocks-service/src/server/mod.rs index 39a87875..8dd7bad4 100644 --- a/crates/shadowsocks-service/src/server/mod.rs +++ b/crates/shadowsocks-service/src/server/mod.rs @@ -70,6 +70,7 @@ pub async fn run(config: Config) -> io::Result<()> { connect_opts.tcp.keepalive = config.keep_alive.or(Some(SERVER_DEFAULT_KEEPALIVE_TIMEOUT)); let mut accept_opts = AcceptOpts::default(); + accept_opts.ipv6_only = config.ipv6_only; 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; diff --git a/crates/shadowsocks-service/src/server/server.rs b/crates/shadowsocks-service/src/server/server.rs index 02014314..0fa4f06f 100644 --- a/crates/shadowsocks-service/src/server/server.rs +++ b/crates/shadowsocks-service/src/server/server.rs @@ -169,7 +169,12 @@ impl Server { } async fn run_udp_server(&self) -> io::Result<()> { - let server = UdpServer::new(self.context.clone(), self.udp_expiry_duration, self.udp_capacity); + let server = UdpServer::new( + self.context.clone(), + self.udp_expiry_duration, + self.udp_capacity, + self.accept_opts.clone(), + ); server.run(&self.svr_cfg).await } diff --git a/crates/shadowsocks-service/src/server/udprelay.rs b/crates/shadowsocks-service/src/server/udprelay.rs index 7e8cbba9..2645884f 100644 --- a/crates/shadowsocks-service/src/server/udprelay.rs +++ b/crates/shadowsocks-service/src/server/udprelay.rs @@ -8,7 +8,7 @@ use log::{debug, error, info, trace, warn}; use lru_time_cache::LruCache; use shadowsocks::{ lookup_then, - net::UdpSocket as OutboundUdpSocket, + net::{AcceptOpts, UdpSocket as OutboundUdpSocket}, relay::{ socks5::Address, udprelay::{ProxySocket, MAXIMUM_UDP_PAYLOAD_SIZE}, @@ -35,6 +35,7 @@ pub struct UdpServer { cleanup_abortable: JoinHandle<()>, keepalive_abortable: JoinHandle<()>, keepalive_tx: mpsc::Sender, + accept_opts: AcceptOpts, } impl Drop for UdpServer { @@ -45,7 +46,12 @@ impl Drop for UdpServer { } impl UdpServer { - pub fn new(context: Arc, time_to_live: Option, capacity: Option) -> UdpServer { + pub fn new( + context: Arc, + time_to_live: Option, + capacity: Option, + accept_opts: AcceptOpts, + ) -> UdpServer { let time_to_live = time_to_live.unwrap_or(crate::DEFAULT_UDP_EXPIRY_DURATION); let assoc_map = Arc::new(Mutex::new(match capacity { Some(capacity) => LruCache::with_expiry_duration_and_capacity(time_to_live, capacity), @@ -81,11 +87,12 @@ impl UdpServer { cleanup_abortable, keepalive_abortable, keepalive_tx, + accept_opts, } } pub async fn run(mut self, svr_cfg: &ServerConfig) -> io::Result<()> { - let socket = ProxySocket::bind(self.context.context(), svr_cfg).await?; + let socket = ProxySocket::bind_with_opts(self.context.context(), svr_cfg, self.accept_opts.clone()).await?; info!( "shadowsocks udp server listening on {}", diff --git a/crates/shadowsocks/src/net/mod.rs b/crates/shadowsocks/src/net/mod.rs index 925825cd..c459fba6 100644 --- a/crates/shadowsocks/src/net/mod.rs +++ b/crates/shadowsocks/src/net/mod.rs @@ -6,6 +6,7 @@ use std::net::SocketAddr; pub use self::sys::uds::{UnixListener, UnixStream}; pub use self::{ option::{AcceptOpts, ConnectOpts}, + sys::{set_tcp_fastopen, socket_bind_dual_stack}, tcp::{TcpListener, TcpStream}, udp::UdpSocket, }; @@ -41,3 +42,12 @@ impl From for AddrFamily { } } } + +/// Check if `SocketAddr` could be used for creating dual-stack sockets +pub fn is_dual_stack_addr(addr: &SocketAddr) -> bool { + if let SocketAddr::V6(ref v6) = *addr { + v6.ip().is_unspecified() + } else { + false + } +} diff --git a/crates/shadowsocks/src/net/option.rs b/crates/shadowsocks/src/net/option.rs index b5b6779a..951f152e 100644 --- a/crates/shadowsocks/src/net/option.rs +++ b/crates/shadowsocks/src/net/option.rs @@ -52,4 +52,7 @@ pub struct ConnectOpts { pub struct AcceptOpts { /// TCP options pub tcp: TcpSocketOpts, + + /// Enable IPV6_V6ONLY option for socket + pub ipv6_only: bool, } diff --git a/crates/shadowsocks/src/net/sys/mod.rs b/crates/shadowsocks/src/net/sys/mod.rs index 6d2fba87..4e6a3278 100644 --- a/crates/shadowsocks/src/net/sys/mod.rs +++ b/crates/shadowsocks/src/net/sys/mod.rs @@ -1,9 +1,11 @@ use std::{ - io, + io::{self, ErrorKind}, net::{IpAddr, SocketAddr}, }; use cfg_if::cfg_if; +use log::{debug, warn}; +use socket2::{SockAddr, Socket}; use tokio::net::TcpSocket; use super::ConnectOpts; @@ -50,3 +52,78 @@ fn set_common_sockopt_for_connect(addr: SocketAddr, socket: &TcpSocket, opts: &C fn set_common_sockopt_after_connect_sys(_: &tokio::net::TcpStream, _: &ConnectOpts) -> io::Result<()> { Ok(()) } + +/// Try to call `bind()` with dual-stack enabled. +/// +/// Users have to ensure that `addr` is a dual-stack inbound address (`::`) when `ipv6_only` is `false`. +#[cfg(unix)] +pub fn socket_bind_dual_stack(socket: &S, addr: &SocketAddr, ipv6_only: bool) -> io::Result<()> +where + S: std::os::unix::io::AsRawFd, +{ + use std::os::unix::prelude::{FromRawFd, IntoRawFd}; + + let fd = socket.as_raw_fd(); + + let sock = unsafe { Socket::from_raw_fd(fd) }; + let result = socket_bind_dual_stack_inner(&sock, addr, ipv6_only); + sock.into_raw_fd(); + + result +} + +/// Try to call `bind()` with dual-stack enabled. +/// +/// Users have to ensure that `addr` is a dual-stack inbound address (`::`) when `ipv6_only` is `false`. +#[cfg(windows)] +pub fn socket_bind_dual_stack(socket: &S, addr: &SocketAddr, ipv6_only: bool) -> io::Result<()> +where + S: std::os::windows::io::AsRawSocket, +{ + use std::os::windows::prelude::{FromRawSocket, IntoRawSocket}; + + let handle = socket.as_raw_socket(); + + let sock = unsafe { Socket::from_raw_socket(handle) }; + let result = socket_bind_dual_stack_inner(&sock, addr, ipv6_only); + sock.into_raw_socket(); + + result +} + +fn socket_bind_dual_stack_inner(socket: &Socket, addr: &SocketAddr, ipv6_only: bool) -> io::Result<()> { + let saddr = SockAddr::from(*addr); + + if ipv6_only { + // Requested to set IPV6_V6ONLY + socket.set_only_v6(true)?; + socket.bind(&saddr)?; + } else { + if let Err(err) = socket.set_only_v6(false) { + warn!("failed to set IPV6_V6ONLY: false for listener, error: {}", err); + + // This is not a fatal error, just warn and skip + } + + match socket.bind(&saddr) { + Ok(..) => {} + Err(ref err) if err.kind() == ErrorKind::AddrInUse => { + // This is probably 0.0.0.0 with the same port has already been occupied + debug!( + "0.0.0.0:{} may have already been occupied, retry with IPV6_V6ONLY", + addr.port() + ); + + if let Err(err) = socket.set_only_v6(true) { + warn!("failed to set IPV6_V6ONLY: true for listener, error: {}", err); + + // This is not a fatal error, just warn and skip + } + socket.bind(&saddr)?; + } + Err(err) => return Err(err), + } + } + + Ok(()) +} diff --git a/crates/shadowsocks/src/net/sys/unix/mod.rs b/crates/shadowsocks/src/net/sys/unix/mod.rs index b55adfec..3f0b9085 100644 --- a/crates/shadowsocks/src/net/sys/unix/mod.rs +++ b/crates/shadowsocks/src/net/sys/unix/mod.rs @@ -1,15 +1,14 @@ use std::{ - io::{self, ErrorKind}, + io, net::SocketAddr, os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}, }; use cfg_if::cfg_if; -use log::{debug, warn}; -use socket2::{Domain, Protocol, SockAddr, Socket, TcpKeepalive, Type}; +use socket2::{Domain, Protocol, Socket, TcpKeepalive, Type}; use tokio::net::UdpSocket; -use crate::net::ConnectOpts; +use crate::net::{is_dual_stack_addr, sys::socket_bind_dual_stack, ConnectOpts}; cfg_if! { if #[cfg(any(target_os = "linux", target_os = "android"))] { @@ -34,44 +33,14 @@ cfg_if! { pub mod uds; /// Create a `UdpSocket` binded to `addr` -pub async fn create_inbound_udp_socket(addr: &SocketAddr) -> io::Result { - let set_dual_stack = if let SocketAddr::V6(ref v6) = *addr { - v6.ip().is_unspecified() - } else { - false - }; +pub async fn create_inbound_udp_socket(addr: &SocketAddr, ipv6_only: bool) -> io::Result { + let set_dual_stack = is_dual_stack_addr(addr); if !set_dual_stack { UdpSocket::bind(addr).await } else { let socket = Socket::new(Domain::for_address(*addr), Type::DGRAM, Some(Protocol::UDP))?; - - if let Err(err) = socket.set_only_v6(false) { - warn!("failed to set IPV6_V6ONLY: false for listener, error: {}", err); - - // This is not a fatal error, just warn and skip - } - - let saddr = SockAddr::from(*addr); - - match socket.bind(&saddr) { - Ok(..) => {} - Err(ref err) if err.kind() == ErrorKind::AddrInUse => { - // This is probably 0.0.0.0 with the same port has already been occupied - debug!( - "0.0.0.0:{} may have already been occupied, retry with IPV6_V6ONLY", - addr.port() - ); - - if let Err(err) = socket.set_only_v6(true) { - warn!("failed to set IPV6_V6ONLY: true for listener, error: {}", err); - - // This is not a fatal error, just warn and skip - } - socket.bind(&saddr)?; - } - Err(err) => return Err(err), - } + socket_bind_dual_stack(&socket, addr, ipv6_only)?; // UdpSocket::from_std requires socket to be non-blocked socket.set_nonblocking(true)?; diff --git a/crates/shadowsocks/src/net/sys/windows/mod.rs b/crates/shadowsocks/src/net/sys/windows/mod.rs index 83f4b37b..e5ecdd43 100644 --- a/crates/shadowsocks/src/net/sys/windows/mod.rs +++ b/crates/shadowsocks/src/net/sys/windows/mod.rs @@ -9,9 +9,9 @@ use std::{ task::{self, Poll}, }; -use log::{debug, error, warn}; +use log::error; use pin_project::pin_project; -use socket2::{Domain, Protocol, SockAddr, Socket, TcpKeepalive, Type}; +use socket2::{Domain, Protocol, Socket, TcpKeepalive, Type}; use tokio::{ io::{AsyncRead, AsyncWrite, ReadBuf}, net::{TcpSocket, TcpStream as TokioTcpStream, UdpSocket}, @@ -32,7 +32,12 @@ use winapi::{ }, }; -use crate::net::{sys::set_common_sockopt_for_connect, AddrFamily, ConnectOpts}; +use crate::net::{ + is_dual_stack_addr, + sys::{set_common_sockopt_for_connect, socket_bind_dual_stack}, + AddrFamily, + ConnectOpts, +}; // ws2ipdef.h // FIXME: Use winapi's definition if issue resolved @@ -268,44 +273,14 @@ fn disable_connection_reset(socket: &UdpSocket) -> io::Result<()> { /// Create a `UdpSocket` binded to `addr` /// /// It also disables `WSAECONNRESET` for UDP socket -pub async fn create_inbound_udp_socket(addr: &SocketAddr) -> io::Result { - let set_dual_stack = if let SocketAddr::V6(ref v6) = *addr { - v6.ip().is_unspecified() - } else { - false - }; +pub async fn create_inbound_udp_socket(addr: &SocketAddr, ipv6_only: bool) -> io::Result { + let set_dual_stack = is_dual_stack_addr(addr); let socket = if !set_dual_stack { UdpSocket::bind(addr).await? } else { let socket = Socket::new(Domain::for_address(*addr), Type::DGRAM, Some(Protocol::UDP))?; - - if let Err(err) = socket.set_only_v6(false) { - warn!("failed to set IPV6_V6ONLY: false for listener, error: {}", err); - - // This is not a fatal error, just warn and skip - } - - let saddr = SockAddr::from(*addr); - - match socket.bind(&saddr) { - Ok(..) => {} - Err(ref err) if err.kind() == ErrorKind::AddrInUse => { - // This is probably 0.0.0.0 with the same port has already been occupied - debug!( - "0.0.0.0:{} may have already been occupied, retry with IPV6_V6ONLY", - addr.port() - ); - - if let Err(err) = socket.set_only_v6(true) { - warn!("failed to set IPV6_V6ONLY: true for listener, error: {}", err); - - // This is not a fatal error, just warn and skip - } - socket.bind(&saddr)?; - } - Err(err) => return Err(err), - } + socket_bind_dual_stack(&socket, addr, ipv6_only)?; // UdpSocket::from_std requires socket to be non-blocked socket.set_nonblocking(true)?; diff --git a/crates/shadowsocks/src/net/tcp.rs b/crates/shadowsocks/src/net/tcp.rs index 1cb11227..bb116997 100644 --- a/crates/shadowsocks/src/net/tcp.rs +++ b/crates/shadowsocks/src/net/tcp.rs @@ -5,7 +5,7 @@ use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; #[cfg(windows)] use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket}; use std::{ - io::{self, ErrorKind}, + io, net::SocketAddr, ops::{Deref, DerefMut}, pin::Pin, @@ -13,7 +13,6 @@ use std::{ }; use futures::{future, ready}; -use log::warn; use pin_project::pin_project; use socket2::{Socket, TcpKeepalive}; use tokio::{ @@ -24,7 +23,8 @@ use tokio::{ use crate::{context::Context, relay::socks5::Address, ServerAddr}; use super::{ - sys::{set_tcp_fastopen, TcpStream as SysTcpStream}, + is_dual_stack_addr, + sys::{set_tcp_fastopen, socket_bind_dual_stack, TcpStream as SysTcpStream}, AcceptOpts, ConnectOpts, }; @@ -143,52 +143,10 @@ impl TcpListener { #[cfg(not(windows))] socket.set_reuseaddr(true)?; - let set_dual_stack = if let SocketAddr::V6(ref v6) = *addr { - v6.ip().is_unspecified() - } else { - false - }; + let set_dual_stack = is_dual_stack_addr(addr); if set_dual_stack { - // Set to DUAL STACK mode by default. - // WARNING: This would fail if you want to start another program listening on the same port. - // - // Should this behavior be configurable? - fn set_only_v6(socket: &TcpSocket, only_v6: bool) { - unsafe { - // WARN: If the following code panics, FD will be closed twice. - #[cfg(unix)] - let s = Socket::from_raw_fd(socket.as_raw_fd()); - #[cfg(windows)] - let s = Socket::from_raw_socket(socket.as_raw_socket()); - if let Err(err) = s.set_only_v6(only_v6) { - warn!("failed to set IPV6_V6ONLY: {} for listener, error: {}", only_v6, err); - - // This is not a fatal error, just warn and skip - } - - #[cfg(unix)] - let _ = s.into_raw_fd(); - #[cfg(windows)] - let _ = s.into_raw_socket(); - } - } - - set_only_v6(&socket, false); - match socket.bind(*addr) { - Ok(..) => {} - Err(ref err) if err.kind() == ErrorKind::AddrInUse => { - // This is probably 0.0.0.0 with the same port has already been occupied - warn!( - "0.0.0.0:{} may have already been occupied, retry with IPV6_V6ONLY", - addr.port() - ); - - set_only_v6(&socket, true); - socket.bind(*addr)?; - } - Err(err) => return Err(err), - } + socket_bind_dual_stack(&socket, addr, accept_opts.ipv6_only)?; } else { socket.bind(*addr)?; } diff --git a/crates/shadowsocks/src/net/udp.rs b/crates/shadowsocks/src/net/udp.rs index ea6fee25..195b81c1 100644 --- a/crates/shadowsocks/src/net/udp.rs +++ b/crates/shadowsocks/src/net/udp.rs @@ -12,6 +12,7 @@ use crate::{context::Context, relay::socks5::Address, ServerAddr}; use super::{ sys::{create_inbound_udp_socket, create_outbound_udp_socket}, + AcceptOpts, AddrFamily, ConnectOpts, }; @@ -76,9 +77,15 @@ impl UdpSocket { Ok(UdpSocket(socket)) } - /// Binds to a specific address + /// Binds to a specific address (inbound) + #[inline] pub async fn listen(addr: &SocketAddr) -> io::Result { - let socket = create_inbound_udp_socket(addr).await?; + UdpSocket::listen_with_opts(addr, AcceptOpts::default()).await + } + + /// Binds to a specific address (inbound) + pub async fn listen_with_opts(addr: &SocketAddr, opts: AcceptOpts) -> io::Result { + let socket = create_inbound_udp_socket(addr, opts.ipv6_only).await?; Ok(UdpSocket(socket)) } diff --git a/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs b/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs index 9d6cc2c4..566a0331 100644 --- a/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs +++ b/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs @@ -14,7 +14,7 @@ use crate::{ config::{ServerAddr, ServerConfig}, context::SharedContext, crypto::v1::CipherKind, - net::{ConnectOpts, UdpSocket as ShadowUdpSocket}, + net::{AcceptOpts, ConnectOpts, UdpSocket as ShadowUdpSocket}, relay::socks5::Address, }; @@ -72,11 +72,23 @@ impl ProxySocket { /// Create a `ProxySocket` binding to a specific address (inbound) pub async fn bind(context: SharedContext, svr_cfg: &ServerConfig) -> io::Result { + ProxySocket::bind_with_opts(context, svr_cfg, AcceptOpts::default()).await + } + + /// Create a `ProxySocket` binding to a specific address (inbound) + pub async fn bind_with_opts( + context: SharedContext, + svr_cfg: &ServerConfig, + opts: AcceptOpts, + ) -> io::Result { // Plugins doesn't support UDP let socket = match svr_cfg.addr() { - ServerAddr::SocketAddr(sa) => ShadowUdpSocket::listen(sa).await?, + ServerAddr::SocketAddr(sa) => ShadowUdpSocket::listen_with_opts(sa, opts).await?, ServerAddr::DomainName(domain, port) => { - lookup_then!(&context, domain, *port, |addr| { ShadowUdpSocket::listen(&addr).await })?.1 + lookup_then!(&context, domain, *port, |addr| { + ShadowUdpSocket::listen_with_opts(&addr, opts.clone()).await + })? + .1 } }; Ok(ProxySocket::from_socket(context, svr_cfg, socket.into()))