Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion clash-lib/src/config/internal/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::{HashMap, HashSet};

use std::{
net::{IpAddr, Ipv4Addr},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};

Expand Down Expand Up @@ -106,6 +106,10 @@ impl BindAddress {
Self(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
}

pub fn dual_stack() -> Self {
Self(IpAddr::V6(Ipv6Addr::UNSPECIFIED))
}

pub fn local() -> Self {
Self(IpAddr::V4(Ipv4Addr::LOCALHOST))
}
Expand All @@ -132,6 +136,7 @@ impl<'de> Deserialize<'de> for BindAddress {
match str.as_str() {
"*" => Ok(Self(IpAddr::V4(Ipv4Addr::UNSPECIFIED))),
"localhost" => Ok(Self(IpAddr::from([127, 0, 0, 1]))),
"[::]" | "::" => Ok(Self(IpAddr::V6(Ipv6Addr::UNSPECIFIED))),
_ => {
if let Ok(ip) = str.parse::<IpAddr>() {
Ok(Self(ip))
Expand All @@ -152,6 +157,7 @@ impl FromStr for BindAddress {
match str {
"*" => Ok(Self(IpAddr::V4(Ipv4Addr::UNSPECIFIED))),
"localhost" => Ok(Self(IpAddr::from([127, 0, 0, 1]))),
"[::]" | "::" => Ok(Self(IpAddr::V6(Ipv6Addr::UNSPECIFIED))),
_ => {
if let Ok(ip) = str.parse::<IpAddr>() {
Ok(Self(ip))
Expand Down
63 changes: 51 additions & 12 deletions clash-lib/src/proxy/tproxy/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use super::{inbound::InboundHandlerTrait, tun::TunDatagram};
use crate::{
app::dispatcher::Dispatcher,
proxy::{datagram::UdpPacket, utils::apply_tcp_options},
proxy::{
datagram::UdpPacket,
utils::{ToCanonical, apply_tcp_options, try_create_dualstack_socket},
},
session::{Network, Session, Type},
};

use async_trait::async_trait;
use std::{
io,
net::SocketAddr,
os::fd::{AsFd, AsRawFd},
sync::Arc,
Expand Down Expand Up @@ -54,12 +58,13 @@ impl InboundHandlerTrait for TproxyInbound {
}

async fn listen_tcp(&self) -> std::io::Result<()> {
let socket = socket2::Socket::new(
socket2::Domain::IPV4,
socket2::Type::STREAM,
None,
)?;
socket.set_ip_transparent_v4(true)?;
let (socket, dualstack) =
try_create_dualstack_socket(self.addr, socket2::Type::STREAM)?;
set_ip_transparent(&socket, self.addr.is_ipv6())?;
if dualstack {
// set ipv4 transparent
set_ip_transparent(&socket, false)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function parameter is confusing.. i think you mean enable for non ipv6 here but passing a false value sounds like disable it...

maybe rename the set_ip_transparent to set_ip_transparent_v6 and stay with using socket.set_ip_transparent_v4 here if that works?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

}
socket.set_nonblocking(true)?;
socket.bind(&self.addr.into())?;
socket.listen(1024)?;
Expand All @@ -68,16 +73,17 @@ impl InboundHandlerTrait for TproxyInbound {

loop {
let (socket, _) = listener.accept().await?;
let src_addr = socket.peer_addr()?;
if !self.allow_lan && src_addr.ip() != socket.local_addr()?.ip() {
let src_addr = socket.peer_addr()?.to_canonical();
// for dualstack socket src_addr may be ipv4 or ipv6
if !self.allow_lan && !src_addr.ip().is_loopback() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this will essentially only allow connection from 127.1

and connecting with 10.0.0.100 to a inbound on 0.0.0.0 from local machine will fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I tested this it barely works. Nearly every connection has a non-localhost address even it is from my local process. So this is a problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any ideas to implement this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the original implementation should still work for v6?

let listener = std::net::TcpListener::bind("[::]:12345").unwrap();
...

  println!(
                    "New connection from {} -> {}",
                    socket.peer_addr().unwrap(),
                    socket.local_addr().unwrap()
                );
Server listening on port 12345
New connection from [::ffff:10.0.15.177]:54306 -> [::ffff:10.0.15.177]:12345
Received 0 bytes: []
New connection from [::1]:54308 -> [::1]:12345
Received 0 bytes: []
New connection from [2401:redacted]:54327 -> [2401:redacted]:12345
Received 0 bytes: []

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but this is for normal socket, not very sure about tproxy case.

Nearly every connection has a non-localhost address

what these srcs look like?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is the address of my nic card like 192.168.1.1

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right then i think it should work same way by checking incoming.src == socket.local_addr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't work the socket.local_addr is "[::]:port"

warn!("Connection from {} is not allowed", src_addr);
continue;
}

apply_tcp_options(&socket)?;

// local_addr is getsockname
let orig_dst = socket.local_addr()?;
let orig_dst = socket.local_addr()?.to_canonical();

let sess = Session {
network: Network::Tcp,
Expand All @@ -98,9 +104,12 @@ impl InboundHandlerTrait for TproxyInbound {
}

async fn listen_udp(&self) -> std::io::Result<()> {
let socket =
socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::DGRAM, None)?;
let (socket, dual_stack) =
try_create_dualstack_socket(self.addr, socket2::Type::DGRAM)?;
socket.set_ip_transparent_v4(true)?;
if dual_stack {
set_ip_transparent(&socket, true)?;
}
socket.set_nonblocking(true)?;
socket.set_broadcast(true)?;

Expand Down Expand Up @@ -223,3 +232,33 @@ async fn handle_inbound_datagram(
let _ = futures::future::join(fut1, fut2).await;
Ok(())
}

// socket2 doesn't provide set_ip_transparent_v6
// So we must implement it ourselves
fn set_ip_transparent(socket: &socket2::Socket, ipv6: bool) -> io::Result<()> {
let fd = socket.as_raw_fd();

let (opt, level) = if ipv6 {
(libc::IPV6_TRANSPARENT, libc::IPPROTO_IPV6)
} else {
(libc::IP_TRANSPARENT, libc::IPPROTO_IP)
};

let enable: libc::c_int = 1;

unsafe {
let ret = libc::setsockopt(
fd,
level,
opt,
&enable as *const _ as *const _,
std::mem::size_of_val(&enable) as libc::socklen_t,
);

if ret != 0 {
return Err(io::Error::last_os_error());
}
}

Ok(())
}
39 changes: 39 additions & 0 deletions clash-lib/src/proxy/utils/socket_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,42 @@
.ok()?
}
}

/// Convert ipv6 mapped ipv4 address back to ipv4. Other address remain
/// unchanged. e.g. ::ffff:127.0.0.1 -> 127.0.0.1
pub trait ToCanonical {

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-unknown-freebsd

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / i686-unknown-freebsd

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-linux-android

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-pc-windows-msvc

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-pc-windows-msvc-static-crt

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-apple-darwin

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-apple-darwin

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-apple-darwin

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-apple-darwin

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-apple-darwin-static-crt

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-apple-darwin-static-crt

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-apple-darwin-static-crt

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-apple-darwin-static-crt

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / i686-pc-windows-msvc

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / i686-pc-windows-msvc

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-pc-windows-msvc

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-pc-windows-msvc

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-pc-windows-msvc-static-crt

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-pc-windows-msvc-static-crt

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / i686-pc-windows-msvc-static-crt

trait `ToCanonical` is never used

Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / i686-pc-windows-msvc-static-crt

trait `ToCanonical` is never used
fn to_canonical(self) -> SocketAddr;
}

impl ToCanonical for SocketAddr {
fn to_canonical(mut self) -> SocketAddr {
self.set_ip(self.ip().to_canonical());
self
}
}

/// Create dualstack socket if it can
/// If failed, fallback to single stack silently
pub fn try_create_dualstack_socket(

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-unknown-freebsd

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / i686-unknown-freebsd

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-linux-android

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-pc-windows-msvc

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-pc-windows-msvc-static-crt

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-apple-darwin

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-apple-darwin

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-apple-darwin

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-apple-darwin

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-apple-darwin-static-crt

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / aarch64-apple-darwin-static-crt

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-apple-darwin-static-crt

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-apple-darwin-static-crt

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / i686-pc-windows-msvc

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / i686-pc-windows-msvc

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-pc-windows-msvc

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-pc-windows-msvc

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-pc-windows-msvc-static-crt

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / x86_64-pc-windows-msvc-static-crt

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / i686-pc-windows-msvc-static-crt

function `try_create_dualstack_socket` is never used

Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs

View workflow job for this annotation

GitHub Actions / i686-pc-windows-msvc-static-crt

function `try_create_dualstack_socket` is never used
addr: SocketAddr,
tcp_or_udp: socket2::Type,
) -> std::io::Result<(socket2::Socket, bool)> {
let domain = if addr.is_ipv4() {
socket2::Domain::IPV4
} else {
socket2::Domain::IPV6
};
let mut dualstack = false;
let socket = socket2::Socket::new(domain, tcp_or_udp, None)?;
if addr.is_ipv6() && addr.ip().is_unspecified() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why checking addr.ip().is_unspecified() here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dualstack only works when binding to a ipv6 unspecified address

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah i see, thanks!

if let Err(e) = socket.set_only_v6(false) {
// If setting dualstack fails, fallback to single stack
tracing::warn!(
"dualstack not supported, falling back to ipv6 only: {e}"
);
} else {
dualstack = true;
}
};
Ok((socket, dualstack))
}
Loading