Windows support (#72)

This commit is contained in:
ssrlive 2023-10-23 09:44:27 +08:00 committed by GitHub
parent a9a562029f
commit 989c42ee61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 721 additions and 49 deletions

View file

@ -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

View file

@ -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"

View file

@ -98,7 +98,7 @@ Options:
-d, --dns <strategy> DNS handling strategy [default: virtual] [possible values: virtual, over-tcp, direct]
--dns-addr <IP> DNS resolver address [default: 8.8.8.8]
-6, --ipv6-enabled IPv6 enabled
-s, --setup <method> Routing and system setup [possible values: auto]
-s, --setup <method> Routing and system setup [default: none] [possible values: none, auto]
-b, --bypass <IP> Public proxy IP used in routing setup which should bypassing the tunnel
-v, --verbosity <level> 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> | Name of the tun interface [default: tun0] |
| PROXY | None | -p, --proxy <URL> | Proxy URL in the form proto://[username[:password]@]host:port |
| DNS | virtual | -d, --dns <strategy> | DNS handling strategy [default: virtual] [possible values: virtual, over-tcp, direct] |
| MODE | auto | -s, --setup <method> | Routing and system setup [possible values: auto] |
| MODE | auto | -s, --setup <method> | Routing and system setup [default: none] [possible values: none, auto] |
| BYPASS_IP | None | -b, --bypass <IP> | Public proxy IP used in routing setup which should bypassing the tunnel |
| VERBOSITY | info | -v, --verbosity <level> | Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace] |
| | | | |

84
build.rs Normal file
View file

@ -0,0 +1,84 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
#[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<std::path::PathBuf, Box<dyn std::error::Error>> {
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<std::path::PathBuf, Box<dyn std::error::Error>> {
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<std::path::PathBuf, Box<dyn std::error::Error>> {
let output = std::process::Command::new("cargo")
.arg("metadata")
.arg("--format-version=1")
.output()?;
let metadata = serde_json::from_slice::<serde_json::Value>(&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")?)
}

View file

@ -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<std::net::IpAddr>,
ipv6_enabled: bool,
bypass: Option<std::net::IpAddr>,
pub setup: bool,
}
impl Options {

View file

@ -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<ArgSetup>,
/// 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)?;

View file

@ -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<ConnectionInfo, ConnectionState>,
@ -231,6 +232,10 @@ pub struct TunToProxy<'a> {
exit_receiver: mio::unix::pipe::Receiver,
#[cfg(target_family = "unix")]
exit_trigger: Option<mio::unix::pipe::Sender>,
#[cfg(target_os = "windows")]
exit_receiver: mio::windows::NamedPipe,
#[cfg(target_os = "windows")]
exit_trigger: Option<mio::windows::NamedPipe>,
}
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<dyn ProxyHandler>,
udp_associate: bool,
) -> Result<ConnectionState> {
#[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<bool> {
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<bool> {
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")?;

551
src/wintuninterface.rs Normal file
View file

@ -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<NamedPipe> {
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<wintun::Session>,
mtu: usize,
medium: Medium,
pipe_server: Rc<RefCell<NamedPipe>>,
pipe_server_cache: Rc<RefCell<Vec<u8>>>,
pipe_client: Arc<Mutex<NamedPipe>>,
pipe_client_cache: Arc<Mutex<Vec<u8>>>,
wintun_reader_thread: Option<JoinHandle<()>>,
old_gateway: Option<IpAddr>,
}
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<WinTunInterface> {
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<dyn std::error::Error>> {
loop {
// Take the old data from pipe_client_cache and append the new data
let cached_data = pipe_client_cache_clone.lock()?.drain(..).collect::<Vec<u8>>();
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::<Vec<u8>>()
};
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<Mutex<NamedPipe>> {
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<dyn std::error::Error + '_>> {
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<dyn std::error::Error + '_>> {
let cache = self.pipe_client_cache.lock()?.drain(..).collect::<Vec<u8>>();
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<IpAddr>, dns_addr: Option<IpAddr>) -> Result<(), io::Error> {
let adapter = self.wintun_session.get_adapter();
// Setup the adapter's address/mask/gateway
let address = "10.1.0.33".parse::<IpAddr>().unwrap();
let mask = "255.255.255.0".parse::<IpAddr>().unwrap();
let gateway = "10.1.0.1".parse::<IpAddr>().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::<IpAddr>().unwrap());
let dns2 = "8.8.4.4".parse::<IpAddr>().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<Self::TxToken<'_>> {
Some(TxToken {
pipe_server: self.pipe_server.clone(),
pipe_server_cache: self.pipe_server_cache.clone(),
})
}
}
#[doc(hidden)]
pub struct RxToken {
buffer: Vec<u8>,
}
impl phy::RxToken for RxToken {
fn consume<R, F>(mut self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
f(&mut self.buffer[..])
}
}
#[doc(hidden)]
pub struct TxToken {
pipe_server: Rc<RefCell<NamedPipe>>,
pipe_server_cache: Rc<RefCell<Vec<u8>>>,
}
impl phy::TxToken for TxToken {
fn consume<R, F>(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::<Vec<_>>();
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<Mutex<NamedPipe>>);
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::<Vec<_>>().join(",");
let dns = dns.encode_utf16().chain(std::iter::once(0)).collect::<Vec<_>>();
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<Vec<SocketAddr>> {
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<F>(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<u8> = 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<SocketAddr> {
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)
}