diff --git a/Cargo.lock b/Cargo.lock index 9d969d982828..70744bb39b3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 61773b69a2ac..779de73fc4ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/crates/shadowsocks-service/Cargo.toml b/crates/shadowsocks-service/Cargo.toml index df2489a7af21..407b71b30a55 100644 --- a/crates/shadowsocks-service/Cargo.toml +++ b/crates/shadowsocks-service/Cargo.toml @@ -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] diff --git a/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs b/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs index bf5a1202d328..1c327a6c7790 100644 --- a/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs +++ b/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs @@ -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); - - 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), } } } diff --git a/crates/shadowsocks-service/src/server/udprelay.rs b/crates/shadowsocks-service/src/server/udprelay.rs index 5719ad769402..60a0a1cd0974 100644 --- a/crates/shadowsocks-service/src/server/udprelay.rs +++ b/crates/shadowsocks-service/src/server/udprelay.rs @@ -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", - ))); - - 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 ip_stack_caps = get_ip_stack_capabilities(); + + 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) + } + 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 } - } 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(), - ) + } + }; + + 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(), - ) + 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) - } - }, + 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(()); + 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() + ); } - 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), - }, + Ok(()) } + Err(err) => Err(err), } } diff --git a/crates/shadowsocks/Cargo.toml b/crates/shadowsocks/Cargo.toml index 45df2fd9eebf..cd7d3792d6f3 100644 --- a/crates/shadowsocks/Cargo.toml +++ b/crates/shadowsocks/Cargo.toml @@ -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" diff --git a/crates/shadowsocks/src/net/mod.rs b/crates/shadowsocks/src/net/mod.rs index a47bda3dc4be..5a6d4a9949b4 100644 --- a/crates/shadowsocks/src/net/mod.rs +++ b/crates/shadowsocks/src/net/mod.rs @@ -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, }; diff --git a/crates/shadowsocks/src/net/sys/mod.rs b/crates/shadowsocks/src/net/sys/mod.rs index 0f5ac079a8b4..8c7cf401a8fc 100644 --- a/crates/shadowsocks/src/net/sys/mod.rs +++ b/crates/shadowsocks/src/net/sys/mod.rs @@ -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 = 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 +}