Skip to content

Commit

Permalink
feat(local-redir, server): Check IP stack capability globally
Browse files Browse the repository at this point in the history
- ref #1543
- Reference Implementation: golang src/net/ipsock_posix.go
  ipStckCapabilities
  • Loading branch information
zonyitoo committed Jun 16, 2024
1 parent 79d68f1 commit 5ba8b7d
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 226 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "shadowsocks-rust"
version = "1.20.0"
version = "1.20.1"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"
Expand Down Expand Up @@ -248,7 +248,7 @@ jemallocator = { version = "0.5", optional = true }
snmalloc-rs = { version = "0.3", optional = true }
rpmalloc = { version = "0.2", optional = true }

shadowsocks-service = { version = "1.20.0", path = "./crates/shadowsocks-service" }
shadowsocks-service = { version = "1.20.1", path = "./crates/shadowsocks-service" }

windows-service = { version = "0.7", optional = true }

Expand Down
4 changes: 2 additions & 2 deletions crates/shadowsocks-service/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "shadowsocks-service"
version = "1.20.0"
version = "1.20.1"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"
Expand Down Expand Up @@ -198,7 +198,7 @@ serde = { version = "1.0", features = ["derive"] }
json5 = "0.4"
bson = { version = "2.10.0", optional = true }

shadowsocks = { version = "1.20.0", path = "../shadowsocks", default-features = false }
shadowsocks = { version = "1.20.1", path = "../shadowsocks", default-features = false }

# Just for the ioctl call macro
[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))'.dependencies]
Expand Down
203 changes: 86 additions & 117 deletions crates/shadowsocks-service/src/local/redir/udprelay/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
use std::{
io::{self, ErrorKind},
net::{IpAddr, SocketAddr},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
sync::Arc,
time::Duration,
};

