diff --git a/Cargo.toml b/Cargo.toml index 5854264..bc2826a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,19 +10,19 @@ clap = { version = "4.1", features = ["derive"] } ctrlc = "3.2" dotenvy = "0.15" env_logger = "0.10" +fork = "0.1" hashlink = "0.8" libc = "0.2" log = "0.4" mio = { version = "0.8", features = ["os-poll", "net", "os-ext"] } +nix = { version = "0.26", features = ["process", "signal"] } +prctl = "1.0" smoltcp = { version = "0.9", git = "https://github.com/smoltcp-rs/smoltcp.git", features = ["std"] } thiserror = "1.0" url = "2.3" [dev-dependencies] ctor = "0.1" -fork = "0.1" -nix = { version = "0.26", features = ["process", "signal"] } -prctl = "1.0" reqwest = { version = "0.11", features = ["blocking", "json"] } serial_test = "1.0" test-log = "0.2" diff --git a/src/error.rs b/src/error.rs index 423cef6..0b48fcd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -35,6 +35,12 @@ pub enum Error { #[error("&String {0}")] RefString(String), + + #[error("nix::errno::Errno {0:?}")] + OSError(#[from] nix::errno::Errno), + + #[error("std::num::ParseIntError {0:?}")] + IntParseError(#[from] std::num::ParseIntError), } impl From<&str> for Error { diff --git a/src/lib.rs b/src/lib.rs index 2d985ac..5c2ad60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,25 +111,25 @@ impl Credentials { } } -pub fn main_entry(tun: &str, proxy: Proxy, options: Options) -> Result<(), Error> { +pub fn main_entry(tun: &str, proxy: &Proxy, options: Options) -> Result<(), Error> { let mut ttp = TunToProxy::new(tun, options)?; match proxy.proxy_type { ProxyType::Socks4 => { ttp.add_connection_manager(SocksManager::new( proxy.addr, SocksVersion::V4, - proxy.credentials, + proxy.credentials.clone(), )); } ProxyType::Socks5 => { ttp.add_connection_manager(SocksManager::new( proxy.addr, SocksVersion::V5, - proxy.credentials, + proxy.credentials.clone(), )); } ProxyType::Http => { - ttp.add_connection_manager(HttpManager::new(proxy.addr, proxy.credentials)); + ttp.add_connection_manager(HttpManager::new(proxy.addr, proxy.credentials.clone())); } } ttp.run() diff --git a/src/main.rs b/src/main.rs index 2d388dd..8b9dcd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ use clap::Parser; use env_logger::Env; + use std::net::IpAddr; use std::process::ExitCode; +use tun2proxy::error::Error; use tun2proxy::setup::{get_default_cidrs, Setup}; use tun2proxy::Options; use tun2proxy::{main_entry, Proxy}; @@ -63,27 +65,32 @@ fn main() -> ExitCode { options = options.with_virtual_dns(); } - let mut setup: Setup; - if args.setup == Some(ArgSetup::Auto) { - let bypass_tun_ip = match args.setup_ip { - Some(addr) => addr, - None => args.proxy.addr.ip(), - }; - setup = Setup::new( - &args.tun, - &bypass_tun_ip, - get_default_cidrs(), - args.setup_ip.is_some(), - ); - if let Err(e) = setup.setup() { - log::error!("{e}"); - return ExitCode::FAILURE; - } - } + if let Err(e) = (|| -> Result<(), Error> { + let mut setup: Setup; + if args.setup == Some(ArgSetup::Auto) { + let bypass_tun_ip = match args.setup_ip { + Some(addr) => addr, + None => args.proxy.addr.ip(), + }; + setup = Setup::new( + &args.tun, + &bypass_tun_ip, + get_default_cidrs(), + args.setup_ip.is_some(), + ); - if let Err(e) = main_entry(&args.tun, args.proxy, options) { + setup.configure()?; + + setup.drop_privileges()?; + } + + main_entry(&args.tun, &args.proxy, options)?; + + Ok(()) + })() { log::error!("{e}"); - return ExitCode::FAILURE; - } + std::process::exit(1); + }; + ExitCode::SUCCESS } diff --git a/src/setup.rs b/src/setup.rs index 8d9099a..c977e0c 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,14 +1,19 @@ use crate::error::Error; use smoltcp::wire::IpCidr; -use std::ffi::{CString, OsStr}; -use std::io::{BufRead, Write}; -use std::mem; +use std::convert::TryFrom; + +use std::ffi::OsStr; +use std::io::BufRead; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use std::os::unix::io::FromRawFd; + +use std::os::fd::RawFd; + use std::process::{Command, Output}; -use std::ptr::null; + use std::str::FromStr; +use fork::Fork; + #[derive(Clone)] pub struct Setup { routes: Vec, @@ -17,6 +22,7 @@ pub struct Setup { tun: String, set_up: bool, delete_proxy_route: bool, + child: libc::pid_t, } pub fn get_default_cidrs() -> [IpCidr; 4] { @@ -54,7 +60,13 @@ where cmdline.append(&mut args); let command = cmdline.as_slice().join(" "); match String::from_utf8(output.stderr.clone()) { - Ok(output) => Err(format!("Command `{}` failed: {}", command, output).into()), + Ok(output) => Err(format!( + "[{}] Command `{}` failed: {}", + nix::unistd::getpid(), + command, + output + ) + .into()), Err(_) => Err(format!( "Command `{:?}` failed with exit code {}", command, @@ -80,6 +92,7 @@ impl Setup { routes: routes_cidr, set_up: false, delete_proxy_route: false, + child: 0, } } @@ -113,7 +126,17 @@ impl Setup { } } - let (addr_str, prefix_len_str) = dst_str.split_once(['/']).unwrap(); + let (addr_str, prefix_len_str) = match dst_str.split_once(['/']) { + None => ( + dst_str, + if self.tunnel_bypass_addr.is_ipv6() { + "128" + } else { + "32" + }, + ), + Some((addr_str, prefix_len_str)) => (addr_str, prefix_len_str), + }; let cidr: IpCidr = IpCidr::new( std::net::IpAddr::from_str(addr_str).unwrap().into(), @@ -147,32 +170,29 @@ impl Setup { } 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, 0o444) == -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()); + 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_u32).unwrap(), + )?; + let data = "nameserver 198.18.0.1\n".as_bytes(); + let mut written = 0; + loop { + if written >= data.len() { + break; } + written += nix::unistd::write(fd, &data[written..])?; } + nix::sys::stat::fchmod(fd, nix::sys::stat::Mode::from_bits(0o444_u32).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(), + )?; + nix::unistd::close(fd)?; Ok(()) } @@ -194,32 +214,114 @@ impl Setup { Ok(()) } - fn shutdown(&self) { - if !self.set_up { - return; - } - Self::shutdown_with_args(&self.tun, self.tunnel_bypass_addr, self.delete_proxy_route); - } - - fn shutdown_with_args(tun_name: &str, proxy_ip: IpAddr, delete_proxy_route: bool) { - log::info!("Restoring network configuration"); - let _ = Command::new("ip").args(["link", "del", tun_name]).output(); - if delete_proxy_route { + fn shutdown(&mut self) -> Result<(), Error> { + 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 { let _ = Command::new("ip") - .args(["route", "del", proxy_ip.to_string().as_str()]) + .args(["route", "del", self.tunnel_bypass_addr.to_string().as_str()]) .output(); } - unsafe { - let umount_path = CString::new("/etc/resolv.conf").unwrap(); - libc::umount(umount_path.as_ptr()); - } + nix::mount::umount("/etc/resolv.conf")?; + Ok(()) } - pub fn setup(&mut self) -> Result<(), Error> { - unsafe { - if libc::getuid() != 0 { - return Err("Automatic setup requires root privileges".into()); + fn setup_and_handle_signals(&mut self, read_from_child: RawFd, write_to_parent: RawFd) { + if let Err(e) = (|| -> Result<(), Error> { + nix::unistd::close(read_from_child)?; + run_iproute( + [ + "ip", + "tuntap", + "add", + "name", + self.tun.as_str(), + "mode", + "tun", + ], + "failed to create tunnel device", + true, + )?; + + 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"], + "failed to bring up tunnel device", + true, + )?; + + let delete_proxy_route = self.route_proxy_address()?; + self.delete_proxy_route = delete_proxy_route; + Self::setup_resolv_conf()?; + self.add_tunnel_routes()?; + + // Signal to child that we are done setting up everything. + if nix::unistd::write(write_to_parent, &[1])? != 1 { + return Err("Failed to write to pipe".into()); } + nix::unistd::close(write_to_parent)?; + + // Now wait for the termination signals. + let mut mask = nix::sys::signal::SigSet::empty(); + mask.add(nix::sys::signal::SIGINT); + mask.add(nix::sys::signal::SIGTERM); + mask.add(nix::sys::signal::SIGQUIT); + mask.thread_block().unwrap(); + + let mut fd = nix::sys::signalfd::SignalFd::new(&mask).unwrap(); + loop { + let res = fd.read_signal().unwrap().unwrap(); + let signo = nix::sys::signal::Signal::try_from(res.ssi_signo as i32).unwrap(); + if signo == nix::sys::signal::SIGINT + || signo == nix::sys::signal::SIGTERM + || signo == nix::sys::signal::SIGQUIT + { + break; + } + } + + self.shutdown()?; + Ok(()) + })() { + log::error!("{e}"); + self.shutdown().unwrap(); + }; + } + + pub fn drop_privileges(&self) -> Result<(), Error> { + let gid_str = match std::env::var("SUDO_GID") { + Ok(uid_str) => uid_str, + _ => String::from("65535"), + }; + let gid = gid_str.parse::()?; + nix::unistd::setgid(nix::unistd::Gid::from_raw(gid))?; + + let uid_str = match std::env::var("SUDO_UID") { + Ok(uid_str) => uid_str, + _ => String::from("65535"), + }; + let uid = uid_str.parse::()?; + nix::unistd::setuid(nix::unistd::Uid::from_raw(uid))?; + + Ok(()) + } + + pub fn configure(&mut self) -> Result<(), Error> { + log::info!( + "[{}] Setting up network configuration", + nix::unistd::getpid() + ); + if nix::unistd::getuid() != 0.into() { + return Err("Automatic setup requires root privileges".into()); } if self.tunnel_bypass_addr.is_loopback() && !self.allow_private { @@ -230,45 +332,34 @@ impl Setup { ) } - run_iproute( - [ - "ip", - "tuntap", - "add", - "name", - self.tun.as_str(), - "mode", - "tun", - ], - "failed to create tunnel device", - true, + let (read_from_child, write_to_parent) = nix::unistd::pipe()?; + match fork::fork() { + Ok(Fork::Child) => { + prctl::set_death_signal(nix::sys::signal::SIGINT as isize).unwrap(); + self.setup_and_handle_signals(read_from_child, write_to_parent); + std::process::exit(0); + } + Ok(Fork::Parent(child)) => { + self.child = child; + nix::unistd::close(write_to_parent)?; + let mut buf = [0]; + if nix::unistd::read(read_from_child, &mut buf)? != 1 { + return Err("Failed to read from pipe".into()); + } + nix::unistd::close(read_from_child)?; + + Ok(()) + } + _ => Err("Failed to fork".into()), + } + } + + pub fn restore(&mut self) -> Result<(), Error> { + nix::sys::signal::kill( + nix::unistd::Pid::from_raw(self.child), + nix::sys::signal::SIGINT, )?; - - 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"], - "failed to bring up tunnel device", - true, - )?; - - let delete_proxy_route = self.route_proxy_address()?; - self.delete_proxy_route = delete_proxy_route; - ctrlc::set_handler(move || { - Self::shutdown_with_args(&tun_name, proxy_ip, delete_proxy_route); - std::process::exit(0); - })?; - Self::setup_resolv_conf()?; - self.add_tunnel_routes()?; - + nix::sys::wait::waitpid(nix::unistd::Pid::from_raw(self.child), None)?; Ok(()) } } - -impl Drop for Setup { - fn drop(&mut self) { - self.shutdown(); - } -} diff --git a/tests/proxy.rs b/tests/proxy.rs index 4fe4dd6..00128db 100644 --- a/tests/proxy.rs +++ b/tests/proxy.rs @@ -64,28 +64,29 @@ mod tests { continue; } + let mut setup = Setup::new( + TUN_TEST_DEVICE, + &test.proxy.addr.ip(), + get_default_cidrs(), + false, + ); + setup.configure().unwrap(); + match fork::fork() { Ok(Fork::Parent(child)) => { test_function(); signal::kill(Pid::from_raw(child), signal::SIGINT) .expect("failed to kill child"); - nix::sys::wait::waitpid(Pid::from_raw(child), None) - .expect("failed to wait for child"); + setup.restore().unwrap(); } Ok(Fork::Child) => { - prctl::set_death_signal(signal::SIGKILL as isize).unwrap(); // 9 == SIGKILL - - let _setup = Setup::new( - TUN_TEST_DEVICE, - &test.proxy.addr.ip(), - get_default_cidrs(), - false, - ); + prctl::set_death_signal(signal::SIGINT as isize).unwrap(); let _ = main_entry( TUN_TEST_DEVICE, - test.proxy, + &test.proxy, Options::new().with_virtual_dns(), ); + std::process::exit(0); } Err(_) => panic!(), }