Skip to content

Commit 1d2d932

Browse files
authored
feat: adding dual stack (#863)
* feat(tproxy): adding dual stack * refactor: remove set_ip_transparent_v6 * fix: add tproxy udp recv orig dst * fix(tproxy): bump unix-udp-socks to fix udp * fix(tproxy): udp packet with wrong src_addr #865 * fix(socks5): udp always associated ipv4 addr * fix(tproxy): ipv6 udp * feat(tproxy): add ip6table script * fix: avoid tproxy lan in iptables script * feat(ipv6): socks5 ipv6 listening * feat(http): dualstack listening * feat(mixed): dualstack listening * fix: fmt and clippy * feat(shadowsocks): dualstack listener * fix: commenting allow-lan for tproxy * fix: not set reuse address causing addr in use * fix: ss udp listener not dualstack * fix: address in use of tproxy udp
1 parent 4db1bc0 commit 1d2d932

File tree

12 files changed

+369
-92
lines changed

12 files changed

+369
-92
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clash-lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ httpmock = "0.7.0"
197197
prost-build = "0.14"
198198

199199
[target.'cfg(target_os="linux")'.dependencies]
200-
unix-udp-sock = { git = "https://github.com/Watfaq/unix-udp-sock.git", rev = "cd3e4eca43e6f3be82a2703c3d711b7e18fbfd18" }
200+
unix-udp-sock = { git = "https://github.com/Watfaq/unix-udp-sock.git", rev = "847c80b519f0fd8cff5c887ae708429897d08671" }
201201

202202
[target.'cfg(macos)'.dependencies]
203203
security-framework = "3.2.0"

clash-lib/src/config/internal/config.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::{HashMap, HashSet};
22

33
use std::{
4-
net::{IpAddr, Ipv4Addr},
4+
net::{IpAddr, Ipv4Addr, Ipv6Addr},
55
str::FromStr,
66
};
77

@@ -106,6 +106,10 @@ impl BindAddress {
106106
Self(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
107107
}
108108

109+
pub fn dual_stack() -> Self {
110+
Self(IpAddr::V6(Ipv6Addr::UNSPECIFIED))
111+
}
112+
109113
pub fn local() -> Self {
110114
Self(IpAddr::V4(Ipv4Addr::LOCALHOST))
111115
}
@@ -132,6 +136,7 @@ impl<'de> Deserialize<'de> for BindAddress {
132136
match str.as_str() {
133137
"*" => Ok(Self(IpAddr::V4(Ipv4Addr::UNSPECIFIED))),
134138
"localhost" => Ok(Self(IpAddr::from([127, 0, 0, 1]))),
139+
"[::]" | "::" => Ok(Self(IpAddr::V6(Ipv6Addr::UNSPECIFIED))),
135140
_ => {
136141
if let Ok(ip) = str.parse::<IpAddr>() {
137142
Ok(Self(ip))
@@ -152,6 +157,7 @@ impl FromStr for BindAddress {
152157
match str {
153158
"*" => Ok(Self(IpAddr::V4(Ipv4Addr::UNSPECIFIED))),
154159
"localhost" => Ok(Self(IpAddr::from([127, 0, 0, 1]))),
160+
"[::]" | "::" => Ok(Self(IpAddr::V6(Ipv6Addr::UNSPECIFIED))),
155161
_ => {
156162
if let Ok(ip) = str.parse::<IpAddr>() {
157163
Ok(Self(ip))

clash-lib/src/proxy/http/inbound/mod.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ mod proxy;
55
use crate::{
66
Dispatcher,
77
common::auth::ThreadSafeAuthenticator,
8-
proxy::{inbound::InboundHandlerTrait, utils::apply_tcp_options},
8+
proxy::{
9+
inbound::InboundHandlerTrait,
10+
utils::{ToCanonical, apply_tcp_options, try_create_dualstack_tcplistener},
11+
},
912
};
1013

1114
pub use proxy::handle as handle_http;
1215

1316
use crate::common::errors::new_io_error;
1417
use async_trait::async_trait;
1518
use std::{net::SocketAddr, sync::Arc};
16-
use tokio::net::TcpListener;
1719
use tracing::warn;
1820

1921
#[derive(Clone)]
@@ -60,13 +62,15 @@ impl InboundHandlerTrait for HttpInbound {
6062
}
6163

6264
async fn listen_tcp(&self) -> std::io::Result<()> {
63-
let listener = TcpListener::bind(self.addr).await?;
65+
let listener = try_create_dualstack_tcplistener(self.addr)?;
6466

6567
loop {
6668
let (socket, _) = listener.accept().await?;
67-
let src_addr = socket.peer_addr()?;
69+
let src_addr = socket.peer_addr()?.to_canonical();
6870

69-
if !self.allow_lan && src_addr.ip() != socket.local_addr()?.ip() {
71+
if !self.allow_lan
72+
&& src_addr.ip() != socket.local_addr()?.ip().to_canonical()
73+
{
7074
warn!("Connection from {} is not allowed", src_addr);
7175
continue;
7276
}

clash-lib/src/proxy/mixed/mod.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use crate::{
22
Dispatcher,
33
common::auth::ThreadSafeAuthenticator,
4+
proxy::utils::{ToCanonical, try_create_dualstack_tcplistener},
45
session::{Network, Session},
56
};
67

78
use super::{http, inbound::InboundHandlerTrait, socks, utils::apply_tcp_options};
89
use crate::common::errors::new_io_error;
910
use async_trait::async_trait;
1011
use std::{net::SocketAddr, sync::Arc};
11-
use tokio::net::TcpListener;
1212
use tracing::warn;
1313

1414
pub struct MixedInbound {
@@ -54,7 +54,7 @@ impl InboundHandlerTrait for MixedInbound {
5454
}
5555

5656
async fn listen_tcp(&self) -> std::io::Result<()> {
57-
let listener = TcpListener::bind(self.addr).await?;
57+
let listener = try_create_dualstack_tcplistener(self.addr)?;
5858

5959
loop {
6060
let (socket, _) = match listener.accept().await {
@@ -65,13 +65,15 @@ impl InboundHandlerTrait for MixedInbound {
6565
}
6666
};
6767
let src_addr = match socket.peer_addr() {
68-
Ok(a) => a,
68+
Ok(a) => a.to_canonical(),
6969
Err(e) => {
7070
warn!("failed to get peer address: {:?}", e);
7171
continue;
7272
}
7373
};
74-
if !self.allow_lan && src_addr.ip() != socket.local_addr()?.ip() {
74+
if !self.allow_lan
75+
&& src_addr.ip() != socket.local_addr()?.ip().to_canonical()
76+
{
7577
warn!("Connection from {} is not allowed", src_addr);
7678
continue;
7779
}
@@ -101,7 +103,7 @@ impl InboundHandlerTrait for MixedInbound {
101103
socks::SOCKS5_VERSION => {
102104
let mut sess = Session {
103105
network: Network::Tcp,
104-
source: socket.peer_addr()?,
106+
source: socket.peer_addr()?.to_canonical(),
105107
so_mark: fw_mark,
106108
..Default::default()
107109
};
@@ -118,7 +120,7 @@ impl InboundHandlerTrait for MixedInbound {
118120
}
119121

120122
_ => {
121-
let src = socket.peer_addr()?;
123+
let src = socket.peer_addr()?.to_canonical();
122124
let dispatcher = dispatcher.clone();
123125
let authenticator = authenticator.clone();
124126
tokio::spawn(async move {

clash-lib/src/proxy/shadowsocks/inbound/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ use crate::{
66
proxy::{
77
inbound::InboundHandlerTrait,
88
shadowsocks::{inbound::datagram::InboundShadowsocksDatagram, map_cipher},
9-
utils::{apply_tcp_options, new_udp_socket},
9+
utils::{
10+
ToCanonical, apply_tcp_options, new_udp_socket,
11+
try_create_dualstack_tcplistener,
12+
},
1013
},
1114
session::{Network, Session, SocksAddr, Type},
1215
};
1316

1417
use async_trait::async_trait;
1518
use shadowsocks::{ProxySocket, context::Context, net::AcceptOpts, relay::Address};
1619
use std::{net::SocketAddr, sync::Arc};
17-
use tokio::net::TcpListener;
1820
use tracing::{debug, warn};
1921

2022
#[derive(Clone)]
@@ -97,7 +99,7 @@ impl InboundHandlerTrait for ShadowsocksInbound {
9799
//
98100
// config.set_user_manager(user_manager);
99101

100-
let listener = TcpListener::bind(self.addr).await?;
102+
let listener = try_create_dualstack_tcplistener(self.addr)?;
101103

102104
let ss_listener = shadowsocks::relay::tcprelay::ProxyListener::from_listener(
103105
context,
@@ -130,7 +132,7 @@ impl InboundHandlerTrait for ShadowsocksInbound {
130132
if !self.allow_lan
131133
&& src_addr.ip() != socket.get_ref().local_addr()?.ip()
132134
{
133-
warn!("Connection from {} is not allowed", src_addr);
135+
warn!("Connection from {} is not allowed", src_addr.to_canonical());
134136
continue;
135137
}
136138

@@ -149,7 +151,7 @@ impl InboundHandlerTrait for ShadowsocksInbound {
149151
let sess = Session {
150152
network: Network::Tcp,
151153
typ: Type::Shadowsocks,
152-
source: src_addr,
154+
source: src_addr.to_canonical(),
153155
so_mark: self.fw_mark,
154156
destination: match target {
155157
Address::SocketAddress(addr) => SocksAddr::Ip(addr),

clash-lib/src/proxy/socks/inbound/mod.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ mod stream;
44
use crate::{
55
Dispatcher,
66
common::auth::ThreadSafeAuthenticator,
7-
proxy::{inbound::InboundHandlerTrait, utils::apply_tcp_options},
7+
proxy::{
8+
inbound::InboundHandlerTrait,
9+
utils::{ToCanonical, apply_tcp_options, try_create_dualstack_tcplistener},
10+
},
811
session::{Network, Session, Type},
912
};
1013

1114
use async_trait::async_trait;
1215
use std::{net::SocketAddr, sync::Arc};
1316
pub use stream::handle_tcp;
14-
use tokio::net::TcpListener;
1517
use tracing::warn;
1618

1719
use crate::common::errors::new_io_error;
@@ -60,12 +62,14 @@ impl InboundHandlerTrait for SocksInbound {
6062
}
6163

6264
async fn listen_tcp(&self) -> std::io::Result<()> {
63-
let listener = TcpListener::bind(self.addr).await?;
65+
let listener = try_create_dualstack_tcplistener(self.addr)?;
6466

6567
loop {
6668
let (socket, _) = listener.accept().await?;
67-
let src_addr = socket.peer_addr()?;
68-
if !self.allow_lan && src_addr.ip() != socket.local_addr()?.ip() {
69+
let src_addr = socket.peer_addr()?.to_canonical();
70+
if !self.allow_lan
71+
&& src_addr.ip() != socket.local_addr()?.ip().to_canonical()
72+
{
6973
warn!("Connection from {} is not allowed", src_addr);
7074
continue;
7175
}
@@ -74,7 +78,7 @@ impl InboundHandlerTrait for SocksInbound {
7478
let mut sess = Session {
7579
network: Network::Tcp,
7680
typ: Type::Socks5,
77-
source: socket.peer_addr()?,
81+
source: socket.peer_addr()?.to_canonical(),
7882
so_mark: self.fw_mark,
7983

8084
..Default::default()

clash-lib/src/proxy/socks/socks5.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,11 @@ pub(crate) async fn client_handshake(
9999
buf.put_u8(command);
100100
buf.put_u8(0x00);
101101
if command == socks_command::UDP_ASSOCIATE {
102-
let addr = SocksAddr::any_ipv4();
102+
let addr = if addr.clone().must_into_socket_addr().is_ipv4() {
103+
SocksAddr::any_ipv4()
104+
} else {
105+
SocksAddr::any_ipv6()
106+
};
103107
addr.write_buf(&mut buf);
104108
} else {
105109
addr.write_buf(&mut buf);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
### IPv6 RULES
2+
3+
4+
TPROXY_IP=::
5+
TPROXY_PORT=8900
6+
7+
readonly IPV6_RESERVED_IPADDRS="\
8+
::/128 \
9+
::1/128 \
10+
::ffff:0:0/96 \
11+
::ffff:0:0:0/96 \
12+
64:ff9b::/96 \
13+
100::/64 \
14+
2001::/32 \
15+
2001:20::/28 \
16+
2001:db8::/32 \
17+
2002::/16 \
18+
fc00::/7 \
19+
fe80::/10 \
20+
ff00::/8 \
21+
"
22+
23+
## TCP+UDP
24+
# Strategy Route
25+
ip -6 rule del fwmark 0x1 table 803
26+
ip -6 rule add fwmark 0x1 table 803
27+
ip -6 route del local ::/0 dev lo table 803
28+
ip -6 route add local ::/0 dev lo table 803
29+
30+
# TPROXY for LAN
31+
ip6tables -t mangle -N CLASH-TRPOXY
32+
# Skip LoopBack, Reserved
33+
for addr in ${IPV6_RESERVED_IPADDRS}; do
34+
ip6tables -t mangle -A CLASH-TRPOXY -d "${addr}" -j RETURN
35+
done
36+
37+
# Bypass LAN data
38+
ip6tables -t mangle -A CLASH-TRPOXY -m addrtype --dst-type LOCAL -j RETURN
39+
# Bypass sslocal's outbound data
40+
ip6tables -t mangle -A CLASH-TRPOXY -m mark --mark 0xff/0xff -j RETURN
41+
# UDP: TPROXY UDP
42+
ip6tables -t mangle -A CLASH-TRPOXY -p udp -j TPROXY --on-ip ${TPROXY_IP} --on-port ${TPROXY_PORT} --tproxy-mark 0x01/0x01
43+
44+
# TCP: TPROXY UDP
45+
ip6tables -t mangle -A CLASH-TRPOXY -p tcp -j TPROXY --on-ip ${TPROXY_IP} --on-port ${TPROXY_PORT} --tproxy-mark 0x01/0x01
46+
47+
# TPROXY for Local
48+
ip6tables -t mangle -N CLASH-TRPOXY-mark
49+
# Skip LoopBack, Reserved
50+
for addr in ${IPV6_RESERVED_IPADDRS}; do
51+
ip6tables -t mangle -A CLASH-TRPOXY-mark -d "${addr}" -j RETURN
52+
done
53+
54+
# TCP: conntrack
55+
ip6tables -t mangle -A CLASH-TRPOXY-mark -p tcp -m conntrack --ctdir REPLY -j RETURN
56+
# Bypass sslocal's outbound data
57+
ip6tables -t mangle -A CLASH-TRPOXY-mark -m mark --mark 0xff/0xff -j RETURN
58+
ip6tables -t mangle -A CLASH-TRPOXY-mark -m owner --uid-owner root -j RETURN
59+
# Set MARK and reroute
60+
ip6tables -t mangle -A CLASH-TRPOXY-mark -p udp -j MARK --set-xmark 0x01/0xffffffff
61+
ip6tables -t mangle -A CLASH-TRPOXY-mark -p tcp -j MARK --set-xmark 0x01/0xffffffff
62+
63+
# Apply TPROXY to LAN
64+
ip6tables -t mangle -A PREROUTING -p udp -j CLASH-TRPOXY
65+
ip6tables -t mangle -A PREROUTING -p tcp -j CLASH-TRPOXY
66+
#ip6tables -t mangle -A PREROUTING -p udp -m addrtype ! --src-type LOCAL ! --dst-type LOCAL -j CLASH-TRPOXY
67+
# Apply TPROXY for Local
68+
ip6tables -t mangle -A OUTPUT -p udp -j CLASH-TRPOXY-mark
69+
ip6tables -t mangle -A OUTPUT -p tcp -j CLASH-TRPOXY-mark
70+
#ip6tables -t mangle -A OUTPUT -p udp -m addrtype --src-type LOCAL ! --dst-type LOCAL -j CLASH-TRPOXY-mark

clash-lib/src/proxy/tproxy/iptables.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
readonly LOCAL_BY_PASS="\
55
127/8 \
66
10/8 \
7+
192/8
78
"
89

910
# declare ip as local for tproxy

0 commit comments

Comments
 (0)