feat(local-redir, server): Check IP stack capability globally

- ref #1543
- Reference Implementation: golang src/net/ipsock_posix.go
  ipStckCapabilities
This commit is contained in:
zonyitoo
2024-06-16 19:55:59 +08:00
parent 79d68f1566
commit 5ba8b7dcd4
8 changed files with 223 additions and 239 deletions
Generated
+3 -3
View File
@@ -3098,7 +3098,7 @@ dependencies = [
[[package]]
name = "shadowsocks"
version = "1.20.0"
version = "1.20.1"
dependencies = [
"aes",
"arc-swap",
@@ -3164,7 +3164,7 @@ dependencies = [
[[package]]
name = "shadowsocks-rust"
version = "1.20.0"
version = "1.20.1"
dependencies = [
"base64 0.22.1",
"build-time",
@@ -3205,7 +3205,7 @@ dependencies = [
[[package]]
name = "shadowsocks-service"
version = "1.20.0"
version = "1.20.1"
dependencies = [
"arc-swap",
"async-trait",
+2 -2
View File
@@ -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"
@@ -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 }
+2 -2
View File
@@ -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"
@@ -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]
@@ -3,10 +3,7 @@
use std::{
io::{self, ErrorKind},
net::{IpAddr, SocketAddr},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
sync::Arc,
time::Duration,
};
@@ -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,
};
@@ -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);
let ip_stack_caps = get_ip_stack_capabilities();
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 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,
},
}
}
}
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),
};
// 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
}
};
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: {:?}",
n,
remote_addr,
peer_addr,
self.socket_opts
);
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
);
} else {
sa
}
_ => return Err(err),
}
}
}
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 = 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
}
};
// 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: {:?}",
n,
remote_addr,
peer_addr,
self.socket_opts
);
Ok(())
}
Err(err) => Err(err),
}
}
}
+65 -104
View File
@@ -3,11 +3,8 @@
use std::{
cell::RefCell,
io::{self, ErrorKind},
net::{SocketAddr, SocketAddrV6},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
net::SocketAddr,
sync::Arc,
time::Duration,
};
@@ -20,7 +17,7 @@ use shadowsocks::{
config::ServerUser,
crypto::CipherCategory,
lookup_then,
net::{AcceptOpts, AddrFamily, UdpSocket as OutboundUdpSocket},
net::{get_ip_stack_capabilities, AcceptOpts, AddrFamily, UdpSocket as OutboundUdpSocket},
relay::{
socks5::Address,
udprelay::{options::UdpSocketControlData, ProxySocket, MAXIMUM_UDP_PAYLOAD_SIZE},
@@ -32,7 +29,10 @@ use tokio::{runtime::Handle, sync::mpsc, task::JoinHandle, time};
use windows_sys::Win32::Networking::WinSock::WSAEAFNOSUPPORT;
use crate::net::{
packet_window::PacketWindowFilter, utils::to_ipv4_mapped, MonProxySocket, UDP_ASSOCIATION_KEEP_ALIVE_CHANNEL_SIZE,
packet_window::PacketWindowFilter,
utils::to_ipv4_mapped,
MonProxySocket,
UDP_ASSOCIATION_KEEP_ALIVE_CHANNEL_SIZE,
UDP_ASSOCIATION_SEND_CHANNEL_SIZE,
};
@@ -665,106 +665,67 @@ impl UdpAssociationContext {
}
async fn send_received_outbound_packet(&mut self, original_target_addr: SocketAddr, data: &[u8]) -> io::Result<()> {
static UDP_SOCKET_SUPPORT_DUAL_STACK: AtomicBool = AtomicBool::new(cfg!(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "ios",
target_os = "watchos",
target_os = "tvos",
target_os = "freebsd",
target_os = "windows",
)));
let ip_stack_caps = get_ip_stack_capabilities();
loop {
let mut target_addr = original_target_addr;
let mut target_addr_mapped_ipv6 = false;
let socket = if UDP_SOCKET_SUPPORT_DUAL_STACK.load(Ordering::Relaxed) {
if let SocketAddr::V4(saddr) = target_addr {
let mapped_ip = saddr.ip().to_ipv6_mapped();
target_addr = SocketAddr::V6(SocketAddrV6::new(mapped_ip, saddr.port(), 0, 0));
target_addr_mapped_ipv6 = true;
let target_addr = match original_target_addr {
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 {
original_target_addr
}
match self.outbound_ipv6_socket {
Some(ref mut socket) => socket,
None => {
let socket = match OutboundUdpSocket::connect_any_with_opts(
AddrFamily::Ipv6,
self.context.connect_opts_ref(),
)
.await
{
Ok(socket) => socket,
Err(err) => match err.raw_os_error() {
#[cfg(unix)]
Some(libc::EAFNOSUPPORT) => {
UDP_SOCKET_SUPPORT_DUAL_STACK.store(false, Ordering::Relaxed);
debug!("udp relay destination {} cannot be IPv4-mapped-IPv6, current platform doesn't support IPv6", target_addr);
continue;
}
#[cfg(windows)]
Some(WSAEAFNOSUPPORT) => {
UDP_SOCKET_SUPPORT_DUAL_STACK.store(false, Ordering::Relaxed);
debug!("udp relay destination {} cannot be IPv4-mapped-IPv6, current platform doesn't support IPv6", target_addr);
continue;
}
_ => return Err(err),
},
};
self.outbound_ipv6_socket.insert(socket)
}
}
} else {
match target_addr {
SocketAddr::V4(..) => match self.outbound_ipv4_socket {
Some(ref mut socket) => socket,
None => {
let socket = OutboundUdpSocket::connect_any_with_opts(
AddrFamily::Ipv4,
self.context.connect_opts_ref(),
)
.await?;
self.outbound_ipv4_socket.insert(socket)
}
},
SocketAddr::V6(..) => match self.outbound_ipv6_socket {
Some(ref mut socket) => socket,
None => {
let socket = OutboundUdpSocket::connect_any_with_opts(
AddrFamily::Ipv6,
self.context.connect_opts_ref(),
)
.await?;
self.outbound_ipv6_socket.insert(socket)
}
},
}
};
match socket.send_to(data, target_addr).await {
Ok(n) => {
if n != data.len() {
warn!(
"{} -> {} sent {} bytes != expected {} bytes",
self.peer_addr,
target_addr,
n,
data.len()
);
}
return Ok(());
}
Err(err) => match err.kind() {
ErrorKind::InvalidInput if target_addr_mapped_ipv6 => {
debug!("udp relay destination {} cannot be IPv4-mapped-IPv6, current platform doesn't support dual-stack routing", target_addr);
UDP_SOCKET_SUPPORT_DUAL_STACK.store(false, Ordering::Relaxed);
}
_ => return Err(err),
},
}
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 => original_target_addr,
}
} else {
original_target_addr
}
}
};
let socket = match target_addr {
SocketAddr::V4(..) => match self.outbound_ipv4_socket {
Some(ref mut socket) => socket,
None => {
let socket =
OutboundUdpSocket::connect_any_with_opts(AddrFamily::Ipv4, self.context.connect_opts_ref())
.await?;
self.outbound_ipv4_socket.insert(socket)
}
},
SocketAddr::V6(..) => match self.outbound_ipv6_socket {
Some(ref mut socket) => socket,
None => {
let socket =
OutboundUdpSocket::connect_any_with_opts(AddrFamily::Ipv6, self.context.connect_opts_ref())
.await?;
self.outbound_ipv6_socket.insert(socket)
}
},
};
match socket.send_to(data, target_addr).await {
Ok(n) => {
if n != data.len() {
warn!(
"{} -> {} sent {} bytes != expected {} bytes",
self.peer_addr,
target_addr,
n,
data.len()
);
}
Ok(())
}
Err(err) => Err(err),
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "shadowsocks"
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"
+1 -1
View File
@@ -6,7 +6,7 @@ use std::net::SocketAddr;
pub use self::sys::uds::{UnixListener, UnixStream};
pub use self::{
option::{AcceptOpts, ConnectOpts, TcpSocketOpts},
sys::{set_tcp_fastopen, socket_bind_dual_stack},
sys::{get_ip_stack_capabilities, set_tcp_fastopen, socket_bind_dual_stack, IpStackCapabilities},
tcp::{TcpListener, TcpStream},
udp::UdpSocket,
};
+56 -2
View File
@@ -1,11 +1,12 @@
use std::{
io::{self, ErrorKind},
net::{IpAddr, SocketAddr},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
};
use cfg_if::cfg_if;
use log::{debug, warn};
use socket2::{SockAddr, Socket};
use once_cell::sync::Lazy;
use socket2::{Domain, Protocol, SockAddr, Socket, Type};
use tokio::net::TcpSocket;
use super::ConnectOpts;
@@ -127,3 +128,56 @@ fn socket_bind_dual_stack_inner(socket: &Socket, addr: &SocketAddr, ipv6_only: b
Ok(())
}
/// IP Stack Capabilities
#[derive(Debug, Clone, Copy, Default)]
pub struct IpStackCapabilities {
/// IP stack supports IPv4
pub support_ipv4: bool,
/// IP stack supports IPv6
pub support_ipv6: bool,
/// IP stack supports IPv4-mapped-IPv6
pub support_ipv4_mapped_ipv6: bool,
}
static IP_STACK_CAPABILITIES: Lazy<IpStackCapabilities> = Lazy::new(|| {
// Reference Implementation: https://github.com/golang/go/blob/master/src/net/ipsock_posix.go
let mut caps = IpStackCapabilities {
support_ipv4: false,
support_ipv6: false,
support_ipv4_mapped_ipv6: false,
};
// Check IPv4
if let Ok(_) = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)) {
caps.support_ipv4 = true;
}
// Check IPv6 (::1)
if let Ok(ipv6_socket) = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP)) {
if let Ok(..) = ipv6_socket.set_only_v6(true) {
let local_host = SockAddr::from(SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 0));
if let Ok(..) = ipv6_socket.bind(&local_host) {
caps.support_ipv6 = true;
}
}
}
// Check IPv4-mapped-IPv6 (127.0.0.1)
if let Ok(ipv6_socket) = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP)) {
if let Ok(..) = ipv6_socket.set_only_v6(false) {
let local_host = SockAddr::from(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0));
if let Ok(..) = ipv6_socket.bind(&local_host) {
caps.support_ipv4_mapped_ipv6 = true;
}
}
}
caps
});
/// Get globally probed `IpStackCapabilities`
pub fn get_ip_stack_capabilities() -> &'static IpStackCapabilities {
&IP_STACK_CAPABILITIES
}