diff --git a/README.md b/README.md index bc6218d..bd5ab52 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # tun2proxy -Tunnel TCP traffic through SOCKS or HTTP on Linux. - -**Error handling incomplete and too restrictive.** +A tunnel interface for HTTP and SOCKS proxies on Linux based on [smoltcp](https://github.com/smoltcp-rs/smoltcp). ## Build Clone the repository and `cd` into the project folder. Then run the following: @@ -10,6 +8,12 @@ cargo build --release ``` ## Setup +## Automated Setup +Using `--setup auto`, you can have tun2proxy configure your system to automatically route all traffic through the +specified proxy. This requires running the tool as root and will roughly perform the steps outlined in the section +describing the manual setup, except that a bind mount is used to overlay the `/etc/resolv.conf` file. + +## Manual Setup A standard setup, which would route all traffic from your system through the tunnel interface, could look as follows: ```shell # The proxy type can be either SOCKS4, SOCKS5 or HTTP. diff --git a/src/setup.rs b/src/setup.rs index 805cae5..c0263f3 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,11 +1,11 @@ use crate::error::Error; use smoltcp::wire::IpCidr; -use std::ffi::CString; +use std::ffi::{CString, OsStr}; 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::process::{Command, Output}; use std::ptr::null; use std::str::FromStr; @@ -26,6 +26,43 @@ pub fn get_default_cidrs() -> [IpCidr; 4] { ] } +fn run_iproute(args: I, error: &str, require_success: bool) -> Result +where + I: IntoIterator, + S: AsRef, +{ + let mut command = Command::new(""); + for (i, arg) in args.into_iter().enumerate() { + if i == 0 { + command = Command::new(arg); + } else { + command.arg(arg); + } + } + + let e = Error::from(error); + let output = command.output().map_err(|_| e)?; + if !require_success || output.status.success() { + Ok(output) + } else { + let mut args: Vec<&str> = command.get_args().map(|x| x.to_str().unwrap()).collect(); + let program = command.get_program().to_str().unwrap(); + let mut cmdline = Vec::<&str>::new(); + cmdline.push(program); + 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()), + Err(_) => Err(format!( + "Command `{:?}` failed with exit code {}", + command, + output.status.code().unwrap() + ) + .into()), + } + } +} + impl Setup { pub fn new( tun: impl Into, @@ -43,16 +80,12 @@ impl Setup { fn clone_default_route(&mut self) -> Result<(), Error> { let route_show_args = if self.proxy_addr.is_ipv6() { - Vec::from(["-6", "route", "show"]) + ["ip", "-6", "route", "show"] } else { - Vec::from(["-4", "route", "show"]) + ["ip", "-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)?; + let routes = run_iproute(route_show_args, "failed to get routing table", true)?; // Equivalent of `ip route | grep '^default' | cut -d ' ' -f 2-` let mut default_route_args = Vec::::new(); @@ -72,14 +105,14 @@ impl Setup { } } - let e = Error::from("failed to clone default route for proxy"); - let mut proxy_route = vec!["route".to_string(), "add".to_string()]; + let mut proxy_route = vec!["ip".into(), "route".into(), "add".into()]; 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)?; + proxy_route.extend(default_route_args.into_iter()); + run_iproute( + proxy_route, + "failed to clone default route for proxy", + false, + )?; Ok(()) } @@ -115,31 +148,35 @@ impl Setup { 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([ + run_iproute( + [ + "ip", "route", "add", route.to_string().as_str(), "dev", self.tun.as_str(), - ]) - .output() - .map_err(|_| e)?; + ], + "failed to add route", + true, + )?; } Ok(()) } fn shutdown(&self) { - Self::shutdown_with_args(&self.tun); + if !self.set_up { + return; + } + Self::shutdown_with_args(&self.tun, self.proxy_addr); } - fn shutdown_with_args(tun_name: &str) { + fn shutdown_with_args(tun_name: &str, proxy_ip: IpAddr) { log::info!("Restoring network configuration"); let _ = Command::new("ip").args(["link", "del", tun_name]).output(); + let _ = Command::new("ip") + .args(["route", "del", proxy_ip.to_string().as_str()]) + .output(); unsafe { let umount_path = CString::new("/etc/resolv.conf").unwrap(); libc::umount(umount_path.as_ptr()); @@ -147,32 +184,40 @@ impl Setup { } pub fn setup(&mut self) -> Result<(), Error> { - self.set_up = true; - unsafe { if libc::getuid() != 0 { return Err("Automatic setup requires root privileges".into()); } } + 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.proxy_addr; // TODO: This is not optimal. ctrlc::set_handler(move || { - Self::shutdown_with_args(&tun_name); + Self::shutdown_with_args(&tun_name, proxy_ip); 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)?; + run_iproute( + ["ip", "link", "set", self.tun.as_str(), "up"], + "failed to bring up tunnel device", + true, + )?; self.clone_default_route()?; Self::setup_resolv_conf()?;