Skip to content

Commit 3973bc1

Browse files
authored
fix(tun): check tun exist before setting IP to preserve pre-configured routes (#940)
1 parent aa01eb6 commit 3973bc1

File tree

7 files changed

+267
-286
lines changed

7 files changed

+267
-286
lines changed

clash-bin/tests/data/config/tuic.json

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
server = "0.0.0.0:10002"
2+
3+
data_dir = ""
4+
5+
zero_rtt_handshake = false
6+
dual_stack = false
7+
8+
acl = '''
9+
direct localhost
10+
'''
11+
12+
[users]
13+
00000000-0000-0000-0000-000000000001 = "passwd"
14+
15+
[tls]
16+
certificate = "/opt/tuic/fullchain.pem"
17+
private_key = "/opt/tuic/privkey.pem"
18+
alpn = ["h3"]
19+
20+
[outbound.default]
21+
type = "direct"
22+
ip_mode = "auto"

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ mod tests {
382382
use super::*;
383383
async fn get_tuic_runner() -> anyhow::Result<DockerTestRunner> {
384384
let test_config_dir = test_config_base_dir();
385-
let conf = test_config_dir.join("tuic.json");
385+
let conf = test_config_dir.join("tuic.toml");
386386
let cert = test_config_dir.join("example.org.pem");
387387
let key = test_config_dir.join("example.org-key.pem");
388388

@@ -393,6 +393,7 @@ mod tests {
393393
(cert.to_str().unwrap(), "/opt/tuic/fullchain.pem"),
394394
(key.to_str().unwrap(), "/opt/tuic/privkey.pem"),
395395
])
396+
.env(&["TUIC_FORCE_TOML=1"])
396397
.build()
397398
.await
398399
}

clash-lib/src/proxy/tun/datagram.rs

Lines changed: 181 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,186 @@
1-
use std::task::Poll;
2-
1+
use crate::{
2+
app::{
3+
dispatcher::Dispatcher,
4+
dns::{ThreadSafeDNSResolver, exchange_with_resolver},
5+
net::DEFAULT_OUTBOUND_INTERFACE,
6+
},
7+
common::errors::new_io_error,
8+
proxy::datagram::UdpPacket,
9+
session::{Network, Session, Type},
10+
};
311
use futures::{Sink, Stream, ready};
12+
use std::{sync::Arc, task::Poll};
13+
use tracing::{debug, trace, warn};
14+
15+
pub(crate) async fn handle_inbound_datagram(
16+
socket: watfaq_netstack::UdpSocket,
17+
dispatcher: Arc<Dispatcher>,
18+
resolver: ThreadSafeDNSResolver,
19+
so_mark: u32,
20+
dns_hijack: bool,
21+
) {
22+
// tun i/o
23+
// lr: app packets went into tun will be accessed from lr
24+
// ls: packet written into ls will go back to app from tun
25+
let (mut lr, mut ls) = socket.split();
26+
let mut ls_dns = ls.clone(); // for dns hijack
27+
let resolver_dns = resolver.clone(); // for dns hijack
28+
29+
// dispatcher <-> tun communications
30+
// l_tx: dispatcher write packet responded from remote proxy
31+
// l_rx: in fut1 items are forwarded to ls
32+
let (l_tx, mut l_rx) = tokio::sync::mpsc::channel::<UdpPacket>(32);
33+
34+
// forward packets from tun to dispatcher
35+
let (d_tx, d_rx) = tokio::sync::mpsc::channel::<UdpPacket>(32);
36+
37+
// for dispatcher - the dispatcher would receive packets from this channel,
38+
// which is from the stack and send back packets to this channel, which
39+
// is to the tun
40+
let udp_stream = TunDatagram::new(l_tx, d_rx);
41+
42+
let default_outbound = DEFAULT_OUTBOUND_INTERFACE.read().await;
43+
let sess = Session {
44+
network: Network::Udp,
45+
typ: Type::Tun,
46+
iface: default_outbound.clone().inspect(|x| {
47+
debug!("selecting outbound interface: {:?} for tun UDP traffic", x);
48+
}),
49+
so_mark: Some(so_mark),
50+
..Default::default()
51+
};
52+
53+
let closer = dispatcher
54+
.dispatch_datagram(sess, Box::new(udp_stream))
55+
.await;
56+
57+
// dispatcher -> tun
58+
let fut1 = tokio::spawn(async move {
59+
while let Some(pkt) = l_rx.recv().await {
60+
trace!("tun <- dispatcher: {:?}", pkt);
61+
if let Err(e) = ls
62+
.send(
63+
(
64+
pkt.data,
65+
pkt.src_addr.must_into_socket_addr(),
66+
pkt.dst_addr.must_into_socket_addr(),
67+
)
68+
.into(),
69+
)
70+
.await
71+
{
72+
warn!("failed to send udp packet to netstack: {}", e);
73+
}
74+
}
75+
});
76+
77+
// tun -> dispatcher
78+
let fut2 = tokio::spawn(async move {
79+
'read_packet: while let Some(watfaq_netstack::UdpPacket {
80+
data,
81+
local_addr,
82+
remote_addr,
83+
}) = lr.recv().await
84+
{
85+
if remote_addr.ip().is_multicast() {
86+
continue;
87+
}
88+
let pkt = UdpPacket {
89+
data: data.data().into(),
90+
src_addr: local_addr.into(),
91+
dst_addr: remote_addr.into(),
92+
};
93+
94+
trace!("tun -> dispatcher: {:?}", pkt);
95+
96+
if dns_hijack && pkt.dst_addr.port() == 53 {
97+
trace!("got dns packet: {:?}, returning from Clash DNS server", pkt);
498

5-
use crate::{common::errors::new_io_error, proxy::datagram::UdpPacket};
99+
match hickory_proto::op::Message::from_vec(&pkt.data) {
100+
Ok(msg) => {
101+
let mut send_response =
102+
async |msg: hickory_proto::op::Message,
103+
pkt: &UdpPacket| {
104+
match msg.to_vec() {
105+
Ok(data) => {
106+
if let Err(e) = ls_dns
107+
.send(
108+
(
109+
data,
110+
pkt.dst_addr
111+
.clone()
112+
.must_into_socket_addr(),
113+
pkt.src_addr
114+
.clone()
115+
.must_into_socket_addr(),
116+
)
117+
.into(),
118+
)
119+
.await
120+
{
121+
warn!(
122+
"failed to send udp packet to \
123+
netstack: {}",
124+
e
125+
);
126+
}
127+
}
128+
Err(e) => {
129+
warn!(
130+
"failed to serialize dns response: {}",
131+
e
132+
);
133+
}
134+
}
135+
};
136+
137+
trace!("hijack dns request: {:?}", msg);
138+
139+
let mut resp =
140+
match exchange_with_resolver(&resolver_dns, &msg, true)
141+
.await
142+
{
143+
Ok(resp) => resp,
144+
Err(e) => {
145+
warn!("failed to exchange dns message: {}", e);
146+
continue 'read_packet;
147+
}
148+
};
149+
150+
// TODO: figure out where the message id got lost
151+
resp.set_id(msg.id());
152+
trace!("hijack dns response: {:?}", resp);
153+
154+
send_response(resp, &pkt).await;
155+
}
156+
Err(e) => {
157+
warn!(
158+
"failed to parse dns packet: {}, putting it back to \
159+
stack",
160+
e
161+
);
162+
}
163+
};
164+
165+
// don't forward dns packet to dispatcher
166+
continue 'read_packet;
167+
}
168+
169+
match d_tx.send(pkt).await {
170+
Ok(_) => {}
171+
Err(e) => {
172+
warn!("failed to send udp packet to proxy: {}", e);
173+
}
174+
}
175+
}
176+
177+
closer.send(0).ok();
178+
});
179+
180+
debug!("tun UDP ready");
181+
182+
let _ = futures::future::join(fut1, fut2).await;
183+
}
6184

7185
#[derive(Debug)]
8186
pub struct TunDatagram {

0 commit comments

Comments
 (0)