From 1a4a8c4c16cfc84009533e1f96fb2c35c3a72e96 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sat, 25 Mar 2023 21:12:41 +0100 Subject: [PATCH] Add auto setup method --- Cargo.toml | 2 + src/error.rs | 8 ++- src/lib.rs | 1 + src/main.rs | 21 +++++++ src/setup.rs | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 src/setup.rs diff --git a/Cargo.toml b/Cargo.toml index 466201d..5854264 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,11 @@ version = "0.1.1" [dependencies] base64 = { version = "0.21" } clap = { version = "4.1", features = ["derive"] } +ctrlc = "3.2" dotenvy = "0.15" env_logger = "0.10" hashlink = "0.8" +libc = "0.2" log = "0.4" mio = { version = "0.8", features = ["os-poll", "net", "os-ext"] } smoltcp = { version = "0.9", git = "https://github.com/smoltcp-rs/smoltcp.git", features = ["std"] } diff --git a/src/error.rs b/src/error.rs index 803111e..dde179d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,11 @@ #[derive(thiserror::Error, Debug)] pub enum Error { + #[error("std::ffi::NulError {0:?}")] + Nul(#[from] std::ffi::NulError), + + #[error("ctrlc::Error {0:?}")] + Send(#[from] ctrlc::Error), + #[error("std::io::Error {0}")] Io(#[from] std::io::Error), @@ -19,7 +25,7 @@ pub enum Error { Bind(#[from] smoltcp::socket::udp::BindError), #[error("smoltcp::socket::tcp::SendError {0:?}")] - Send(#[from] smoltcp::socket::tcp::SendError), + SontrolHandler(#[from] smoltcp::socket::tcp::SendError), #[error("&str {0}")] Str(String), diff --git a/src/lib.rs b/src/lib.rs index 5f5acd1..5c4fe14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ use std::net::{SocketAddr, ToSocketAddrs}; mod error; mod http; +pub mod setup; mod socks5; mod tun2proxy; mod virtdevice; diff --git a/src/main.rs b/src/main.rs index b0adacd..18550d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ use clap::Parser; use env_logger::Env; +use std::process::exit; +use tun2proxy::setup::{get_default_cidrs, Setup}; use tun2proxy::Options; use tun2proxy::{main_entry, Proxy}; @@ -25,6 +27,10 @@ struct Args { default_value = "virtual" )] dns: ArgDns, + + /// Setup + #[arg(short, long, value_name = "method", value_enum)] + setup: ArgSetup, } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] @@ -33,6 +39,11 @@ enum ArgDns { None, } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] +enum ArgSetup { + Auto, +} + fn main() { dotenvy::dotenv().ok(); env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); @@ -47,7 +58,17 @@ fn main() { options = options.with_virtual_dns(); } + let mut setup: Setup; + if args.setup == ArgSetup::Auto { + setup = Setup::new(&args.tun, &args.proxy.addr.ip(), get_default_cidrs()); + if let Err(e) = setup.setup() { + log::error!("{e}"); + exit(1); + } + } + if let Err(e) = main_entry(&args.tun, args.proxy, options) { log::error!("{e}"); + exit(1); } } diff --git a/src/setup.rs b/src/setup.rs new file mode 100644 index 0000000..4d7efce --- /dev/null +++ b/src/setup.rs @@ -0,0 +1,174 @@ +use crate::error::Error; +use smoltcp::wire::IpCidr; +use std::ffi::CString; +use std::io::{BufRead, Write}; +use std::mem; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::os::fd::FromRawFd; +use std::process::Command; +use std::ptr::null; +use std::str::FromStr; + +#[derive(Clone)] +pub struct Setup { + routes: Vec, + proxy_addr: IpAddr, + tun: String, + set_up: bool, +} + +pub fn get_default_cidrs() -> [IpCidr; 4] { + [ + IpCidr::new(Ipv4Addr::from_str("0.0.0.0").unwrap().into(), 1), + IpCidr::new(Ipv4Addr::from_str("128.0.0.0").unwrap().into(), 1), + IpCidr::new(Ipv6Addr::from_str("::").unwrap().into(), 1), + IpCidr::new(Ipv6Addr::from_str("8000::").unwrap().into(), 1), + ] +} + +impl Setup { + pub fn new( + tun: impl Into, + proxy_addr: &IpAddr, + routes: impl IntoIterator, + ) -> Self { + let routes_cidr = routes.into_iter().collect(); + Self { + tun: tun.into(), + proxy_addr: *proxy_addr, + routes: routes_cidr, + set_up: false, + } + } + + fn clone_default_route(&mut self) -> Result<(), Error> { + let route_show_args = if self.proxy_addr.is_ipv6() { + Vec::from(["-6", "route", "show"]) + } else { + Vec::from(["-4", "route", "show"]) + }; + + let e = Error::from("failed to get routing table"); + let routes = Command::new("ip") + .args(route_show_args.as_slice()) + .output() + .map_err(|_| e)?; + + // Equivalent of `ip route | grep '^default' | cut -d ' ' -f 2-` + let mut default_route_args = Vec::::new(); + for result in routes.stdout.lines() { + let line = result.unwrap(); + let split = line.split_whitespace(); + for (i, route_component) in split.enumerate() { + if i == 0 && route_component != "default" { + break; + } else if i == 0 { + continue; + } + default_route_args.push(String::from(route_component)); + } + if !default_route_args.is_empty() { + break; + } + } + + let e = Error::from("failed to clone default route for proxy"); + let mut proxy_route = vec!["route".to_string(), "add".to_string()]; + proxy_route.push(self.proxy_addr.to_string()); + proxy_route.extend(default_route_args.clone()); + Command::new("ip") + .args(proxy_route) + .output() + .map_err(|_| e)?; + Ok(()) + } + + fn setup_resolv_conf() -> Result<(), Error> { + unsafe { + let fd = libc::open( + CString::new("/tmp/tun2proxy-resolv.conf")?.as_ptr(), + libc::O_RDWR | libc::O_CLOEXEC | libc::O_CREAT, + ); + if fd == -1 { + return Err("Failed to create temporary file".into()); + } + let mut f = std::fs::File::from_raw_fd(fd); + f.write_all("nameserver 198.18.0.1\n".as_bytes())?; + mem::forget(f); + if libc::fchmod(fd, 0o644) == -1 { + return Err("Failed to change ownership of /etc/resolv.conf".into()); + } + let fd_path = format!("/proc/self/fd/{}", fd); + if libc::mount( + CString::new(fd_path)?.as_ptr(), + CString::new("/etc/resolv.conf")?.as_ptr(), + CString::new("resolvconf")?.as_ptr(), + libc::MS_BIND, + null(), + ) == -1 + { + return Err("Failed to mount /etc/resolv.conf".into()); + } + } + Ok(()) + } + + fn add_tunnel_routes(&self) -> Result<(), Error> { + for route in &self.routes { + let e = Error::from(format!( + "failed to set up routing of {} through {}", + route, self.tun + )); + Command::new("ip") + .args([ + "route", + "add", + route.to_string().as_str(), + "dev", + self.tun.as_str(), + ]) + .output() + .map_err(|_| e)?; + } + Ok(()) + } + + fn shutdown(tun_name: String) { + let _ = Command::new("ip") + .args(["link", "del", tun_name.as_str()]) + .output(); + unsafe { + let umount_path = CString::new("/etc/resolv.conf").unwrap(); + libc::umount(umount_path.as_ptr()); + } + } + + pub fn setup(&mut self) -> Result<(), Error> { + self.set_up = true; + let tun_name = self.tun.clone(); + + // TODO: This is not optimal. + ctrlc::set_handler(move || { + Self::shutdown(tun_name.clone()); + std::process::exit(0); + })?; + + let e = Error::from("failed to create tunnel device"); + Command::new("ip") + .args(["tuntap", "add", "name", self.tun.as_str(), "mode", "tun"]) + .output() + .map_err(|_| e)?; + + let e = Error::from("failed to bring up tunnel device"); + Command::new("ip") + .args(["link", "set", self.tun.as_str(), "up"]) + .output() + .map_err(|_| e)?; + + self.clone_default_route()?; + Self::setup_resolv_conf()?; + self.add_tunnel_routes()?; + + Ok(()) + } +}