mirror of
https://github.com/shadowsocks/shadowsocks-rust.git
synced 2026-02-09 01:59:16 +08:00
support ipv6_only socket option (IPV6_V6ONLY)
This is useful for creating multiple local / server instances listening on both `::` and `0.0.0.0` with the same port.` Listener sockets (both TCP and UDP) in redir local is now IPV6_V6ONLY=1 because transparent proxies couldn't handle V4 and V6 packets simultaneously properly.
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -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",
|
||||
|
||||
178
README.md
178
README.md
@@ -12,7 +12,6 @@
|
||||
|
||||
[](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)
|
||||
|
||||
<details><summary>Deprecated</summary>
|
||||
<p>
|
||||
|
||||
* `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`
|
||||
|
||||
</p>
|
||||
</details>
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -110,53 +110,72 @@ struct SSConfig {
|
||||
server: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
server_port: Option<u16>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
local_address: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
local_port: Option<u16>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
protocol: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
manager_address: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
manager_port: Option<u16>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
password: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
method: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
plugin: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
plugin_opts: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
plugin_args: Option<Vec<String>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
timeout: Option<u64>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
udp_timeout: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
udp_max_associations: Option<usize>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none", alias = "shadowsocks")]
|
||||
servers: Option<Vec<SSServerExtConfig>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
locals: Option<Vec<SSLocalExtConfig>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
dns: Option<SSDnsConfig>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
mode: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
no_delay: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
keep_alive: Option<u64>,
|
||||
|
||||
#[cfg(all(unix, not(target_os = "android")))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
nofile: Option<u64>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
ipv6_first: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
ipv6_only: Option<bool>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fast_open: Option<bool>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
security: Option<SSSecurityConfig>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
balancer: Option<SSBalancerConfig>,
|
||||
}
|
||||
@@ -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<bool>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
plugin: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
plugin_opts: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
plugin_args: Option<Vec<String>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
timeout: Option<u64>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none", alias = "name")]
|
||||
remarks: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
id: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
mode: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
tcp_weight: Option<f32>,
|
||||
#[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<PathBuf>,
|
||||
|
||||
/// 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 {
|
||||
|
||||
@@ -117,6 +117,7 @@ pub async fn create(config: Config) -> io::Result<Server> {
|
||||
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;
|
||||
|
||||
@@ -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<TcpListener>;
|
||||
async fn bind_redir(ty: RedirType, addr: SocketAddr, accept_opts: AcceptOpts) -> io::Result<TcpListener>;
|
||||
}
|
||||
|
||||
/// Extension function for `TcpStream` for reading original destination address
|
||||
|
||||
@@ -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<S>(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<S>(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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<TcpListener> {
|
||||
async fn bind_redir(ty: RedirType, addr: SocketAddr, accept_opts: AcceptOpts) -> io::Result<TcpListener> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<TcpListener> {
|
||||
async fn bind_redir(ty: RedirType, addr: SocketAddr, accept_opts: AcceptOpts) -> io::Result<TcpListener> {
|
||||
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<SocketAddr> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_redir_listener(addr: SocketAddr) -> io::Result<TcpListener> {
|
||||
async fn create_redir_listener(addr: SocketAddr, accept_opts: AcceptOpts) -> io::Result<TcpListener> {
|
||||
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<TcpListener> {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -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())?;
|
||||
|
||||
@@ -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())?;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SocketAddr>,
|
||||
accept_opts: AcceptOpts,
|
||||
}
|
||||
|
||||
impl Drop for UdpServer {
|
||||
@@ -45,7 +46,12 @@ impl Drop for UdpServer {
|
||||
}
|
||||
|
||||
impl UdpServer {
|
||||
pub fn new(context: Arc<ServiceContext>, time_to_live: Option<Duration>, capacity: Option<usize>) -> UdpServer {
|
||||
pub fn new(
|
||||
context: Arc<ServiceContext>,
|
||||
time_to_live: Option<Duration>,
|
||||
capacity: Option<usize>,
|
||||
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 {}",
|
||||
|
||||
@@ -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<SocketAddr> 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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<S>(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<S>(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(())
|
||||
}
|
||||
|
||||
@@ -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<UdpSocket> {
|
||||
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<UdpSocket> {
|
||||
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)?;
|
||||
|
||||
@@ -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<UdpSocket> {
|
||||
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<UdpSocket> {
|
||||
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)?;
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
@@ -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<UdpSocket> {
|
||||
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<UdpSocket> {
|
||||
let socket = create_inbound_udp_socket(addr, opts.ipv6_only).await?;
|
||||
Ok(UdpSocket(socket))
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
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<ProxySocket> {
|
||||
// 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()))
|
||||
|
||||
Reference in New Issue
Block a user