From bde2d590f95ce2b6ed755115d5535b0a1ad3eb9f Mon Sep 17 00:00:00 2001 From: Itsusinn Date: Wed, 12 Jun 2024 21:56:29 +0800 Subject: [PATCH] WIP --- Cargo.lock | 148 +++++++++-------- clash_lib/Cargo.toml | 6 +- clash_lib/src/proxy/tun/datagram.rs | 7 +- clash_lib/src/proxy/tun/inbound.rs | 242 ++++++++++------------------ clash_lib/src/proxy/tun/mod.rs | 1 - 5 files changed, 173 insertions(+), 231 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb5b1570..8d20f62f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -577,29 +577,6 @@ dependencies = [ "which", ] -[[package]] -name = "bindgen" -version = "0.69.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" -dependencies = [ - "bitflags 2.5.0", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.55", - "which", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -724,7 +701,7 @@ dependencies = [ "ip_network", "ip_network_table", "libc", - "nix", + "nix 0.25.1", "parking_lot", "rand_core", "ring 0.17.8", @@ -862,6 +839,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha20" version = "0.9.1" @@ -1050,12 +1033,12 @@ dependencies = [ "ip_network_table-deps-treebitmap", "ipnet", "libc", + "libtun", "lru_time_cache", "maxminddb", "md-5", "mockall", "murmur3", - "netstack-lwip", "network-interface", "once_cell", "opentelemetry", @@ -1100,7 +1083,6 @@ dependencies = [ "tracing-timing", "tuic", "tuic-quinn", - "tun", "url", "uuid", "webpki-roots", @@ -1869,6 +1851,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "etherparse" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "827292ea592108849932ad8e30218f8b1f21c0dfd0696698a18b5d0aed62d990" +dependencies = [ + "arrayvec", +] + [[package]] name = "event-listener" version = "5.2.0" @@ -1880,6 +1871,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -2741,6 +2742,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.9.3" @@ -2804,12 +2811,6 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" -[[package]] -name = "ioctl-sys" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd11f3a29434026f5ff98c730b668ba74b1033637b8817940b54d040696133c" - [[package]] name = "ip_network" version = "0.4.1" @@ -3000,6 +3001,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libtun" +version = "0.1.0" +source = "git+https://github.com/Itsusinn/libtun.git?rev=e8db488#e8db4880bd5cca35930602222e7cfa549533f1f1" +dependencies = [ + "eyre", + "futures-util", + "netstack-smoltcp", + "tokio", + "tun2", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -3222,18 +3235,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9252111cf132ba0929b6f8e030cac2a24b507f3a4d6db6fb2896f27b354c714b" [[package]] -name = "netstack-lwip" -version = "0.3.4" -source = "git+https://github.com/Watfaq/netstack-lwip.git?rev=2817bf82740e04bbee6b7bf1165f55657a6ed163#2817bf82740e04bbee6b7bf1165f55657a6ed163" +name = "netstack-smoltcp" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622853611e43f6e1ecfff61af1d47cc4a3797c153c940331985dc3fb1fe37072" dependencies = [ - "anyhow", - "bindgen 0.69.4", - "bytes", - "cc", + "etherparse", "futures", - "log", - "thiserror", + "rand", + "smoltcp", + "spin 0.9.8", "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -3269,6 +3283,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -3868,16 +3894,6 @@ dependencies = [ "termtree", ] -[[package]] -name = "prettyplease" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" -dependencies = [ - "proc-macro2", - "syn 2.0.55", -] - [[package]] name = "primeorder" version = "0.13.6" @@ -6392,7 +6408,7 @@ name = "tracing-oslog" version = "0.1.2" source = "git+https://github.com/Absolucy/tracing-oslog.git?branch=main#2207764b7edd3a5c059f4c3e8837fe72127067f9" dependencies = [ - "bindgen 0.64.0", + "bindgen", "cc", "cfg-if", "fnv", @@ -6517,16 +6533,18 @@ dependencies = [ ] [[package]] -name = "tun" -version = "0.6.1" -source = "git+https://github.com/Watfaq/rust-tun.git?rev=8f7568190f1200d3e272ca534baf8d1578147e18#8f7568190f1200d3e272ca534baf8d1578147e18" +name = "tun2" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5af6b725dd317dd689d1e37f559e70cfbe6e87effdaf5f62c80d919bfc9eda95" dependencies = [ - "byteorder", "bytes", + "cfg-if", "futures-core", - "ioctl-sys", + "ipnet", "libc", "log", + "nix 0.29.0", "thiserror", "tokio", "tokio-util", @@ -6914,21 +6932,21 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets 0.48.5", + "windows-targets 0.52.4", ] [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.4", ] [[package]] @@ -7093,9 +7111,9 @@ dependencies = [ [[package]] name = "wintun" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29b83b0eca06dd125dbcd48a45327c708a6da8aada3d95a3f06db0ce4b17e0d4" +checksum = "1b3c8c8876c686f8a2d6376999ac1c9a24c74d2968551c9394f7e89127783685" dependencies = [ "c2rust-bitfields", "libloading", diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index b79230fb..1544d936 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -65,9 +65,6 @@ axum = { version = "0.7", features = ["ws"] } tower-http = { version = "0.5.2", features = ["fs", "trace", "cors"] } chrono = { version = "0.4.38", features = ["serde"] } -tun = { git = "https://github.com/Watfaq/rust-tun.git", rev = "8f7568190f1200d3e272ca534baf8d1578147e18", features = ["async"] } -netstack-lwip = { git = "https://github.com/Watfaq/netstack-lwip.git", rev = "2817bf82740e04bbee6b7bf1165f55657a6ed163" } - boringtun = { version = "0.6.0", git = "https://github.com/cloudflare/boringtun.git", rev = "f672bb6c1e1e371240a8d151f15854687eb740bb" } smoltcp = { version = "0.11", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } @@ -121,6 +118,9 @@ console-subscriber = { version = "0.2.0" } tracing-timing = { version = "0.6.0" } criterion = { version = "0.5", features = ["html_reports", "async_tokio"], optional = true } +# libtun = { path = "/home/itsusinn/Workspace/Code/libtun" } +libtun = { rev = "e8db488", git = "https://github.com/Itsusinn/libtun.git" } + [dev-dependencies] tempfile = "3.10" ctor = "0.2" diff --git a/clash_lib/src/proxy/tun/datagram.rs b/clash_lib/src/proxy/tun/datagram.rs index d8f2ec96..2b872916 100644 --- a/clash_lib/src/proxy/tun/datagram.rs +++ b/clash_lib/src/proxy/tun/datagram.rs @@ -1,4 +1,4 @@ -use std::{net::SocketAddr, task::Poll}; +use std::task::Poll; use futures::{ready, Sink, Stream}; @@ -14,8 +14,6 @@ pub struct TunDatagram { pkt: Option, flushed: bool, - #[allow(unused)] - local_addr: SocketAddr, } impl TunDatagram { @@ -24,15 +22,12 @@ impl TunDatagram { tx: tokio::sync::mpsc::Sender, // receive from tun rx: tokio::sync::mpsc::Receiver, - // the address of the tun udp socket - local_addr: SocketAddr, ) -> Self { Self { rx, tx, pkt: None, flushed: true, - local_addr, } } } diff --git a/clash_lib/src/proxy/tun/inbound.rs b/clash_lib/src/proxy/tun/inbound.rs index 7e1a3339..9ca397f1 100644 --- a/clash_lib/src/proxy/tun/inbound.rs +++ b/clash_lib/src/proxy/tun/inbound.rs @@ -1,14 +1,12 @@ -use super::{datagram::TunDatagram, netstack}; +use super::datagram::TunDatagram; use std::{net::SocketAddr, sync::Arc}; use futures::{SinkExt, StreamExt}; -use tracing::{error, info, trace, warn}; -use tun::{Device, TunPacket}; +use tracing::{info, trace, warn}; use url::Url; use crate::{ app::{dispatcher::Dispatcher, dns::ThreadSafeDNSResolver}, - common::errors::map_io_error, config::internal::config::TunConfig, proxy::datagram::UdpPacket, session::{Network, Session, SocksAddr, Type}, @@ -16,7 +14,7 @@ use crate::{ }; async fn handle_inbound_stream( - stream: netstack::TcpStream, + stream: libtun::TcpStream, local_addr: SocketAddr, remote_addr: SocketAddr, dispatcher: Arc, @@ -33,88 +31,74 @@ async fn handle_inbound_stream( } async fn handle_inbound_datagram( - socket: Box, + socket: libtun::UdpSocket, dispatcher: Arc, resolver: ThreadSafeDNSResolver, ) { - let local_addr = socket.local_addr(); - // tun i/o + let (mut read_half, mut write_half) = socket.split(); - let (ls, mut lr) = socket.split(); - let ls = Arc::new(ls); - - // dispatcher <-> tun communications - let (l_tx, mut l_rx) = tokio::sync::mpsc::channel::(32); - - // forward packets from tun to dispatcher - let (d_tx, d_rx) = tokio::sync::mpsc::channel::(32); - - // for dispatcher - the dispatcher would receive packets from this channel, which is from the stack - // and send back packets to this channel, which is to the tun - let udp_stream = TunDatagram::new(l_tx, d_rx, local_addr); + let (d2tun_tx, mut d2tun_rx) = tokio::sync::mpsc::channel::(32); + let (tun2d_tx, tun2d_rx) = tokio::sync::mpsc::channel::(32); + + let udp_stream = TunDatagram::new(d2tun_tx, tun2d_rx); let sess = Session { network: Network::Udp, typ: Type::Tun, ..Default::default() }; - + // TODO refactor `closer` let closer = dispatcher.dispatch_datagram(sess, Box::new(udp_stream)); - // dispatcher -> tun - let fut1 = tokio::spawn(async move { - while let Some(pkt) = l_rx.recv().await { - trace!("tun <- dispatcher: {:?}", pkt); - // populate the correct src_addr, though is it necessary? - let src_addr = match pkt.src_addr { - SocksAddr::Ip(ip) => ip, - SocksAddr::Domain(host, port) => { - match resolver.resolve(&host, resolver.fake_ip_enabled()).await { - Ok(Some(ip)) => (ip, port).into(), - Ok(None) => { - warn!("failed to resolve domain: {}", host); - continue; - } - Err(e) => { - warn!("failed to resolve domain: {}", e); - continue; + loop { + // !!! Cancellation safety **must** be guaranteed. + tokio::select! { + // dispatcher -> tun + Some(pkt) = d2tun_rx.recv() => { + trace!("tun <- dispatcher: {:?}", pkt); + // populate the correct src_addr, though is it necessary? + let src_addr = match pkt.src_addr { + SocksAddr::Ip(ip) => ip, + SocksAddr::Domain(host, port) => { + match resolver.resolve(&host, resolver.fake_ip_enabled()).await { + Ok(Some(ip)) => (ip, port).into(), + Ok(None) => { + warn!("failed to resolve domain: {}", host); + continue; + } + Err(e) => { + warn!("failed to resolve domain: {}", e); + continue; + } } } + }; + if let Err(e) = write_half.send(( + pkt.data, + src_addr, + pkt.dst_addr.must_into_socket_addr(), + )).await { + warn!("failed to send udp packet to netstack: {}", e); } - }; - if let Err(e) = ls.send_to( - &pkt.data[..], - &src_addr, - &pkt.dst_addr.must_into_socket_addr(), - ) { - warn!("failed to send udp packet to netstack: {}", e); } - } - }); - - // tun -> dispatcher - let fut2 = tokio::spawn(async move { - while let Ok((data, src_addr, dst_addr)) = lr.recv_from().await { - let pkt = UdpPacket { - data, - src_addr: src_addr.into(), - dst_addr: dst_addr.into(), - }; - - trace!("tun -> dispatcher: {:?}", pkt); - - match d_tx.send(pkt).await { - Ok(_) => {} - Err(e) => { - warn!("failed to send udp packet to proxy: {}", e); + Some((data, src_addr, dst_addr)) = read_half.next() => { + let pkt = UdpPacket { + data, + src_addr: src_addr.into(), + dst_addr: dst_addr.into(), + }; + + trace!("tun -> dispatcher: {:?}", pkt); + + match tun2d_tx.send(pkt).await { + Ok(_) => {} + Err(e) => { + warn!("failed to send udp packet to proxy: {}", e); + } } } } - - closer.send(0).ok(); - }); - - let _ = futures::future::join(fut1, fut2).await; + } } pub fn get_runner( @@ -132,8 +116,7 @@ pub fn get_runner( let u = Url::parse(&device_id).map_err(|x| Error::InvalidConfig(format!("tun device {}", x)))?; - let mut tun_cfg = tun::Configuration::default(); - + let device_id: libtun::DeviceID; match u.scheme() { "fd" => { let fd = u @@ -142,100 +125,47 @@ pub fn get_runner( .to_string() .parse() .map_err(|x| Error::InvalidConfig(format!("tun fd {}", x)))?; - tun_cfg.raw_fd(fd); + device_id = libtun::DeviceID::Fd(fd); } "dev" => { let dev = u.host().expect("tun dev must be provided").to_string(); - tun_cfg.name(dev); + device_id = libtun::DeviceID::Dev(dev); } _ => { - return Err(Error::InvalidConfig(format!( - "invalid device id: {}", - device_id - ))); + // TODO Warning + device_id = libtun::DeviceID::default(); } } + let mut tun_system = libtun::TunSystem::new(device_id, true); + tun_system.create_device(); + + info!("tun started at {}", tun_system.device_name()); + let (mut tcp_listener, udp_socket) = tun_system.create_netstack(); + + let dispatcher_clone = dispatcher.clone(); + let tcp_task = tokio::spawn(async move{ + + while let Some(( + stream, + local_addr, + remote_addr + )) = tcp_listener.next().await { + tokio::spawn(handle_inbound_stream( + stream, + local_addr, + remote_addr, + dispatcher_clone.clone(), + )); + } + }); - tun_cfg.up(); - - let tun = tun::create_as_async(&tun_cfg).map_err(map_io_error)?; - - let tun_name = tun.get_ref().name().map_err(map_io_error)?; - info!("tun started at {}", tun_name); - - let (stack, mut tcp_listener, udp_socket) = - netstack::NetStack::with_buffer_size(512, 256).map_err(map_io_error)?; - - Ok(Some(Box::pin(async move { - let framed = tun.into_framed(); - - let (mut tun_sink, mut tun_stream) = framed.split(); - let (mut stack_sink, mut stack_stream) = stack.split(); - - let mut futs: Vec = vec![]; - - // dispatcher -> stack -> tun - futs.push(Box::pin(async move { - while let Some(pkt) = stack_stream.next().await { - match pkt { - Ok(pkt) => { - if let Err(e) = tun_sink.send(TunPacket::new(pkt)).await { - error!("failed to send pkt to tun: {}", e); - break; - } - } - Err(e) => { - error!("tun stack error: {}", e); - break; - } - } - } - - Err(Error::Operation("tun stopped unexpectedly 0".to_string())) - })); - - // tun -> stack -> dispatcher - futs.push(Box::pin(async move { - while let Some(pkt) = tun_stream.next().await { - match pkt { - Ok(pkt) => { - if let Err(e) = stack_sink.send(pkt.into_bytes().into()).await { - error!("failed to send pkt to stack: {}", e); - break; - } - } - Err(e) => { - error!("tun stream error: {}", e); - break; - } - } - } - - Err(Error::Operation("tun stopped unexpectedly 1".to_string())) - })); - - let dsp = dispatcher.clone(); - futs.push(Box::pin(async move { - while let Some((stream, local_addr, remote_addr)) = tcp_listener.next().await { - tokio::spawn(handle_inbound_stream( - stream, - local_addr, - remote_addr, - dsp.clone(), - )); - } - - Err(Error::Operation("tun stopped unexpectedly 2".to_string())) - })); - - futs.push(Box::pin(async move { - handle_inbound_datagram(udp_socket, dispatcher, resolver).await; - Err(Error::Operation("tun stopped unexpectedly 3".to_string())) - })); + let dispatcher_clone = dispatcher.clone(); + let udp_task = tokio::spawn(async move{ + handle_inbound_datagram(udp_socket, dispatcher_clone, resolver).await; + }); - futures::future::select_all(futs).await.0.map_err(|x| { - error!("tun error: {}. stopped", x); - x - }) + Ok(Some(Box::pin(async move { + _ = futures::future::join(tcp_task, udp_task).await; + Ok(()) }))) } diff --git a/clash_lib/src/proxy/tun/mod.rs b/clash_lib/src/proxy/tun/mod.rs index cde10543..e4e3c791 100644 --- a/clash_lib/src/proxy/tun/mod.rs +++ b/clash_lib/src/proxy/tun/mod.rs @@ -1,4 +1,3 @@ pub mod inbound; -pub use netstack_lwip as netstack; mod datagram; pub use inbound::get_runner as get_tun_runner;