mirror of
https://github.com/shadowsocks/shadowsocks-rust.git
synced 2026-11-06 16:14:11 +00:00
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:
Generated
+3
-3
@@ -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
@@ -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 }
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user