mirror of
https://github.com/awfufu/traudit
synced 2026-03-01 05:29:44 +08:00
feat: support dual-stack bind syntax and normalize ipv4-mapped ipv6 clients
This commit is contained in:
@@ -11,6 +11,34 @@ use tokio::net::{TcpListener, UnixListener, UnixStream};
|
|||||||
use tokio_openssl::SslStream;
|
use tokio_openssl::SslStream;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
|
fn normalize_ipv4_mapped_addr(addr: std::net::SocketAddr) -> std::net::SocketAddr {
|
||||||
|
match addr {
|
||||||
|
std::net::SocketAddr::V6(v6) => {
|
||||||
|
if let Some(v4) = v6.ip().to_ipv4_mapped() {
|
||||||
|
std::net::SocketAddr::new(std::net::IpAddr::V4(v4), v6.port())
|
||||||
|
} else {
|
||||||
|
std::net::SocketAddr::V6(v6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_tcp_bind_target(addr_str: &str) -> anyhow::Result<(std::net::SocketAddr, Option<bool>)> {
|
||||||
|
let (normalized_addr, force_v6_only) = if let Some(port) = addr_str.strip_prefix(":::") {
|
||||||
|
(format!("[::]:{}", port), Some(true))
|
||||||
|
} else if let Some(port) = addr_str.strip_prefix(":") {
|
||||||
|
(format!("[::]:{}", port), Some(false))
|
||||||
|
} else if let Some(port) = addr_str.strip_prefix("*:") {
|
||||||
|
(format!("[::]:{}", port), Some(false))
|
||||||
|
} else {
|
||||||
|
(addr_str.to_string(), None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let addr: std::net::SocketAddr = normalized_addr.parse()?;
|
||||||
|
Ok((addr, force_v6_only))
|
||||||
|
}
|
||||||
|
|
||||||
pub enum UnifiedListener {
|
pub enum UnifiedListener {
|
||||||
Tcp(TcpListener),
|
Tcp(TcpListener),
|
||||||
Unix(UnixListener, PathBuf), // PathBuf for cleanup on Drop
|
Unix(UnixListener, PathBuf), // PathBuf for cleanup on Drop
|
||||||
@@ -30,7 +58,7 @@ impl UnifiedListener {
|
|||||||
match self {
|
match self {
|
||||||
UnifiedListener::Tcp(l) => {
|
UnifiedListener::Tcp(l) => {
|
||||||
let (stream, addr) = l.accept().await?;
|
let (stream, addr) = l.accept().await?;
|
||||||
Ok((InboundStream::Tcp(stream), addr))
|
Ok((InboundStream::Tcp(stream), normalize_ipv4_mapped_addr(addr)))
|
||||||
}
|
}
|
||||||
UnifiedListener::Unix(l, _) => {
|
UnifiedListener::Unix(l, _) => {
|
||||||
let (stream, _addr) = l.accept().await?;
|
let (stream, _addr) = l.accept().await?;
|
||||||
@@ -136,21 +164,12 @@ pub async fn bind_listener(
|
|||||||
} else {
|
} else {
|
||||||
// TCP with SO_REUSEPORT
|
// TCP with SO_REUSEPORT
|
||||||
use nix::sys::socket::{setsockopt, sockopt};
|
use nix::sys::socket::{setsockopt, sockopt};
|
||||||
use std::net::SocketAddr;
|
|
||||||
// AsRawFd removed
|
// AsRawFd removed
|
||||||
|
|
||||||
let normalized_addr = if addr_str.starts_with(":::") {
|
let (addr, force_v6_only) = parse_tcp_bind_target(addr_str).map_err(|e| {
|
||||||
format!("[::]:{}", &addr_str[3..])
|
error!("[{}] invalid address {}: {}", service_name, addr_str, e);
|
||||||
} else {
|
e
|
||||||
addr_str.to_string()
|
})?;
|
||||||
};
|
|
||||||
|
|
||||||
let addr: SocketAddr = normalized_addr
|
|
||||||
.parse()
|
|
||||||
.map_err(|e: std::net::AddrParseError| {
|
|
||||||
error!("[{}] invalid address {}: {}", service_name, addr_str, e);
|
|
||||||
anyhow::anyhow!(e)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let domain = if addr.is_ipv4() {
|
let domain = if addr.is_ipv4() {
|
||||||
socket2::Domain::IPV4
|
socket2::Domain::IPV4
|
||||||
@@ -173,6 +192,18 @@ pub async fn bind_listener(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if addr.is_ipv6() {
|
||||||
|
if let Some(v6_only) = force_v6_only {
|
||||||
|
socket.set_only_v6(v6_only).map_err(|e| {
|
||||||
|
error!(
|
||||||
|
"[{}] failed to configure IPV6_V6ONLY={} for {}: {}",
|
||||||
|
service_name, v6_only, addr_str, e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
socket.set_nonblocking(true)?;
|
socket.set_nonblocking(true)?;
|
||||||
|
|
||||||
// Convert std::net::SocketAddr to socket2::SockAddr
|
// Convert std::net::SocketAddr to socket2::SockAddr
|
||||||
@@ -234,6 +265,44 @@ pub async fn bind_listener(
|
|||||||
Ok(listener)
|
Ok(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{normalize_ipv4_mapped_addr, parse_tcp_bind_target};
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_tcp_bind_target_rules() {
|
||||||
|
let (a, v6_only) = parse_tcp_bind_target("0.0.0.0:80").unwrap();
|
||||||
|
assert_eq!(a, "0.0.0.0:80".parse::<SocketAddr>().unwrap());
|
||||||
|
assert_eq!(v6_only, None);
|
||||||
|
|
||||||
|
let (a, v6_only) = parse_tcp_bind_target(":::80").unwrap();
|
||||||
|
assert_eq!(a, "[::]:80".parse::<SocketAddr>().unwrap());
|
||||||
|
assert_eq!(v6_only, Some(true));
|
||||||
|
|
||||||
|
let (a, v6_only) = parse_tcp_bind_target(":80").unwrap();
|
||||||
|
assert_eq!(a, "[::]:80".parse::<SocketAddr>().unwrap());
|
||||||
|
assert_eq!(v6_only, Some(false));
|
||||||
|
|
||||||
|
let (a, v6_only) = parse_tcp_bind_target("*:80").unwrap();
|
||||||
|
assert_eq!(a, "[::]:80".parse::<SocketAddr>().unwrap());
|
||||||
|
assert_eq!(v6_only, Some(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normalize_ipv4_mapped_addr() {
|
||||||
|
let mapped = SocketAddr::new(
|
||||||
|
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xFFFF, 0xC000, 0x0280)),
|
||||||
|
8080,
|
||||||
|
);
|
||||||
|
let normalized = normalize_ipv4_mapped_addr(mapped);
|
||||||
|
assert_eq!(normalized, SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 0, 2, 128)), 8080));
|
||||||
|
|
||||||
|
let normal_v6 = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080);
|
||||||
|
assert_eq!(normalize_ipv4_mapped_addr(normal_v6), normal_v6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn serve_listener_loop<F, Fut>(
|
pub async fn serve_listener_loop<F, Fut>(
|
||||||
listener: UnifiedListener,
|
listener: UnifiedListener,
|
||||||
service: ServiceConfig,
|
service: ServiceConfig,
|
||||||
|
|||||||
Reference in New Issue
Block a user