From eddd439ab301c98352999d92d34cebd32135c7cf Mon Sep 17 00:00:00 2001 From: zonyitoo Date: Sun, 16 Jun 2024 18:42:45 +0800 Subject: [PATCH] fix(local-redir): EINVAL if platform doesn't support IPv4-mapped-IPv6 - ref #1543 --- .../src/local/redir/udprelay/mod.rs | 211 +++++++++--------- 1 file changed, 110 insertions(+), 101 deletions(-) diff --git a/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs b/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs index 0cb931920684..a28de664b3b3 100644 --- a/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs +++ b/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs @@ -101,124 +101,133 @@ impl UdpInboundWrite for UdpRedirInboundWriter { // then we should always use IPv6 sockets for sending IPv4 packets. static SUPPORT_IPV6_TRANSPARENT: AtomicBool = AtomicBool::new(true); - #[allow(unused_mut)] - let mut 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) => SocketAddr::new(v4.ip().to_ipv6_mapped().into(), v4.port()), - SocketAddr::V6(..) => sa, - } - } else { - match sa { - // Converts IPv4-mapped-IPv6 to IPv4 - SocketAddr::V4(..) => sa, - SocketAddr::V6(ref v6) => match v6.ip().to_ipv4_mapped() { - Some(v4) => SocketAddr::new(v4.into(), v6.port()), - None => sa, - }, + 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, + } + } else { + match sa { + // Converts IPv4-mapped-IPv6 to IPv4 + SocketAddr::V4(..) => sa, + SocketAddr::V6(ref v6) => 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.is_ipv6() => { - SUPPORT_IPV6_TRANSPARENT.store(false, Ordering::Relaxed); + Address::DomainNameAddress(..) => { + let err = io::Error::new( + ErrorKind::InvalidInput, + "redir destination must not be an domain name address", + ); + return Err(err); + } + }; - addr = match *remote_addr { - Address::SocketAddress(sa) => { - match sa { - // Converts IPv4-mapped-IPv6 to IPv4 - SocketAddr::V4(..) => sa, - SocketAddr::V6(ref v6) => match v6.ip().to_ipv4_mapped() { - Some(v4) => SocketAddr::new(v4.into(), v6.port()), - None => return Err(err), - }, - } - } - Address::DomainNameAddress(..) => unreachable!(), - }; - - UdpRedirSocket::bind_nonlocal(self.redir_ty, addr, &self.socket_opts)? - } - Some(_) => return Err(err), - }, - #[cfg(not(unix))] - Err(err) => 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()); + // 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()); + } } - }; - 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 { + let send_result = inbound.send_to(data, peer_addr).await.map(|n| { + if n < data.len() { warn!( - "udp redir send back {} bytes, remote: {}, peer: {}, protocol not match", + "udp redir send back data (actual: {} bytes, sent: {} bytes), remote: {}, peer: {}", + n, data.len(), - addr, + remote_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()); - } - } - inbound.send_to(data, peer_addr).await.map(|n| { - if n < data.len() { - warn!( - "udp redir send back data (actual: {} bytes, sent: {} bytes), remote: {}, peer: {}", + trace!( + "udp redir send back data {} bytes, remote: {}, peer: {}, socket_opts: {:?}", n, - data.len(), remote_addr, - peer_addr + peer_addr, + self.socket_opts ); + }); + + match send_result { + Ok(()) => 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 IPv6, 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 - ); - }) + } } }