mirror of
https://github.com/shadowsocks/shadowsocks-rust.git
synced 2026-11-06 16:14:11 +00:00
fix(local-redir): EINVAL if platform doesn't support IPv4-mapped-IPv6
- ref #1543
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
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);
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
// 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());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
trace!(
|
||||
"udp redir send back data {} bytes, remote: {}, peer: {}, socket_opts: {:?}",
|
||||
n,
|
||||
remote_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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: {}",
|
||||
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
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user