Skip to content

Commit

Permalink
fix(local-redir): EINVAL if platform doesn't support IPv4-mapped-IPv6
Browse files Browse the repository at this point in the history
- ref #1543
  • Loading branch information
zonyitoo committed Jun 16, 2024
1 parent ac67a08 commit eddd439
Showing 1 changed file with 110 additions and 101 deletions.
211 changes: 110 additions & 101 deletions crates/shadowsocks-service/src/local/redir/udprelay/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
})
}
}
}

Expand Down

0 comments on commit eddd439

Please sign in to comment.