diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index c376706..7bad5a7 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -63,11 +63,11 @@ jobs: cargo build --all-features --release --target ${{ matrix.target }} fi if [[ "${{ matrix.host_os }}" == "windows-latest" ]]; then - powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy.exe -DestinationPath publishdir/tun2proxy-${{ matrix.target }}.zip + powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy.exe, README.md, target/${{ matrix.target }}/release/wintun.dll -DestinationPath publishdir/tun2proxy-${{ matrix.target }}.zip elif [[ "${{ matrix.host_os }}" == "macos-latest" ]]; then - zip -j publishdir/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy + zip -j publishdir/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then - zip -j publishdir/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy + zip -j publishdir/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md fi - name: Publish diff --git a/Cargo.toml b/Cargo.toml index 193efad..416eb6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,3 +50,17 @@ reqwest = { version = "0.11", default-features = false, features = [ ] } serial_test = "2.0" test-log = "0.2" + +[target.'cfg(target_os="windows")'.dependencies] +rand = "0.8" +windows = { version = "0.51", features = [ + "Win32_Storage_FileSystem", + "Win32_NetworkManagement_IpHelper", + "Win32_NetworkManagement_Ndis", + "Win32_Networking_WinSock", + "Win32_Foundation", +] } +wintun = { git = "https://github.com/ssrlive/wintun.git", branch = "main" } + +[build-dependencies] +serde_json = "1.0" diff --git a/README.md b/README.md index b83837a..cfa1450 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ Options: -d, --dns DNS handling strategy [default: virtual] [possible values: virtual, over-tcp, direct] --dns-addr DNS resolver address [default: 8.8.8.8] -6, --ipv6-enabled IPv6 enabled - -s, --setup Routing and system setup [possible values: auto] + -s, --setup Routing and system setup [default: none] [possible values: none, auto] -b, --bypass Public proxy IP used in routing setup which should bypassing the tunnel -v, --verbosity Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace] -h, --help Print help @@ -134,7 +134,7 @@ container env list | TUN | tun0 | -t, --tun | Name of the tun interface [default: tun0] | | PROXY | None | -p, --proxy | Proxy URL in the form proto://[username[:password]@]host:port | | DNS | virtual | -d, --dns | DNS handling strategy [default: virtual] [possible values: virtual, over-tcp, direct] | -| MODE | auto | -s, --setup | Routing and system setup [possible values: auto] | +| MODE | auto | -s, --setup | Routing and system setup [default: none] [possible values: none, auto] | | BYPASS_IP | None | -b, --bypass | Public proxy IP used in routing setup which should bypassing the tunnel | | VERBOSITY | info | -v, --verbosity | Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace] | | | | | | diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..0c9159c --- /dev/null +++ b/build.rs @@ -0,0 +1,84 @@ +fn main() -> Result<(), Box> { + #[cfg(target_os = "windows")] + if let Ok(cargo_target_dir) = get_cargo_target_dir() { + let mut f = std::fs::File::create(cargo_target_dir.join("build.log"))?; + use std::io::Write; + f.write_all(format!("CARGO_TARGET_DIR: '{}'\r\n", cargo_target_dir.display()).as_bytes())?; + + // The wintun crate's root directory + let crate_dir = get_crate_dir("wintun")?; + + // The path to the DLL file, relative to the crate root, depending on the target architecture + let dll_path = get_wintun_bin_relative_path()?; + let src_path = crate_dir.join(dll_path); + + let dst_path = cargo_target_dir.join("wintun.dll"); + + f.write_all(format!("Source path: '{}'\r\n", src_path.display()).as_bytes())?; + f.write_all(format!("Target path: '{}'\r\n", dst_path.display()).as_bytes())?; + + // Copy to the target directory + if let Err(e) = std::fs::copy(src_path, &dst_path) { + f.write_all(format!("Failed to copy 'wintun.dll': {}\r\n", e).as_bytes())?; + } else { + f.write_all(format!("Copied 'wintun.dll' to '{}'\r\n", dst_path.display()).as_bytes())?; + } + } + Ok(()) +} + +#[allow(dead_code)] +fn get_cargo_target_dir() -> Result> { + let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?); + let profile = std::env::var("PROFILE")?; + let mut target_dir = None; + let mut sub_path = out_dir.as_path(); + while let Some(parent) = sub_path.parent() { + if parent.ends_with(&profile) { + target_dir = Some(parent); + break; + } + sub_path = parent; + } + Ok(target_dir.ok_or("not found")?.to_path_buf()) +} + +#[cfg(target_os = "windows")] +fn get_wintun_bin_relative_path() -> Result> { + let dll_path = if cfg!(target_arch = "x86") { + "wintun/bin/x86/wintun.dll" + } else if cfg!(target_arch = "x86_64") { + "wintun/bin/amd64/wintun.dll" + } else if cfg!(target_arch = "arm") { + "wintun/bin/arm/wintun.dll" + } else if cfg!(target_arch = "aarch64") { + "wintun/bin/arm64/wintun.dll" + } else { + return Err("Unsupported architecture".into()); + }; + Ok(dll_path.into()) +} + +#[allow(dead_code)] +fn get_crate_dir(crate_name: &str) -> Result> { + let output = std::process::Command::new("cargo") + .arg("metadata") + .arg("--format-version=1") + .output()?; + + let metadata = serde_json::from_slice::(&output.stdout)?; + let packages = metadata["packages"].as_array().ok_or("packages")?; + + let mut crate_dir = None; + + for package in packages { + let name = package["name"].as_str().ok_or("name")?; + if name == crate_name { + let path = package["manifest_path"].as_str().ok_or("manifest_path")?; + let path = std::path::PathBuf::from(path); + crate_dir = Some(path.parent().ok_or("parent")?.to_path_buf()); + break; + } + } + Ok(crate_dir.ok_or("crate_dir")?) +} diff --git a/src/lib.rs b/src/lib.rs index 537f0c2..00977dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,8 @@ mod socks; mod tun2proxy; mod virtdevice; mod virtdns; +#[cfg(target_os = "windows")] +mod wintuninterface; #[derive(Clone, Debug)] pub struct Proxy { @@ -103,6 +105,7 @@ pub struct Options { dns_addr: Option, ipv6_enabled: bool, bypass: Option, + pub setup: bool, } impl Options { diff --git a/src/main.rs b/src/main.rs index 404c341..8f18e57 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,7 +38,7 @@ struct Args { ipv6_enabled: bool, /// Routing and system setup - #[arg(short, long, value_name = "method", value_enum)] + #[arg(short, long, value_name = "method", value_enum, default_value = if cfg!(target_os = "linux") { "none" } else { "auto" })] setup: Option, /// Public proxy IP used in routing setup which should bypassing the tunnel @@ -63,6 +63,7 @@ enum ArgDns { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] enum ArgSetup { + None, Auto, } @@ -122,21 +123,20 @@ fn main() -> ExitCode { }; options = options.with_bypass(Some(bypass_tun_ip)); + options.setup = args.setup.map(|s| s == ArgSetup::Auto).unwrap_or(false); + let block = || -> Result<(), Error> { #[cfg(target_os = "linux")] - { - let mut setup: Setup; - if args.setup == Some(ArgSetup::Auto) { - let bypass_tun_ip = match args.bypass { - Some(addr) => addr, - None => args.proxy.addr.ip(), - }; - setup = Setup::new(&args.tun, &bypass_tun_ip, get_default_cidrs(), args.bypass.is_some()); + if options.setup { + let bypass_tun_ip = match args.bypass { + Some(addr) => addr, + None => args.proxy.addr.ip(), + }; + let mut setup = Setup::new(&args.tun, &bypass_tun_ip, get_default_cidrs(), args.bypass.is_some()); - setup.configure()?; + setup.configure()?; - setup.drop_privileges()?; - } + setup.drop_privileges()?; } main_entry(&interface, &args.proxy, options)?; diff --git a/src/tun2proxy.rs b/src/tun2proxy.rs index 652631b..09bb7cb 100644 --- a/src/tun2proxy.rs +++ b/src/tun2proxy.rs @@ -1,19 +1,18 @@ #![allow(dead_code)] +#[cfg(target_os = "windows")] +use crate::wintuninterface::{self, NamedPipeSource, WinTunInterface}; use crate::{dns, error::Error, error::Result, virtdevice::VirtualTunDevice, NetworkInterface, Options}; #[cfg(target_family = "unix")] use mio::unix::SourceFd; use mio::{event::Event, net::TcpStream, net::UdpSocket, Events, Interest, Poll, Token}; -#[cfg(not(target_family = "unix"))] -use smoltcp::phy::DeviceCapabilities; #[cfg(any(target_os = "macos", target_os = "ios"))] use smoltcp::phy::RawSocket; #[cfg(any(target_os = "linux", target_os = "android"))] use smoltcp::phy::TunTapInterface; -#[cfg(target_family = "unix")] -use smoltcp::phy::{Device, Medium, RxToken, TxToken}; use smoltcp::{ iface::{Config, Interface, SocketHandle, SocketSet}, + phy::{Device, Medium, RxToken, TxToken}, socket::{tcp, tcp::State, udp, udp::UdpMetadata}, time::Instant, wire::{IpCidr, IpProtocol, Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket, UDP_HEADER_LEN}, @@ -218,6 +217,8 @@ pub struct TunToProxy<'a> { tun: TunTapInterface, #[cfg(any(target_os = "macos", target_os = "ios"))] tun: RawSocket, + #[cfg(target_os = "windows")] + tun: WinTunInterface, poll: Poll, iface: Interface, connection_map: HashMap, @@ -231,6 +232,10 @@ pub struct TunToProxy<'a> { exit_receiver: mio::unix::pipe::Receiver, #[cfg(target_family = "unix")] exit_trigger: Option, + #[cfg(target_os = "windows")] + exit_receiver: mio::windows::NamedPipe, + #[cfg(target_os = "windows")] + exit_trigger: Option, } impl<'a> TunToProxy<'a> { @@ -247,35 +252,47 @@ impl<'a> TunToProxy<'a> { NetworkInterface::Fd(_fd) => panic!("Not supported"), }; + #[cfg(target_os = "windows")] + let mut tun = match _interface { + NetworkInterface::Named(name) => WinTunInterface::new(name.as_str(), Medium::Ip)?, + }; + + #[cfg(target_os = "windows")] + if options.setup { + tun.setup_config(options.bypass, options.dns_addr)?; + } + let poll = Poll::new()?; #[cfg(target_family = "unix")] poll.registry() .register(&mut SourceFd(&tun.as_raw_fd()), TUN_TOKEN, Interest::READABLE)?; - #[cfg(target_family = "unix")] - let (mut exit_trigger, mut exit_receiver) = mio::unix::pipe::new()?; + #[cfg(target_os = "windows")] + { + let interest = Interest::READABLE | Interest::WRITABLE; + poll.registry().register(&mut tun, TUN_TOKEN, interest)?; + let mut pipe = NamedPipeSource(tun.pipe_client()); + poll.registry().register(&mut pipe, PIPE_TOKEN, interest)?; + } #[cfg(target_family = "unix")] + let (mut exit_trigger, mut exit_receiver) = mio::unix::pipe::new()?; + #[cfg(target_family = "windows")] + let (mut exit_trigger, mut exit_receiver) = wintuninterface::pipe()?; + poll.registry() .register(&mut exit_trigger, EXIT_TRIGGER_TOKEN, Interest::WRITABLE)?; - #[cfg(target_family = "unix")] poll.registry() .register(&mut exit_receiver, EXIT_TOKEN, Interest::READABLE)?; - #[cfg(target_family = "unix")] let config = match tun.capabilities().medium { Medium::Ethernet => Config::new(smoltcp::wire::EthernetAddress([0x02, 0, 0, 0, 0, 0x01]).into()), Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), Medium::Ieee802154 => todo!(), }; - #[cfg(not(target_family = "unix"))] - let config = Config::new(smoltcp::wire::HardwareAddress::Ip); - #[cfg(target_family = "unix")] let mut device = VirtualTunDevice::new(tun.capabilities()); - #[cfg(not(target_family = "unix"))] - let mut device = VirtualTunDevice::new(DeviceCapabilities::default()); let gateway4: Ipv4Addr = Ipv4Addr::from_str("0.0.0.1")?; let gateway6: Ipv6Addr = Ipv6Addr::from_str("::1")?; @@ -289,7 +306,6 @@ impl<'a> TunToProxy<'a> { iface.set_any_ip(true); let tun = Self { - #[cfg(target_family = "unix")] tun, poll, iface, @@ -300,9 +316,7 @@ impl<'a> TunToProxy<'a> { device, options, write_sockets: HashSet::default(), - #[cfg(target_family = "unix")] exit_receiver, - #[cfg(target_family = "unix")] exit_trigger: Some(exit_trigger), }; Ok(tun) @@ -325,7 +339,6 @@ impl<'a> TunToProxy<'a> { let _slice = vec.as_slice(); // TODO: Actual write. Replace. - #[cfg(target_family = "unix")] self.tun .transmit(Instant::now()) .ok_or("tx token not available")? @@ -773,17 +786,24 @@ impl<'a> TunToProxy<'a> { proxy_handler: Box, udp_associate: bool, ) -> Result { + #[cfg(any(target_os = "linux", target_os = "android"))] let mut socket = tcp::Socket::new( tcp::SocketBuffer::new(vec![0; 1024 * 128]), tcp::SocketBuffer::new(vec![0; 1024 * 128]), ); + #[cfg(any(target_os = "ios", target_os = "macos", target_os = "windows"))] + let mut socket = tcp::Socket::new( + // TODO: Look into how the buffer size affects IP header length and fragmentation + tcp::SocketBuffer::new(vec![0; 1024 * 2]), + tcp::SocketBuffer::new(vec![0; 1024 * 2]), + ); socket.set_ack_delay(None); socket.listen(dst)?; let handle = self.sockets.add(socket); let mut client = TcpStream::connect(server_addr)?; let token = self.new_token(); - let i = Interest::READABLE; + let i = Interest::READABLE | Interest::WRITABLE; self.poll.registry().register(&mut client, token, i)?; let expiry = if udp_associate { @@ -808,7 +828,7 @@ impl<'a> TunToProxy<'a> { proxy_handler, close_state: 0, wait_read: true, - wait_write: false, + wait_write: true, udp_acco_expiry: expiry, udp_socket, udp_token, @@ -876,8 +896,8 @@ impl<'a> TunToProxy<'a> { state.wait_write = true; Self::update_mio_socket_interest(&mut self.poll, state)?; } - Err(error) => { - return Err(error.into()); + Err(_) => { + return Ok(()); } } } @@ -921,15 +941,23 @@ impl<'a> TunToProxy<'a> { fn tun_event(&mut self, event: &Event) -> Result<(), Error> { if event.is_readable() { - #[cfg(target_family = "unix")] while let Some((rx_token, _)) = self.tun.receive(Instant::now()) { rx_token.consume(|frame| self.receive_tun(frame))?; } } + #[cfg(target_os = "windows")] + if event.is_writable() { + // log::trace!("Tun writable"); + let tx_token = self.tun.transmit(Instant::now()).ok_or("tx token not available")?; + // Just consume the cached packets, do nothing else. + tx_token.consume(0, |_buf| {}); + } Ok(()) } fn pipe_event(&mut self, _event: &Event) -> Result<(), Error> { + #[cfg(target_os = "windows")] + self.tun.pipe_client_event(_event)?; Ok(()) } @@ -1132,7 +1160,7 @@ impl<'a> TunToProxy<'a> { Ok(()) } - #[cfg(any(target_os = "linux", target_os = "macos"))] + #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] fn prepare_exiting_signal_trigger(&mut self) -> Result<()> { let mut exit_trigger = self.exit_trigger.take().ok_or("Already running")?; ctrlc::set_handler(move || { @@ -1163,7 +1191,7 @@ impl<'a> TunToProxy<'a> { } pub fn run(&mut self) -> Result<(), Error> { - #[cfg(any(target_os = "linux", target_os = "macos"))] + #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] self.prepare_exiting_signal_trigger()?; let mut events = Events::with_capacity(1024); @@ -1183,7 +1211,6 @@ impl<'a> TunToProxy<'a> { } } EXIT_TRIGGER_TOKEN => { - #[cfg(target_family = "unix")] log::trace!("Exiting trigger is ready, {:?}", self.exit_trigger); } TUN_TOKEN => self.tun_event(event)?, @@ -1197,7 +1224,6 @@ impl<'a> TunToProxy<'a> { } } - #[cfg(target_family = "unix")] fn exiting_event_handler(&mut self) -> Result { let mut buffer = vec![0; 100]; match self.exit_receiver.read(&mut buffer) { @@ -1214,12 +1240,6 @@ impl<'a> TunToProxy<'a> { } } - #[cfg(target_os = "windows")] - fn exiting_event_handler(&mut self) -> Result { - Ok(true) - } - - #[cfg(target_family = "unix")] pub fn shutdown(&mut self) -> Result<(), Error> { log::debug!("Shutdown tun2proxy..."); _ = self.exit_trigger.as_mut().ok_or("Already triggered")?.write(b"EXIT")?; diff --git a/src/wintuninterface.rs b/src/wintuninterface.rs new file mode 100644 index 0000000..ab4e4eb --- /dev/null +++ b/src/wintuninterface.rs @@ -0,0 +1,551 @@ +use mio::{event, windows::NamedPipe, Interest, Registry, Token}; +use smoltcp::{ + phy::{self, Device, DeviceCapabilities, Medium}, + time::Instant, +}; +use std::{ + cell::RefCell, + fs::OpenOptions, + io::{self, Read, Write}, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + os::windows::prelude::{FromRawHandle, IntoRawHandle, OpenOptionsExt}, + rc::Rc, + sync::{Arc, Mutex}, + thread::JoinHandle, + vec::Vec, +}; +use windows::{ + core::{GUID, PWSTR}, + Win32::{ + Foundation::{ERROR_BUFFER_OVERFLOW, WIN32_ERROR}, + NetworkManagement::{ + IpHelper::{ + GetAdaptersAddresses, SetInterfaceDnsSettings, DNS_INTERFACE_SETTINGS, DNS_INTERFACE_SETTINGS_VERSION1, + DNS_SETTING_NAMESERVER, GAA_FLAG_INCLUDE_GATEWAYS, GAA_FLAG_INCLUDE_PREFIX, IF_TYPE_ETHERNET_CSMACD, + IF_TYPE_IEEE80211, IP_ADAPTER_ADDRESSES_LH, + }, + Ndis::IfOperStatusUp, + }, + Networking::WinSock::{AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6}, + Storage::FileSystem::FILE_FLAG_OVERLAPPED, + }, +}; + +fn server() -> io::Result<(NamedPipe, String)> { + use rand::Rng; + let num: u64 = rand::thread_rng().gen(); + let name = format!(r"\\.\pipe\my-pipe-{}", num); + let pipe = NamedPipe::new(&name)?; + Ok((pipe, name)) +} + +fn client(name: &str) -> io::Result { + let mut opts = OpenOptions::new(); + opts.read(true).write(true).custom_flags(FILE_FLAG_OVERLAPPED.0); + let file = opts.open(name)?; + unsafe { Ok(NamedPipe::from_raw_handle(file.into_raw_handle())) } +} + +pub(crate) fn pipe() -> io::Result<(NamedPipe, NamedPipe)> { + let (pipe, name) = server()?; + Ok((pipe, client(&name)?)) +} + +/// A virtual TUN (IP) interface. +pub struct WinTunInterface { + wintun_session: Arc, + mtu: usize, + medium: Medium, + pipe_server: Rc>, + pipe_server_cache: Rc>>, + pipe_client: Arc>, + pipe_client_cache: Arc>>, + wintun_reader_thread: Option>, + old_gateway: Option, +} + +impl event::Source for WinTunInterface { + fn register(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { + self.pipe_server.borrow_mut().register(registry, token, interests)?; + Ok(()) + } + + fn reregister(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { + self.pipe_server.borrow_mut().reregister(registry, token, interests)?; + Ok(()) + } + + fn deregister(&mut self, registry: &Registry) -> io::Result<()> { + self.pipe_server.borrow_mut().deregister(registry)?; + Ok(()) + } +} + +impl WinTunInterface { + pub fn new(tun_name: &str, medium: Medium) -> io::Result { + let wintun = unsafe { wintun::load() }.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let guid = 324435345345345345_u128; + let adapter = match wintun::Adapter::open(&wintun, tun_name) { + Ok(a) => a, + Err(_) => wintun::Adapter::create(&wintun, tun_name, tun_name, Some(guid)) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?, + }; + + let session = adapter + .start_session(wintun::MAX_RING_CAPACITY) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let wintun_session = Arc::new(session); + + let (pipe_server, pipe_client) = pipe()?; + + let pipe_client = Arc::new(Mutex::new(pipe_client)); + let pipe_client_cache = Arc::new(Mutex::new(Vec::new())); + + let mtu = adapter.get_mtu().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + let reader_session = wintun_session.clone(); + let pipe_client_clone = pipe_client.clone(); + let pipe_client_cache_clone = pipe_client_cache.clone(); + let reader_thread = std::thread::spawn(move || { + let block = || -> Result<(), Box> { + loop { + // Take the old data from pipe_client_cache and append the new data + let cached_data = pipe_client_cache_clone.lock()?.drain(..).collect::>(); + let bytes = if cached_data.len() >= mtu { + // if the cached data is greater than mtu, then sleep 1ms and return the data + std::thread::sleep(std::time::Duration::from_millis(1)); + cached_data + } else { + // read data from tunnel interface + let packet = reader_session.receive_blocking()?; + let bytes = packet.bytes().to_vec(); + // and append to the end of cached data + cached_data.into_iter().chain(bytes).collect::>() + }; + + if bytes.is_empty() { + continue; + } + let len = bytes.len(); + + // write data to named pipe_server + let result = { pipe_client_clone.lock()?.write(&bytes) }; + match result { + Ok(n) => { + if n < len { + log::trace!("Wintun pipe_client write data {} less than buffer {}", n, len); + pipe_client_cache_clone.lock()?.extend_from_slice(&bytes[n..]); + } + } + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + log::trace!("Wintun pipe_client write WouldBlock (1) len {}", len); + pipe_client_cache_clone.lock()?.extend_from_slice(&bytes); + } + Err(err) => log::error!("Wintun pipe_client write data len {} error \"{}\"", len, err), + } + } + }; + if let Err(err) = block() { + log::trace!("Reader {}", err); + } + }); + + Ok(WinTunInterface { + wintun_session, + mtu, + medium, + pipe_server: Rc::new(RefCell::new(pipe_server)), + pipe_server_cache: Rc::new(RefCell::new(Vec::new())), + pipe_client, + pipe_client_cache, + wintun_reader_thread: Some(reader_thread), + old_gateway: None, + }) + } + + pub fn pipe_client(&self) -> Arc> { + self.pipe_client.clone() + } + + pub fn pipe_client_event(&self, event: &event::Event) -> Result<(), io::Error> { + if event.is_readable() { + self.pipe_client_event_readable() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + } else if event.is_writable() { + self.pipe_client_event_writable() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + } + Ok(()) + } + + fn pipe_client_event_readable(&self) -> Result<(), Box> { + let mut reader = self.pipe_client.lock()?; + let mut buffer = vec![0; self.mtu]; + loop { + // some data arieved to pipe_client from pipe_server + match reader.read(&mut buffer[..]) { + Ok(len) => match self.wintun_session.allocate_send_packet(len as u16) { + Ok(mut write_pack) => { + write_pack.bytes_mut().copy_from_slice(&buffer[..len]); + // write data to tunnel interface + self.wintun_session.send_packet(write_pack); + } + Err(err) => { + log::error!("Wintun: failed to allocate send packet: {}", err); + } + }, + Err(err) if err.kind() == io::ErrorKind::WouldBlock => break, + Err(err) if err.kind() == io::ErrorKind::Interrupted => continue, + Err(err) => return Err(err.into()), + } + } + Ok(()) + } + + fn pipe_client_event_writable(&self) -> Result<(), Box> { + let cache = self.pipe_client_cache.lock()?.drain(..).collect::>(); + if cache.is_empty() { + return Ok(()); + } + let len = cache.len(); + let result = self.pipe_client.lock()?.write(&cache[..]); + match result { + Ok(n) => { + if n < len { + log::trace!("Wintun pipe_client write data {} less than buffer {}", n, len); + self.pipe_client_cache.lock()?.extend_from_slice(&cache[n..]); + } + } + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + log::trace!("Wintun pipe_client write WouldBlock (2) len {}", len); + self.pipe_client_cache.lock()?.extend_from_slice(&cache); + } + Err(err) => log::error!("Wintun pipe_client write data len {} error \"{}\"", len, err), + } + Ok(()) + } + + pub fn setup_config(&mut self, bypass_ip: Option, dns_addr: Option) -> Result<(), io::Error> { + let adapter = self.wintun_session.get_adapter(); + + // Setup the adapter's address/mask/gateway + let address = "10.1.0.33".parse::().unwrap(); + let mask = "255.255.255.0".parse::().unwrap(); + let gateway = "10.1.0.1".parse::().unwrap(); + adapter + .set_network_addresses_tuple(address, mask, Some(gateway)) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + // 1. Setup the adapter's DNS + let interface = GUID::from(adapter.get_guid()); + let dns = dns_addr.unwrap_or("8.8.8.8".parse::().unwrap()); + let dns2 = "8.8.4.4".parse::().unwrap(); + set_interface_dns_settings(interface, &[dns, dns2])?; + + // 2. Route all traffic to the adapter, here the destination is adapter's gateway + // command: `route add 0.0.0.0 mask 0.0.0.0 10.1.0.1 metric 6` + let unspecified = Ipv4Addr::UNSPECIFIED.to_string(); + let gateway = gateway.to_string(); + let args = &["add", &unspecified, "mask", &unspecified, &gateway, "metric", "6"]; + run_command("route", args)?; + log::info!("route {:?}", args); + + let old_gateways = get_active_network_interface_gateways()?; + // find ipv4 gateway address, or error return + let old_gateway = old_gateways + .iter() + .find(|addr| addr.is_ipv4()) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "No ipv4 gateway found"))?; + let old_gateway = old_gateway.ip(); + self.old_gateway = Some(old_gateway); + + // 3. route the bypass ip to the old gateway + // command: `route add bypass_ip old_gateway metric 1` + if let Some(bypass_ip) = bypass_ip { + let args = &["add", &bypass_ip.to_string(), &old_gateway.to_string(), "metric", "1"]; + run_command("route", args)?; + log::info!("route {:?}", args); + } + + Ok(()) + } + + pub fn restore_config(&mut self) -> Result<(), io::Error> { + if self.old_gateway.is_none() { + return Ok(()); + } + let unspecified = Ipv4Addr::UNSPECIFIED.to_string(); + + // 1. Remove current adapter's route + // command: `route delete 0.0.0.0 mask 0.0.0.0` + let args = &["delete", &unspecified, "mask", &unspecified]; + run_command("route", args)?; + + // 2. Add back the old gateway route + // command: `route add 0.0.0.0 mask 0.0.0.0 old_gateway metric 200` + let old_gateway = self.old_gateway.take().unwrap().to_string(); + let args = &["add", &unspecified, "mask", &unspecified, &old_gateway, "metric", "200"]; + run_command("route", args)?; + + Ok(()) + } +} + +impl Drop for WinTunInterface { + fn drop(&mut self) { + if let Err(e) = self.restore_config() { + log::error!("Faild to unsetup config: {}", e); + } + if let Err(e) = self.wintun_session.shutdown() { + log::error!("phy: failed to shutdown interface: {}", e); + } + if let Some(thread) = self.wintun_reader_thread.take() { + if let Err(e) = thread.join() { + log::error!("phy: failed to join reader thread: {:?}", e); + } + } + } +} + +impl Device for WinTunInterface { + type RxToken<'a> = RxToken; + type TxToken<'a> = TxToken; + + fn capabilities(&self) -> DeviceCapabilities { + let mut v = DeviceCapabilities::default(); + v.max_transmission_unit = self.mtu; + v.medium = self.medium; + v + } + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let mut buffer = vec![0; self.mtu]; + match self.pipe_server.borrow_mut().read(&mut buffer[..]) { + Ok(size) => { + buffer.resize(size, 0); + let rx = RxToken { buffer }; + let tx = TxToken { + pipe_server: self.pipe_server.clone(), + pipe_server_cache: self.pipe_server_cache.clone(), + }; + Some((rx, tx)) + } + Err(err) if err.kind() == io::ErrorKind::WouldBlock => None, + Err(err) => panic!("{}", err), + } + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + Some(TxToken { + pipe_server: self.pipe_server.clone(), + pipe_server_cache: self.pipe_server_cache.clone(), + }) + } +} + +#[doc(hidden)] +pub struct RxToken { + buffer: Vec, +} + +impl phy::RxToken for RxToken { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + f(&mut self.buffer[..]) + } +} + +#[doc(hidden)] +pub struct TxToken { + pipe_server: Rc>, + pipe_server_cache: Rc>>, +} + +impl phy::TxToken for TxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut buffer = vec![0; len]; + let result = f(&mut buffer); + + let buffer = self + .pipe_server_cache + .borrow_mut() + .drain(..) + .chain(buffer) + .collect::>(); + if buffer.is_empty() { + // log::trace!("Wintun TxToken (pipe_server) is empty"); + return result; + } + let len = buffer.len(); + + match self.pipe_server.borrow_mut().write(&buffer[..]) { + Ok(n) => { + if n < len { + log::trace!("Wintun TxToken (pipe_server) sent {} less than buffer len {}", n, len); + self.pipe_server_cache.borrow_mut().extend_from_slice(&buffer[n..]); + } + } + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + self.pipe_server_cache.borrow_mut().extend_from_slice(&buffer[..]); + log::trace!("Wintun TxToken (pipe_server) WouldBlock data len: {}", len) + } + Err(err) => log::error!("Wintun TxToken (pipe_server) len {} error \"{}\"", len, err), + } + result + } +} + +pub struct NamedPipeSource(pub Arc>); + +impl event::Source for NamedPipeSource { + fn register(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { + self.0 + .lock() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))? + .register(registry, token, interests) + } + + fn reregister(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { + self.0 + .lock() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))? + .reregister(registry, token, interests) + } + + fn deregister(&mut self, registry: &Registry) -> io::Result<()> { + self.0 + .lock() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))? + .deregister(registry) + } +} + +pub(crate) fn run_command(command: &str, args: &[&str]) -> io::Result<()> { + let out = std::process::Command::new(command).args(args).output()?; + if !out.status.success() { + let err = String::from_utf8_lossy(if out.stderr.is_empty() { + &out.stdout + } else { + &out.stderr + }); + let info = format!("{} failed with: \"{}\"", command, err); + return Err(std::io::Error::new(std::io::ErrorKind::Other, info)); + } + Ok(()) +} + +pub(crate) fn set_interface_dns_settings(interface: GUID, dns: &[IpAddr]) -> io::Result<()> { + // format L"1.1.1.1 8.8.8.8", or L"1.1.1.1,8.8.8.8". + let dns = dns.iter().map(|ip| ip.to_string()).collect::>().join(","); + let dns = dns.encode_utf16().chain(std::iter::once(0)).collect::>(); + + let settings = DNS_INTERFACE_SETTINGS { + Version: DNS_INTERFACE_SETTINGS_VERSION1, + Flags: DNS_SETTING_NAMESERVER as _, + NameServer: PWSTR(dns.as_ptr() as _), + ..DNS_INTERFACE_SETTINGS::default() + }; + + unsafe { SetInterfaceDnsSettings(interface, &settings as *const _)? }; + Ok(()) +} + +pub(crate) fn get_active_network_interface_gateways() -> io::Result> { + let mut addrs = vec![]; + get_adapters_addresses(|adapter| { + if adapter.OperStatus == IfOperStatusUp + && [IF_TYPE_ETHERNET_CSMACD, IF_TYPE_IEEE80211].contains(&adapter.IfType) + { + let mut current_gateway = adapter.FirstGatewayAddress; + while !current_gateway.is_null() { + let gateway = unsafe { &*current_gateway }; + { + let sockaddr_ptr = gateway.Address.lpSockaddr; + let sockaddr = unsafe { &*(sockaddr_ptr as *const SOCKADDR) }; + let a = unsafe { sockaddr_to_socket_addr(sockaddr) }?; + addrs.push(a); + } + current_gateway = gateway.Next; + } + } + Ok(()) + })?; + Ok(addrs) +} + +pub(crate) fn get_adapters_addresses(mut callback: F) -> io::Result<()> +where + F: FnMut(IP_ADAPTER_ADDRESSES_LH) -> io::Result<()>, +{ + let mut size = 0; + let flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_GATEWAYS; + let family = AF_UNSPEC.0 as u32; + + // Make an initial call to GetAdaptersAddresses to get the + // size needed into the size variable + let result = unsafe { GetAdaptersAddresses(family, flags, None, None, &mut size) }; + + if WIN32_ERROR(result) != ERROR_BUFFER_OVERFLOW { + WIN32_ERROR(result).ok()?; + } + // Allocate memory for the buffer + let mut addresses: Vec = vec![0; (size + 4) as usize]; + + // Make a second call to GetAdaptersAddresses to get the actual data we want + let result = unsafe { + let addr = Some(addresses.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH); + GetAdaptersAddresses(family, flags, None, addr, &mut size) + }; + + WIN32_ERROR(result).ok()?; + + // If successful, output some information from the data we received + let mut current_addresses = addresses.as_ptr() as *const IP_ADAPTER_ADDRESSES_LH; + while !current_addresses.is_null() { + unsafe { + callback(*current_addresses)?; + current_addresses = (*current_addresses).Next; + } + } + Ok(()) +} + +pub(crate) unsafe fn sockaddr_to_socket_addr(sock_addr: *const SOCKADDR) -> io::Result { + let address = match (*sock_addr).sa_family { + AF_INET => sockaddr_in_to_socket_addr(&*(sock_addr as *const SOCKADDR_IN)), + AF_INET6 => sockaddr_in6_to_socket_addr(&*(sock_addr as *const SOCKADDR_IN6)), + _ => return Err(io::Error::new(io::ErrorKind::Other, "Unsupported address type")), + }; + Ok(address) +} + +pub(crate) unsafe fn sockaddr_in_to_socket_addr(sockaddr_in: &SOCKADDR_IN) -> SocketAddr { + let ip = Ipv4Addr::new( + sockaddr_in.sin_addr.S_un.S_un_b.s_b1, + sockaddr_in.sin_addr.S_un.S_un_b.s_b2, + sockaddr_in.sin_addr.S_un.S_un_b.s_b3, + sockaddr_in.sin_addr.S_un.S_un_b.s_b4, + ); + let port = u16::from_be(sockaddr_in.sin_port); + SocketAddr::new(ip.into(), port) +} + +pub(crate) unsafe fn sockaddr_in6_to_socket_addr(sockaddr_in6: &SOCKADDR_IN6) -> SocketAddr { + let ip = IpAddr::V6(Ipv6Addr::new( + u16::from_be(sockaddr_in6.sin6_addr.u.Word[0]), + u16::from_be(sockaddr_in6.sin6_addr.u.Word[1]), + u16::from_be(sockaddr_in6.sin6_addr.u.Word[2]), + u16::from_be(sockaddr_in6.sin6_addr.u.Word[3]), + u16::from_be(sockaddr_in6.sin6_addr.u.Word[4]), + u16::from_be(sockaddr_in6.sin6_addr.u.Word[5]), + u16::from_be(sockaddr_in6.sin6_addr.u.Word[6]), + u16::from_be(sockaddr_in6.sin6_addr.u.Word[7]), + )); + let port = u16::from_be(sockaddr_in6.sin6_port); + SocketAddr::new(ip, port) +}