Expand All @@ -15,7 +12,7 @@ use log::{debug, error, info, trace, warn};
use lru_time_cache::LruCache;
use shadowsocks::{
lookup_then,
net::ConnectOpts,
net::{get_ip_stack_capabilities, ConnectOpts},
relay::{socks5::Address, udprelay::MAXIMUM_UDP_PAYLOAD_SIZE},
ServerAddr,
};
Expand Down Expand Up @@ -99,134 +96,106 @@ impl UdpInboundWrite for UdpRedirInboundWriter {
async fn send_to(&self, mut peer_addr: SocketAddr, remote_addr: &Address, data: &[u8]) -> io::Result<()> {
// If IPv6 Transparent Proxy is supported on the current platform,
// then we should always use IPv6 sockets for sending IPv4 packets.
static SUPPORT_IPV6_TRANSPARENT: AtomicBool = AtomicBool::new(true);

loop {
let mut addr_mapped_ipv6 = false;

let addr = match *remote_addr {
Address::SocketAddress(sa) => {
if SUPPORT_IPV6_TRANSPARENT.load(Ordering::Relaxed) {
match sa {
// Converts IPv4 address to IPv4-mapped-IPv6
// All sockets will be created in IPv6 (nearly all modern OS supports IPv6 sockets)
SocketAddr::V4(ref v4) => {
addr_mapped_ipv6 = true;
SocketAddr::new(v4.ip().to_ipv6_mapped().into(), v4.port())
}
SocketAddr::V6(..) => sa,
let ip_stack_caps = get_ip_stack_capabilities();

let addr = match *remote_addr {
Address::SocketAddress(sa) => {
match sa {
SocketAddr::V4(ref v4) => {
// If IPv4-mapped-IPv6 is supported.
// Converts IPv4 address to IPv4-mapped-IPv6
// All sockets will be created in IPv6 (nearly all modern OS supports IPv6 sockets)
if ip_stack_caps.support_ipv4_mapped_ipv6 {
SocketAddr::new(v4.ip().to_ipv6_mapped().into(), v4.port())
} else {
sa
}
} else {
match sa {
// Converts IPv4-mapped-IPv6 to IPv4
SocketAddr::V4(..) => sa,
SocketAddr::V6(ref v6) => match v6.ip().to_ipv4_mapped() {
}
SocketAddr::V6(ref v6) => {
// If IPv6 is not supported. Try to map it back to IPv4.
if !ip_stack_caps.support_ipv6 || !ip_stack_caps.support_ipv4_mapped_ipv6 {
match v6.ip().to_ipv4_mapped() {
Some(v4) => SocketAddr::new(v4.into(), v6.port()),
None => sa,
},
}
} else {
sa
}
}
}
Address::DomainNameAddress(..) => {
let err = io::Error::new(
ErrorKind::InvalidInput,
"redir destination must not be an domain name address",
);
return Err(err);
}
};

let inbound = {
let mut cache = self.inbound_cache.cache.lock().await;
if let Some(socket) = cache.get(&addr) {
socket.clone()
} else {
// Create a socket binds to destination addr
// This only works for systems that supports binding to non-local addresses
//
// This socket has to set SO_REUSEADDR and SO_REUSEPORT.
// Outbound addresses could be connected from different source addresses.
let inbound = match UdpRedirSocket::bind_nonlocal(self.redir_ty, addr, &self.socket_opts) {
Ok(s) => s,
#[cfg(unix)]
Err(err) => match err.raw_os_error() {
None => return Err(err),
// https://github.com/shadowsocks/shadowsocks-rust/issues/988
// IPV6_TRANSPARENT was supported since 2.6.37.
Some(libc::ENOPROTOOPT) if addr_mapped_ipv6 => {
SUPPORT_IPV6_TRANSPARENT.store(false, Ordering::Relaxed);
debug!("redir destination socket doesn't support IPv6, addr cannot be IPv4-mapped-IPv6: {}", addr);
continue;
}
Some(_) => return Err(err),
},
#[cfg(not(unix))]
Err(err) => return Err(err),
};
}
Address::DomainNameAddress(..) => {
let err = io::Error::new(
ErrorKind::InvalidInput,
"redir destination must not be an domain name address",
);
return Err(err);
}
};

// UDP socket could be shared between threads and is safe to be manipulated by multiple threads
let inbound = Arc::new(inbound);
cache.insert(addr, inbound.clone());
let inbound = {
let mut cache = self.inbound_cache.cache.lock().await;
if let Some(socket) = cache.get(&addr) {
socket.clone()
} else {
// Create a socket binds to destination addr
// This only works for systems that supports binding to non-local addresses
//
// This socket has to set SO_REUSEADDR and SO_REUSEPORT.
// Outbound addresses could be connected from different source addresses.
let inbound = UdpRedirSocket::bind_nonlocal(self.redir_ty, addr, &self.socket_opts)?;

// UDP socket could be shared between threads and is safe to be manipulated by multiple threads
let inbound = Arc::new(inbound);
cache.insert(addr, inbound.clone());

inbound
}
};

inbound
}
};

match (addr, peer_addr) {
(SocketAddr::V4(..), SocketAddr::V4(..)) | (SocketAddr::V6(..), SocketAddr::V6(..)) => {}
(SocketAddr::V4(..), SocketAddr::V6(v6_peer_addr)) => {
if let Some(v4_ip) = v6_peer_addr.ip().to_ipv4_mapped() {
peer_addr = SocketAddr::new(v4_ip.into(), v6_peer_addr.port());
} else {
warn!(
"udp redir send back {} bytes, remote: {}, peer: {}, protocol not match",
data.len(),
addr,
peer_addr
);
}
}
(SocketAddr::V6(..), SocketAddr::V4(v4_peer_addr)) => {
peer_addr = SocketAddr::new(v4_peer_addr.ip().to_ipv6_mapped().into(), v4_peer_addr.port());
// Convert peer_addr (client)'s address family to match remote_addr (target)
match (addr, peer_addr) {
(SocketAddr::V4(..), SocketAddr::V4(..)) | (SocketAddr::V6(..), SocketAddr::V6(..)) => {}
(SocketAddr::V4(..), SocketAddr::V6(v6_peer_addr)) => {
if let Some(v4_ip) = v6_peer_addr.ip().to_ipv4_mapped() {
peer_addr = SocketAddr::new(v4_ip.into(), v6_peer_addr.port());
} else {
warn!(
"udp redir send back {} bytes, remote: {}, peer: {}, protocol not match",
data.len(),
addr,
peer_addr
);
}
}
(SocketAddr::V6(..), SocketAddr::V4(v4_peer_addr)) => {
peer_addr = SocketAddr::new(v4_peer_addr.ip().to_ipv6_mapped().into(), v4_peer_addr.port());
}
}

match inbound.send_to(data, peer_addr).await {
Ok(n) => {
if n < data.len() {
warn!(
"udp redir send back data (actual: {} bytes, sent: {} bytes), remote: {}, peer: {}",
n,
data.len(),
remote_addr,
peer_addr
);
}

trace!(
"udp redir send back data {} bytes, remote: {}, peer: {}, socket_opts: {:?}",
match inbound.send_to(data, peer_addr).await {
Ok(n) => {
if n < data.len() {
warn!(
"udp redir send back data (actual: {} bytes, sent: {} bytes), remote: {}, peer: {}",
n,
data.len(),
remote_addr,
peer_addr,
self.socket_opts
peer_addr
);

return Ok(());
}
Err(err) => {
match err.kind() {
// Invalid Argument
ErrorKind::InvalidInput if addr_mapped_ipv6 => {
SUPPORT_IPV6_TRANSPARENT.store(false, Ordering::Relaxed);
debug!(
"redir destination socket doesn't support dual-stack routing, addr cannot be IPv4-mapped-IPv6: {}",
addr
);
}
_ => return Err(err),
}
}

trace!(
"udp redir send back data {} bytes, remote: {}, peer: {}, socket_opts: {:?}",
n,
remote_addr,
peer_addr,
self.socket_opts
);

Ok(())
}
Err(err) => Err(err),
}
}
}
Expand Down
Loading

0 comments on commit 5ba8b7d

Please sign in to comment.