mirror of
https://github.com/tun2proxy/tun2proxy.git
synced 2025-04-21 22:39:08 +00:00
merge upstream
This commit is contained in:
commit
b7ae2ec2ea
20 changed files with 995 additions and 208 deletions
6
.github/workflows/publish-exe.yml
vendored
6
.github/workflows/publish-exe.yml
vendored
|
@ -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
|
||||
|
|
20
Cargo.toml
20
Cargo.toml
|
@ -1,8 +1,8 @@
|
|||
[package]
|
||||
authors = ["B. Blechschmidt"]
|
||||
authors = ["B. Blechschmidt", "ssrlive"]
|
||||
edition = "2021"
|
||||
name = "tun2proxy"
|
||||
version = "0.1.8"
|
||||
version = "0.1.10"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
@ -10,7 +10,7 @@ crate-type = ["cdylib", "lib"]
|
|||
[dependencies]
|
||||
base64 = { version = "0.21" }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
ctrlc = { git = "https://github.com/ssrlive/rust-ctrlc.git", branch = "master" }
|
||||
ctrlc2 = { version = "3.5", features = ["termination"] }
|
||||
digest_auth = "0.3"
|
||||
dotenvy = "0.15"
|
||||
env_logger = "0.10"
|
||||
|
@ -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"
|
||||
|
|
12
Dockerfile
12
Dockerfile
|
@ -12,17 +12,9 @@ RUN cargo build --release --target x86_64-unknown-linux-gnu
|
|||
## Final image
|
||||
####################################################################################################
|
||||
FROM ubuntu:latest
|
||||
WORKDIR /app
|
||||
|
||||
ENV TUN=tun0
|
||||
ENV PROXY=
|
||||
ENV DNS=virtual
|
||||
ENV MODE=auto
|
||||
ENV BYPASS_IP=
|
||||
|
||||
RUN apt update && apt install -y iproute2 curl && apt clean all
|
||||
RUN apt update && apt install -y iproute2 && apt clean all
|
||||
|
||||
COPY --from=builder /worker/target/x86_64-unknown-linux-gnu/release/tun2proxy /usr/bin/tun2proxy
|
||||
COPY --from=builder /worker/docker/entrypoint.sh /app
|
||||
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
ENTRYPOINT ["/usr/bin/tun2proxy", "--setup", "auto"]
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) B. Blechschmidt and contributors
|
||||
Copyright (c) @ssrlive, B. Blechschmidt and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
11
README.md
11
README.md
|
@ -98,8 +98,8 @@ 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]
|
||||
-b, --bypass <IP> Public proxy IP used in routing setup which should bypassing the tunnel
|
||||
-s, --setup <method> Routing and system setup [default: none] [possible values: none, auto]
|
||||
-b, --bypass <IP|CIDR> IPs and CIDRs used in routing setup which should bypass the tunnel
|
||||
-v, --verbosity <level> Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace]
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
|
@ -119,20 +119,17 @@ Next, start a container from the tun2proxy image:
|
|||
|
||||
```bash
|
||||
docker run -d \
|
||||
-e PROXY=PROXY_TYPE://PROXY_IP:PROXY_PORT \
|
||||
-v /dev/net/tun:/dev/net/tun \
|
||||
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
|
||||
--sysctl net.ipv6.conf.default.disable_ipv6=0 \
|
||||
--cap-add NET_ADMIN \
|
||||
--name tun2proxy \
|
||||
tun2proxy
|
||||
tun2proxy --proxy proto://[username[:password]@]host:port
|
||||
```
|
||||
|
||||
You can then provide the running container's network to another worker container by sharing the network namespace:
|
||||
You can then provide the running container's network to another worker container by sharing the network namespace (like kubernetes sidecar):
|
||||
|
||||
```bash
|
||||
docker run -it \
|
||||
-d \
|
||||
--network "container:tun2proxy" \
|
||||
ubuntu:latest
|
||||
```
|
||||
|
|
84
build.rs
Normal file
84
build.rs
Normal 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")?)
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
|
||||
run() {
|
||||
if [ -n "$BYPASS_IP" ]; then
|
||||
BYPASS_IP="--bypass $BYPASS_IP"
|
||||
fi
|
||||
|
||||
if [ -n "$DNS" ]; then
|
||||
DNS="--dns $DNS"
|
||||
fi
|
||||
|
||||
if [ -n "$MODE" ]; then
|
||||
MODE="--setup $MODE"
|
||||
fi
|
||||
|
||||
if [ -n "$PROXY" ]; then
|
||||
PROXY="--proxy $PROXY"
|
||||
fi
|
||||
|
||||
if [ -n "$TUN" ]; then
|
||||
TUN="--tun $TUN"
|
||||
fi
|
||||
|
||||
exec tun2proxy $TUN $PROXY $DNS $MODE $BYPASS_IP
|
||||
}
|
||||
|
||||
|
||||
run || echo "Runing ERROR!!"
|
|
@ -4,6 +4,7 @@ use std::{
|
|||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
str::FromStr,
|
||||
};
|
||||
use trust_dns_proto::op::MessageType;
|
||||
use trust_dns_proto::{
|
||||
op::{Message, ResponseCode},
|
||||
rr::{record_type::RecordType, Name, RData, Record},
|
||||
|
@ -46,6 +47,11 @@ pub fn build_dns_response(mut request: Message, domain: &str, ip: IpAddr, ttl: u
|
|||
record
|
||||
}
|
||||
};
|
||||
|
||||
// We must indicate that this message is a response. Otherwise, implementations may not
|
||||
// recognize it.
|
||||
request.set_message_type(MessageType::Response);
|
||||
|
||||
request.add_answer(record);
|
||||
Ok(request)
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ pub enum Error {
|
|||
#[error("std::ffi::NulError {0:?}")]
|
||||
Nul(#[from] std::ffi::NulError),
|
||||
|
||||
#[error("ctrlc::Error {0:?}")]
|
||||
InterruptHandler(#[from] ctrlc::Error),
|
||||
#[error("ctrlc2::Error {0:?}")]
|
||||
InterruptHandler(#[from] ctrlc2::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
|
|
10
src/http.rs
10
src/http.rs
|
@ -366,15 +366,15 @@ impl ProxyHandler for HttpConnection {
|
|||
self.state == HttpState::Established
|
||||
}
|
||||
|
||||
fn have_data(&mut self, dir: Direction) -> bool {
|
||||
fn data_len(&self, dir: Direction) -> usize {
|
||||
match dir {
|
||||
Direction::Incoming(incoming) => match incoming {
|
||||
IncomingDirection::FromServer => !self.server_inbuf.is_empty(),
|
||||
IncomingDirection::FromClient => !self.client_inbuf.is_empty() || !self.data_buf.is_empty(),
|
||||
IncomingDirection::FromServer => self.server_inbuf.len(),
|
||||
IncomingDirection::FromClient => self.client_inbuf.len().max(self.data_buf.len()),
|
||||
},
|
||||
Direction::Outgoing(outgoing) => match outgoing {
|
||||
OutgoingDirection::ToServer => !self.server_outbuf.is_empty(),
|
||||
OutgoingDirection::ToClient => !self.client_outbuf.is_empty(),
|
||||
OutgoingDirection::ToServer => self.server_outbuf.len(),
|
||||
OutgoingDirection::ToClient => self.client_outbuf.len(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
13
src/lib.rs
13
src/lib.rs
|
@ -4,6 +4,7 @@ use crate::{
|
|||
socks::SocksProxyManager,
|
||||
tun2proxy::{ConnectionManager, TunToProxy},
|
||||
};
|
||||
use smoltcp::wire::IpCidr;
|
||||
use socks5_impl::protocol::UserKey;
|
||||
use std::{
|
||||
net::{SocketAddr, ToSocketAddrs},
|
||||
|
@ -17,8 +18,11 @@ mod http;
|
|||
pub mod setup;
|
||||
mod socks;
|
||||
mod tun2proxy;
|
||||
pub mod util;
|
||||
mod virtdevice;
|
||||
mod virtdns;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod wintuninterface;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Proxy {
|
||||
|
@ -102,7 +106,8 @@ pub struct Options {
|
|||
dns_over_tcp: bool,
|
||||
dns_addr: Option<std::net::IpAddr>,
|
||||
ipv6_enabled: bool,
|
||||
bypass: Option<std::net::IpAddr>,
|
||||
pub setup: bool,
|
||||
bypass: Vec<IpCidr>,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
|
@ -137,8 +142,10 @@ impl Options {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_bypass(mut self, ip: Option<std::net::IpAddr>) -> Self {
|
||||
self.bypass = ip;
|
||||
pub fn with_bypass_ips<'a>(mut self, bypass_ips: impl IntoIterator<Item = &'a IpCidr>) -> Self {
|
||||
for bypass_ip in bypass_ips {
|
||||
self.bypass.push(*bypass_ip);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -1,5 +1,7 @@
|
|||
use clap::Parser;
|
||||
use smoltcp::wire::IpCidr;
|
||||
use std::{net::IpAddr, process::ExitCode};
|
||||
use tun2proxy::util::str_to_cidr;
|
||||
use tun2proxy::{error::Error, main_entry, NetworkInterface, Options, Proxy};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -38,12 +40,12 @@ 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
|
||||
#[arg(short, long, value_name = "IP")]
|
||||
bypass: Option<IpAddr>,
|
||||
/// IPs used in routing setup which should bypass the tunnel
|
||||
#[arg(short, long, value_name = "IP|CIDR")]
|
||||
bypass: Vec<String>,
|
||||
|
||||
/// Verbosity level
|
||||
#[arg(short, long, value_name = "level", value_enum, default_value = "info")]
|
||||
|
@ -53,7 +55,7 @@ struct Args {
|
|||
/// DNS query handling strategy
|
||||
/// - Virtual: Intercept DNS queries and resolve them locally with a fake IP address
|
||||
/// - OverTcp: Use TCP to send DNS queries to the DNS server
|
||||
/// - Direct: Looks as general UDP traffic but change the destination to the DNS server
|
||||
/// - Direct: Do not handle DNS by relying on DNS server bypassing
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
|
||||
enum ArgDns {
|
||||
Virtual,
|
||||
|
@ -63,6 +65,7 @@ enum ArgDns {
|
|||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
|
||||
enum ArgSetup {
|
||||
None,
|
||||
Auto,
|
||||
}
|
||||
|
||||
|
@ -116,25 +119,26 @@ fn main() -> ExitCode {
|
|||
}
|
||||
};
|
||||
|
||||
let bypass_tun_ip = match args.bypass {
|
||||
Some(addr) => addr,
|
||||
None => args.proxy.addr.ip(),
|
||||
};
|
||||
options = options.with_bypass(Some(bypass_tun_ip));
|
||||
options.setup = args.setup.map(|s| s == ArgSetup::Auto).unwrap_or(false);
|
||||
|
||||
let block = || -> Result<(), Error> {
|
||||
let mut bypass_ips = Vec::<IpCidr>::new();
|
||||
for cidr_str in args.bypass {
|
||||
bypass_ips.push(str_to_cidr(&cidr_str)?);
|
||||
}
|
||||
if bypass_ips.is_empty() {
|
||||
let prefix_len = if args.proxy.addr.ip().is_ipv6() { 128 } else { 32 };
|
||||
bypass_ips.push(IpCidr::new(args.proxy.addr.ip().into(), prefix_len))
|
||||
}
|
||||
|
||||
options = options.with_bypass_ips(&bypass_ips);
|
||||
|
||||
#[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 {
|
||||
setup = Setup::new(&args.tun, bypass_ips, get_default_cidrs());
|
||||
setup.configure()?;
|
||||
|
||||
setup.drop_privileges()?;
|
||||
}
|
||||
}
|
||||
|
|
141
src/setup.rs
141
src/setup.rs
|
@ -6,8 +6,9 @@ use smoltcp::wire::IpCidr;
|
|||
use std::{
|
||||
convert::TryFrom,
|
||||
ffi::OsStr,
|
||||
fs,
|
||||
io::BufRead,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
os::unix::io::RawFd,
|
||||
process::{Command, Output},
|
||||
str::FromStr,
|
||||
|
@ -16,12 +17,13 @@ use std::{
|
|||
#[derive(Clone)]
|
||||
pub struct Setup {
|
||||
routes: Vec<IpCidr>,
|
||||
tunnel_bypass_addr: IpAddr,
|
||||
allow_private: bool,
|
||||
tunnel_bypass_addrs: Vec<IpCidr>,
|
||||
tun: String,
|
||||
set_up: bool,
|
||||
delete_proxy_route: bool,
|
||||
delete_proxy_routes: Vec<IpCidr>,
|
||||
child: libc::pid_t,
|
||||
unmount_resolvconf: bool,
|
||||
restore_resolvconf_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
pub fn get_default_cidrs() -> [IpCidr; 4] {
|
||||
|
@ -73,33 +75,41 @@ where
|
|||
impl Setup {
|
||||
pub fn new(
|
||||
tun: impl Into<String>,
|
||||
tunnel_bypass_addr: &IpAddr,
|
||||
tunnel_bypass_addrs: impl IntoIterator<Item = IpCidr>,
|
||||
routes: impl IntoIterator<Item = IpCidr>,
|
||||
allow_private: bool,
|
||||
) -> Self {
|
||||
let routes_cidr = routes.into_iter().collect();
|
||||
let bypass_cidrs = tunnel_bypass_addrs.into_iter().collect();
|
||||
Self {
|
||||
tun: tun.into(),
|
||||
tunnel_bypass_addr: *tunnel_bypass_addr,
|
||||
allow_private,
|
||||
tunnel_bypass_addrs: bypass_cidrs,
|
||||
routes: routes_cidr,
|
||||
set_up: false,
|
||||
delete_proxy_route: false,
|
||||
delete_proxy_routes: Vec::<IpCidr>::new(),
|
||||
child: 0,
|
||||
unmount_resolvconf: false,
|
||||
restore_resolvconf_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn route_proxy_address(&mut self) -> Result<bool, Error> {
|
||||
let route_show_args = if self.tunnel_bypass_addr.is_ipv6() {
|
||||
fn bypass_cidr(cidr: &IpCidr) -> Result<bool, Error> {
|
||||
let is_ipv6 = match cidr {
|
||||
IpCidr::Ipv4(_) => false,
|
||||
IpCidr::Ipv6(_) => true,
|
||||
};
|
||||
let route_show_args = if is_ipv6 {
|
||||
["ip", "-6", "route", "show"]
|
||||
} else {
|
||||
["ip", "-4", "route", "show"]
|
||||
};
|
||||
|
||||
let routes = run_iproute(route_show_args, "failed to get routing table", true)?;
|
||||
let routes = run_iproute(
|
||||
route_show_args,
|
||||
"failed to get routing table through the ip command",
|
||||
true,
|
||||
)?;
|
||||
|
||||
let mut route_info = Vec::<(IpCidr, Vec<String>)>::new();
|
||||
|
||||
for line in routes.stdout.lines() {
|
||||
if line.is_err() {
|
||||
break;
|
||||
|
@ -112,15 +122,11 @@ impl Setup {
|
|||
let mut split = line.split_whitespace();
|
||||
let mut dst_str = split.next().unwrap();
|
||||
if dst_str == "default" {
|
||||
dst_str = if self.tunnel_bypass_addr.is_ipv6() {
|
||||
"::/0"
|
||||
} else {
|
||||
"0.0.0.0/0"
|
||||
}
|
||||
dst_str = if is_ipv6 { "::/0" } else { "0.0.0.0/0" }
|
||||
}
|
||||
|
||||
let (addr_str, prefix_len_str) = match dst_str.split_once(['/']) {
|
||||
None => (dst_str, if self.tunnel_bypass_addr.is_ipv6() { "128" } else { "32" }),
|
||||
None => (dst_str, if is_ipv6 { "128" } else { "32" }),
|
||||
Some((addr_str, prefix_len_str)) => (addr_str, prefix_len_str),
|
||||
};
|
||||
|
||||
|
@ -135,19 +141,19 @@ impl Setup {
|
|||
// Sort routes by prefix length, the most specific route comes first.
|
||||
route_info.sort_by(|entry1, entry2| entry2.0.prefix_len().cmp(&entry1.0.prefix_len()));
|
||||
|
||||
for (cidr, route_components) in route_info {
|
||||
if !cidr.contains_addr(&smoltcp::wire::IpAddress::from(self.tunnel_bypass_addr)) {
|
||||
for (route_cidr, route_components) in route_info {
|
||||
if !route_cidr.contains_subnet(cidr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The IP address is routed through a more specific route than the default route.
|
||||
// In this case, there is nothing to do.
|
||||
if cidr.prefix_len() != 0 {
|
||||
if route_cidr.prefix_len() != 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut proxy_route = vec!["ip".into(), "route".into(), "add".into()];
|
||||
proxy_route.push(self.tunnel_bypass_addr.to_string());
|
||||
proxy_route.push(cidr.to_string());
|
||||
proxy_route.extend(route_components.into_iter());
|
||||
run_iproute(proxy_route, "failed to clone route for proxy", false)?;
|
||||
return Ok(true);
|
||||
|
@ -155,13 +161,7 @@ impl Setup {
|
|||
Ok(false)
|
||||
}
|
||||
|
||||
fn setup_resolv_conf() -> Result<(), Error> {
|
||||
let fd = nix::fcntl::open(
|
||||
"/tmp/tun2proxy-resolv.conf",
|
||||
nix::fcntl::OFlag::O_RDWR | nix::fcntl::OFlag::O_CLOEXEC | nix::fcntl::OFlag::O_CREAT,
|
||||
nix::sys::stat::Mode::from_bits(0o644).unwrap(),
|
||||
)?;
|
||||
let data = "nameserver 198.18.0.1\n".as_bytes();
|
||||
fn write_buffer_to_fd(fd: RawFd, data: &[u8]) -> Result<(), Error> {
|
||||
let mut written = 0;
|
||||
loop {
|
||||
if written >= data.len() {
|
||||
|
@ -169,15 +169,47 @@ impl Setup {
|
|||
}
|
||||
written += nix::unistd::write(fd, &data[written..])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_nameserver(fd: RawFd) -> Result<(), Error> {
|
||||
let data = "nameserver 198.18.0.1\n".as_bytes();
|
||||
Self::write_buffer_to_fd(fd, data)?;
|
||||
nix::sys::stat::fchmod(fd, nix::sys::stat::Mode::from_bits(0o444).unwrap())?;
|
||||
let source = format!("/proc/self/fd/{}", fd);
|
||||
nix::mount::mount(
|
||||
source.as_str().into(),
|
||||
"/etc/resolv.conf",
|
||||
"".into(),
|
||||
nix::mount::MsFlags::MS_BIND,
|
||||
"".into(),
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_resolv_conf(&mut self) -> Result<(), Error> {
|
||||
let mut fd = nix::fcntl::open(
|
||||
"/tmp/tun2proxy-resolv.conf",
|
||||
nix::fcntl::OFlag::O_RDWR | nix::fcntl::OFlag::O_CLOEXEC | nix::fcntl::OFlag::O_CREAT,
|
||||
nix::sys::stat::Mode::from_bits(0o644).unwrap(),
|
||||
)?;
|
||||
Self::write_nameserver(fd)?;
|
||||
let source = format!("/proc/self/fd/{}", fd);
|
||||
if Ok(())
|
||||
!= nix::mount::mount(
|
||||
source.as_str().into(),
|
||||
"/etc/resolv.conf",
|
||||
"".into(),
|
||||
nix::mount::MsFlags::MS_BIND,
|
||||
"".into(),
|
||||
)
|
||||
{
|
||||
log::warn!("failed to bind mount custom resolv.conf onto /etc/resolv.conf, resorting to direct write");
|
||||
nix::unistd::close(fd)?;
|
||||
|
||||
self.restore_resolvconf_data = Some(fs::read("/etc/resolv.conf")?);
|
||||
|
||||
fd = nix::fcntl::open(
|
||||
"/etc/resolv.conf",
|
||||
nix::fcntl::OFlag::O_WRONLY | nix::fcntl::OFlag::O_CLOEXEC | nix::fcntl::OFlag::O_TRUNC,
|
||||
nix::sys::stat::Mode::from_bits(0o644).unwrap(),
|
||||
)?;
|
||||
Self::write_nameserver(fd)?;
|
||||
} else {
|
||||
self.unmount_resolvconf = true;
|
||||
}
|
||||
nix::unistd::close(fd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -204,12 +236,20 @@ impl Setup {
|
|||
self.set_up = false;
|
||||
log::info!("[{}] Restoring network configuration", nix::unistd::getpid());
|
||||
let _ = Command::new("ip").args(["link", "del", self.tun.as_str()]).output();
|
||||
if self.delete_proxy_route {
|
||||
|
||||
for cidr in &self.delete_proxy_routes {
|
||||
let _ = Command::new("ip")
|
||||
.args(["route", "del", self.tunnel_bypass_addr.to_string().as_str()])
|
||||
.args(["route", "del", cidr.to_string().as_str()])
|
||||
.output();
|
||||
}
|
||||
nix::mount::umount("/etc/resolv.conf")?;
|
||||
|
||||
if self.unmount_resolvconf {
|
||||
nix::mount::umount("/etc/resolv.conf")?;
|
||||
}
|
||||
|
||||
if let Some(data) = &self.restore_resolvconf_data {
|
||||
fs::write("/etc/resolv.conf", data)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -223,8 +263,6 @@ impl Setup {
|
|||
)?;
|
||||
|
||||
self.set_up = true;
|
||||
let _tun_name = self.tun.clone();
|
||||
let _proxy_ip = self.tunnel_bypass_addr;
|
||||
|
||||
run_iproute(
|
||||
["ip", "link", "set", self.tun.as_str(), "up"],
|
||||
|
@ -232,9 +270,14 @@ impl Setup {
|
|||
true,
|
||||
)?;
|
||||
|
||||
let delete_proxy_route = self.route_proxy_address()?;
|
||||
self.delete_proxy_route = delete_proxy_route;
|
||||
Self::setup_resolv_conf()?;
|
||||
let mut delete_proxy_route = Vec::<IpCidr>::new();
|
||||
for cidr in &self.tunnel_bypass_addrs {
|
||||
if Self::bypass_cidr(cidr)? {
|
||||
delete_proxy_route.push(*cidr);
|
||||
}
|
||||
}
|
||||
self.delete_proxy_routes = delete_proxy_route;
|
||||
self.setup_resolv_conf()?;
|
||||
self.add_tunnel_routes()?;
|
||||
|
||||
// Signal to child that we are done setting up everything.
|
||||
|
@ -285,14 +328,6 @@ impl Setup {
|
|||
return Err("Automatic setup requires root privileges".into());
|
||||
}
|
||||
|
||||
if self.tunnel_bypass_addr.is_loopback() && !self.allow_private {
|
||||
log::warn!(
|
||||
"The proxy address {} is a loopback address. You may need to manually \
|
||||
provide --bypass-ip to specify the server IP bypassing the tunnel",
|
||||
self.tunnel_bypass_addr
|
||||
)
|
||||
}
|
||||
|
||||
let (read_from_child, write_to_parent) = nix::unistd::pipe()?;
|
||||
match fork::fork() {
|
||||
Ok(Fork::Child) => {
|
||||
|
|
24
src/socks.rs
24
src/socks.rs
|
@ -27,7 +27,6 @@ struct SocksProxyImpl {
|
|||
server_inbuf: VecDeque<u8>,
|
||||
client_outbuf: VecDeque<u8>,
|
||||
server_outbuf: VecDeque<u8>,
|
||||
data_buf: VecDeque<u8>,
|
||||
version: Version,
|
||||
credentials: Option<UserKey>,
|
||||
command: protocol::Command,
|
||||
|
@ -48,7 +47,6 @@ impl SocksProxyImpl {
|
|||
server_inbuf: VecDeque::default(),
|
||||
client_outbuf: VecDeque::default(),
|
||||
server_outbuf: VecDeque::default(),
|
||||
data_buf: VecDeque::default(),
|
||||
version,
|
||||
credentials,
|
||||
command,
|
||||
|
@ -131,8 +129,6 @@ impl SocksProxyImpl {
|
|||
}
|
||||
|
||||
self.server_inbuf.drain(0..8);
|
||||
self.server_outbuf.append(&mut self.data_buf);
|
||||
self.data_buf.clear();
|
||||
|
||||
self.state = SocksState::Established;
|
||||
self.state_change()
|
||||
|
@ -230,13 +226,9 @@ impl SocksProxyImpl {
|
|||
}
|
||||
if self.command == protocol::Command::UdpAssociate {
|
||||
self.udp_associate = Some(SocketAddr::try_from(&response.address)?);
|
||||
assert!(self.data_buf.is_empty());
|
||||
log::trace!("UDP associate recieved address {}", response.address);
|
||||
}
|
||||
|
||||
self.server_outbuf.append(&mut self.data_buf);
|
||||
self.data_buf.clear();
|
||||
|
||||
self.state = SocksState::Established;
|
||||
self.state_change()
|
||||
}
|
||||
|
@ -280,11 +272,7 @@ impl ProxyHandler for SocksProxyImpl {
|
|||
self.server_inbuf.extend(buffer.iter());
|
||||
}
|
||||
IncomingDirection::FromClient => {
|
||||
if self.state == SocksState::Established {
|
||||
self.client_inbuf.extend(buffer.iter());
|
||||
} else {
|
||||
self.data_buf.extend(buffer.iter());
|
||||
}
|
||||
self.client_inbuf.extend(buffer.iter());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,15 +302,15 @@ impl ProxyHandler for SocksProxyImpl {
|
|||
self.state == SocksState::Established
|
||||
}
|
||||
|
||||
fn have_data(&mut self, dir: Direction) -> bool {
|
||||
fn data_len(&self, dir: Direction) -> usize {
|
||||
match dir {
|
||||
Direction::Incoming(incoming) => match incoming {
|
||||
IncomingDirection::FromServer => !self.server_inbuf.is_empty(),
|
||||
IncomingDirection::FromClient => !self.client_inbuf.is_empty() || !self.data_buf.is_empty(),
|
||||
IncomingDirection::FromServer => self.server_inbuf.len(),
|
||||
IncomingDirection::FromClient => self.client_inbuf.len(),
|
||||
},
|
||||
Direction::Outgoing(outgoing) => match outgoing {
|
||||
OutgoingDirection::ToServer => !self.server_outbuf.is_empty(),
|
||||
OutgoingDirection::ToClient => !self.client_outbuf.is_empty(),
|
||||
OutgoingDirection::ToServer => self.server_outbuf.len(),
|
||||
OutgoingDirection::ToClient => self.client_outbuf.len(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
138
src/tun2proxy.rs
138
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},
|
||||
|
@ -174,6 +173,7 @@ const CLIENT_WRITE_CLOSED: u8 = 2;
|
|||
|
||||
const UDP_ASSO_TIMEOUT: u64 = 10; // seconds
|
||||
const DNS_PORT: u16 = 53;
|
||||
const IP_PACKAGE_MAX_SIZE: usize = 0xFFFF;
|
||||
|
||||
struct ConnectionState {
|
||||
smoltcp_handle: SocketHandle,
|
||||
|
@ -189,6 +189,7 @@ struct ConnectionState {
|
|||
udp_token: Option<Token>,
|
||||
udp_data_cache: LinkedList<Vec<u8>>,
|
||||
dns_over_tcp_expiry: Option<::std::time::Instant>,
|
||||
is_tcp_closed: bool,
|
||||
}
|
||||
|
||||
pub(crate) trait ProxyHandler {
|
||||
|
@ -197,7 +198,7 @@ pub(crate) trait ProxyHandler {
|
|||
fn consume_data(&mut self, dir: OutgoingDirection, size: usize);
|
||||
fn peek_data(&mut self, dir: OutgoingDirection) -> OutgoingDataEvent;
|
||||
fn connection_established(&self) -> bool;
|
||||
fn have_data(&mut self, dir: Direction) -> bool;
|
||||
fn data_len(&self, dir: Direction) -> usize;
|
||||
fn reset_connection(&self) -> bool;
|
||||
fn get_udp_associate(&self) -> Option<SocketAddr>;
|
||||
}
|
||||
|
@ -217,6 +218,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>,
|
||||
|
@ -230,6 +233,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> {
|
||||
|
@ -246,35 +253,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")?;
|
||||
|
@ -288,7 +307,6 @@ impl<'a> TunToProxy<'a> {
|
|||
iface.set_any_ip(true);
|
||||
|
||||
let tun = Self {
|
||||
#[cfg(target_family = "unix")]
|
||||
tun,
|
||||
poll,
|
||||
iface,
|
||||
|
@ -299,9 +317,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)
|
||||
|
@ -324,7 +340,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")?
|
||||
|
@ -395,13 +410,10 @@ impl<'a> TunToProxy<'a> {
|
|||
None => return Ok(()),
|
||||
};
|
||||
let mut closed_ends = 0;
|
||||
let handler = state.proxy_handler.as_ref();
|
||||
if (state.close_state & SERVER_WRITE_CLOSED) == SERVER_WRITE_CLOSED
|
||||
&& !state
|
||||
.proxy_handler
|
||||
.have_data(Direction::Incoming(IncomingDirection::FromServer))
|
||||
&& !state
|
||||
.proxy_handler
|
||||
.have_data(Direction::Outgoing(OutgoingDirection::ToClient))
|
||||
&& handler.data_len(Direction::Incoming(IncomingDirection::FromServer)) == 0
|
||||
&& handler.data_len(Direction::Outgoing(OutgoingDirection::ToClient)) == 0
|
||||
{
|
||||
// Close tun interface
|
||||
let socket = self.sockets.get_mut::<tcp::Socket>(state.smoltcp_handle);
|
||||
|
@ -411,12 +423,8 @@ impl<'a> TunToProxy<'a> {
|
|||
}
|
||||
|
||||
if (state.close_state & CLIENT_WRITE_CLOSED) == CLIENT_WRITE_CLOSED
|
||||
&& !state
|
||||
.proxy_handler
|
||||
.have_data(Direction::Incoming(IncomingDirection::FromClient))
|
||||
&& !state
|
||||
.proxy_handler
|
||||
.have_data(Direction::Outgoing(OutgoingDirection::ToServer))
|
||||
&& handler.data_len(Direction::Incoming(IncomingDirection::FromClient)) == 0
|
||||
&& handler.data_len(Direction::Outgoing(OutgoingDirection::ToServer)) == 0
|
||||
{
|
||||
// Close remote server
|
||||
if let Err(err) = state.mio_stream.shutdown(Shutdown::Write) {
|
||||
|
@ -443,6 +451,11 @@ impl<'a> TunToProxy<'a> {
|
|||
let socket = self.sockets.get_mut::<tcp::Socket>(state.smoltcp_handle);
|
||||
let mut error = Ok(());
|
||||
while socket.can_recv() && error.is_ok() {
|
||||
let dir = Direction::Outgoing(OutgoingDirection::ToServer);
|
||||
if state.proxy_handler.data_len(dir) >= IP_PACKAGE_MAX_SIZE {
|
||||
break;
|
||||
}
|
||||
|
||||
socket.recv(|data| {
|
||||
let event = IncomingDataEvent {
|
||||
direction: IncomingDirection::FromClient,
|
||||
|
@ -568,7 +581,7 @@ impl<'a> TunToProxy<'a> {
|
|||
state.dns_over_tcp_expiry = Some(Self::common_udp_life_timeout());
|
||||
|
||||
let mut vecbuf = vec![];
|
||||
Self::read_data_from_tcp_stream(&mut state.mio_stream, |data| {
|
||||
Self::read_data_from_tcp_stream(&mut state.mio_stream, &mut state.is_tcp_closed, |data| {
|
||||
vecbuf.extend_from_slice(data);
|
||||
Ok(())
|
||||
})?;
|
||||
|
@ -774,17 +787,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 {
|
||||
|
@ -809,13 +829,14 @@ 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,
|
||||
origin_dst: dst,
|
||||
udp_data_cache: LinkedList::new(),
|
||||
dns_over_tcp_expiry: None,
|
||||
is_tcp_closed: false,
|
||||
};
|
||||
Ok(state)
|
||||
}
|
||||
|
@ -833,13 +854,25 @@ impl<'a> TunToProxy<'a> {
|
|||
false
|
||||
}
|
||||
|
||||
fn clearup_expired_udp_associate(&mut self) -> Result<()> {
|
||||
fn tcp_is_closed(&self, info: &ConnectionInfo) -> bool {
|
||||
if let Some(state) = self.connection_map.get(info) {
|
||||
return state.is_tcp_closed;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn clearup_expired_connection(&mut self) -> Result<()> {
|
||||
let keys = self.connection_map.keys().cloned().collect::<Vec<_>>();
|
||||
for key in keys {
|
||||
if self.udp_associate_timeout_expired(&key) {
|
||||
log::trace!("UDP associate timeout: {}", key);
|
||||
self.remove_connection(&key)?;
|
||||
}
|
||||
|
||||
if self.tcp_is_closed(&key) {
|
||||
log::trace!("TCP closed: {}", key);
|
||||
self.remove_connection(&key)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -877,8 +910,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(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -922,15 +955,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(())
|
||||
}
|
||||
|
||||
|
@ -1034,7 +1075,7 @@ impl<'a> TunToProxy<'a> {
|
|||
|
||||
// TODO: Move this reading process to its own function.
|
||||
let mut vecbuf = vec![];
|
||||
Self::read_data_from_tcp_stream(&mut state.mio_stream, |data| {
|
||||
Self::read_data_from_tcp_stream(&mut state.mio_stream, &mut state.is_tcp_closed, |data| {
|
||||
vecbuf.extend_from_slice(data);
|
||||
Ok(())
|
||||
})?;
|
||||
|
@ -1103,7 +1144,7 @@ impl<'a> TunToProxy<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn read_data_from_tcp_stream<F>(stream: &mut TcpStream, mut callback: F) -> Result<()>
|
||||
fn read_data_from_tcp_stream<F>(stream: &mut TcpStream, is_closed: &mut bool, mut callback: F) -> Result<()>
|
||||
where
|
||||
F: FnMut(&mut [u8]) -> Result<()>,
|
||||
{
|
||||
|
@ -1112,6 +1153,7 @@ impl<'a> TunToProxy<'a> {
|
|||
match stream.read(&mut tmp) {
|
||||
Ok(0) => {
|
||||
// The tcp connection closed
|
||||
*is_closed = true;
|
||||
break;
|
||||
}
|
||||
Ok(read_result) => {
|
||||
|
@ -1133,11 +1175,11 @@ 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<std::thread::JoinHandle<()>> {
|
||||
let mut exit_trigger = self.exit_trigger.take().ok_or("Already running")?;
|
||||
let mut count = 0;
|
||||
let handle = ctrlc::set_handler(move || -> bool {
|
||||
let handle = ctrlc2::set_handler(move || -> bool {
|
||||
match exit_trigger.write(b"EXIT") {
|
||||
Ok(_) => {
|
||||
log::trace!("Exit signal triggered successfully");
|
||||
|
@ -1161,7 +1203,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"))]
|
||||
let handle = self.prepare_exiting_signal_trigger()?;
|
||||
|
||||
let mut events = Events::with_capacity(1024);
|
||||
|
@ -1181,7 +1223,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)?,
|
||||
|
@ -1190,16 +1231,15 @@ impl<'a> TunToProxy<'a> {
|
|||
}
|
||||
}
|
||||
self.send_to_smoltcp()?;
|
||||
self.clearup_expired_udp_associate()?;
|
||||
self.clearup_expired_connection()?;
|
||||
self.clearup_expired_dns_over_tcp()?;
|
||||
};
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
handle.join().unwrap();
|
||||
log::trace!("{:?}", ret);
|
||||
ret
|
||||
}
|
||||
|
||||
#[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) {
|
||||
|
@ -1216,12 +1256,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")?;
|
||||
|
|
22
src/util.rs
Normal file
22
src/util.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use crate::error::Error;
|
||||
use smoltcp::wire::IpCidr;
|
||||
use std::net::IpAddr;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn str_to_cidr(s: &str) -> Result<IpCidr, Error> {
|
||||
// IpCidr's FromString implementation requires the netmask to be specified.
|
||||
// Try to parse as IP address without netmask before falling back.
|
||||
match IpAddr::from_str(s) {
|
||||
Err(_) => (),
|
||||
Ok(cidr) => {
|
||||
let prefix_len = if cidr.is_ipv4() { 32 } else { 128 };
|
||||
return Ok(IpCidr::new(cidr.into(), prefix_len));
|
||||
}
|
||||
};
|
||||
|
||||
let cidr = IpCidr::from_str(s);
|
||||
match cidr {
|
||||
Err(()) => Err("Invalid CIDR: ".into()),
|
||||
Ok(cidr) => Ok(cidr),
|
||||
}
|
||||
}
|
556
src/wintuninterface.rs
Normal file
556
src/wintuninterface.rs
Normal file
|
@ -0,0 +1,556 @@
|
|||
use mio::{event, windows::NamedPipe, Interest, Registry, Token};
|
||||
use smoltcp::wire::IpCidr;
|
||||
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<'a>(
|
||||
&mut self,
|
||||
bypass_ips: impl IntoIterator<Item = &'a IpCidr>,
|
||||
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`
|
||||
for bypass_ip in bypass_ips {
|
||||
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)
|
||||
}
|
24
tests/iperf/dante.conf
Normal file
24
tests/iperf/dante.conf
Normal file
|
@ -0,0 +1,24 @@
|
|||
# logoutput: /var/log/socks.log
|
||||
internal: 10.0.0.3
|
||||
external: 10.0.0.3
|
||||
clientmethod: none
|
||||
socksmethod: none
|
||||
user.privileged: root
|
||||
user.notprivileged: nobody
|
||||
|
||||
client pass {
|
||||
from: 0/0 to: 0/0
|
||||
log: error connect disconnect
|
||||
}
|
||||
|
||||
socks pass {
|
||||
from: 0/0 to: 0/0
|
||||
command: bind connect udpassociate
|
||||
log: error connect disconnect
|
||||
socksmethod: none
|
||||
}
|
||||
|
||||
socks pass {
|
||||
from: 0.0.0.0/0 to: 0.0.0.0/0
|
||||
command: bindreply udpreply
|
||||
}
|
46
tests/iperf/test.sh
Executable file
46
tests/iperf/test.sh
Executable file
|
@ -0,0 +1,46 @@
|
|||
#!/bin/bash
|
||||
|
||||
# sudo apt install iperf3 dante-server
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
echo $SCRIPT_DIR
|
||||
|
||||
netns="test"
|
||||
dante="danted"
|
||||
tun2proxy="${SCRIPT_DIR}/../../target/release/tun2proxy"
|
||||
|
||||
ip netns add "$netns"
|
||||
|
||||
ip link add veth0 type veth peer name veth0 netns "$netns"
|
||||
|
||||
# Configure veth0 in default ns
|
||||
ip addr add 10.0.0.2/24 dev veth0
|
||||
ip link set dev veth0 up
|
||||
|
||||
# Configure veth0 in child ns
|
||||
ip netns exec "$netns" ip addr add 10.0.0.3/24 dev veth0
|
||||
ip netns exec "$netns" ip addr add 10.0.0.4/24 dev veth0
|
||||
ip netns exec "$netns" ip link set dev veth0 up
|
||||
|
||||
# Configure lo interface in child ns
|
||||
ip netns exec "$netns" ip addr add 127.0.0.1/8 dev lo
|
||||
ip netns exec "$netns" ip link set dev lo up
|
||||
|
||||
echo "Starting Dante in background ..."
|
||||
ip netns exec "$netns" "$dante" -f ${SCRIPT_DIR}/dante.conf &
|
||||
|
||||
# Start iperf3 server in netns
|
||||
ip netns exec "$netns" iperf3 -s -B 10.0.0.4 &
|
||||
|
||||
sleep 1
|
||||
|
||||
# Prepare tun2proxy
|
||||
ip tuntap add name tun0 mode tun
|
||||
ip link set tun0 up
|
||||
ip route add 10.0.0.4 dev tun0
|
||||
"$tun2proxy" --proxy socks5://10.0.0.3:1080 &
|
||||
|
||||
# Run iperf client through tun2proxy
|
||||
iperf3 -c 10.0.0.4
|
||||
|
||||
iperf3 -c 10.0.0.4 -R
|
|
@ -11,8 +11,10 @@ mod tests {
|
|||
use nix::sys::signal;
|
||||
use nix::unistd::Pid;
|
||||
use serial_test::serial;
|
||||
use smoltcp::wire::IpCidr;
|
||||
|
||||
use tun2proxy::setup::{get_default_cidrs, Setup};
|
||||
use tun2proxy::util::str_to_cidr;
|
||||
use tun2proxy::{main_entry, NetworkInterface, Options, Proxy, ProxyType};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -66,12 +68,17 @@ mod tests {
|
|||
continue;
|
||||
}
|
||||
|
||||
let bypass_ip = match env::var("BYPASS_IP") {
|
||||
Err(_) => test.proxy.addr.ip(),
|
||||
Ok(ip_str) => IpAddr::from_str(ip_str.as_str()).unwrap(),
|
||||
let mut bypass_ips = Vec::<IpCidr>::new();
|
||||
|
||||
match env::var("BYPASS_IP") {
|
||||
Err(_) => {
|
||||
let prefix_len = if test.proxy.addr.ip().is_ipv6() { 128 } else { 32 };
|
||||
bypass_ips.push(IpCidr::new(test.proxy.addr.ip().into(), prefix_len));
|
||||
}
|
||||
Ok(ip_str) => bypass_ips.push(str_to_cidr(&ip_str).expect("Invalid bypass IP")),
|
||||
};
|
||||
|
||||
let mut setup = Setup::new(TUN_TEST_DEVICE, &bypass_ip, get_default_cidrs(), false);
|
||||
let mut setup = Setup::new(TUN_TEST_DEVICE, bypass_ips, get_default_cidrs());
|
||||
setup.configure().unwrap();
|
||||
|
||||
match fork::fork() {
|
||||
|
|
Loading…
Add table
Reference in a new issue