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:
zonyitoo
2021-11-27 14:00:42 +08:00
committed by ty
parent 48e822d104
commit 17d8475ceb
26 changed files with 472 additions and 240 deletions

2
Cargo.lock generated
View File

@@ -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
View File

@@ -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)
<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

View File

@@ -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"

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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())?;

View File

@@ -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())?;

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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 {}",

View File

@@ -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
}
}

View File

@@ -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,
}

View File

@@ -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(())
}

View File

@@ -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)?;

View File

@@ -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)?;

View File

@@ -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)?;
}

View File

@@ -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))
}

View File

@@ -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()))