Improve setup error handling and describe the setup function in the README

This commit is contained in:
B. Blechschmidt 2023-03-26 00:18:28 +01:00
parent bfface515d
commit d0c24b9f6a
2 changed files with 93 additions and 44 deletions

View file

@ -1,7 +1,5 @@
# tun2proxy # tun2proxy
Tunnel TCP traffic through SOCKS or HTTP on Linux. A tunnel interface for HTTP and SOCKS proxies on Linux based on [smoltcp](https://github.com/smoltcp-rs/smoltcp).
**Error handling incomplete and too restrictive.**
## Build ## Build
Clone the repository and `cd` into the project folder. Then run the following: Clone the repository and `cd` into the project folder. Then run the following:
@ -10,6 +8,12 @@ cargo build --release
``` ```
## Setup ## 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: A standard setup, which would route all traffic from your system through the tunnel interface, could look as follows:
```shell ```shell
# The proxy type can be either SOCKS4, SOCKS5 or HTTP. # The proxy type can be either SOCKS4, SOCKS5 or HTTP.

View file

@ -1,11 +1,11 @@
use crate::error::Error; use crate::error::Error;
use smoltcp::wire::IpCidr; use smoltcp::wire::IpCidr;
use std::ffi::CString; use std::ffi::{CString, OsStr};
use std::io::{BufRead, Write}; use std::io::{BufRead, Write};
use std::mem; use std::mem;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::os::fd::FromRawFd; use std::os::fd::FromRawFd;
use std::process::Command; use std::process::{Command, Output};
use std::ptr::null; use std::ptr::null;
use std::str::FromStr; use std::str::FromStr;
@ -26,6 +26,43 @@ pub fn get_default_cidrs() -> [IpCidr; 4] {
] ]
} }
fn run_iproute<I, S>(args: I, error: &str, require_success: bool) -> Result<Output, Error>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
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 { impl Setup {
pub fn new( pub fn new(
tun: impl Into<String>, tun: impl Into<String>,
@ -43,16 +80,12 @@ impl Setup {
fn clone_default_route(&mut self) -> Result<(), Error> { fn clone_default_route(&mut self) -> Result<(), Error> {
let route_show_args = if self.proxy_addr.is_ipv6() { let route_show_args = if self.proxy_addr.is_ipv6() {
Vec::from(["-6", "route", "show"]) ["ip", "-6", "route", "show"]
} else { } else {
Vec::from(["-4", "route", "show"]) ["ip", "-4", "route", "show"]
}; };
let e = Error::from("failed to get routing table"); let routes = run_iproute(route_show_args, "failed to get routing table", true)?;
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-` // Equivalent of `ip route | grep '^default' | cut -d ' ' -f 2-`
let mut default_route_args = Vec::<String>::new(); let mut default_route_args = Vec::<String>::new();
@ -72,14 +105,14 @@ impl Setup {
} }
} }
let e = Error::from("failed to clone default route for proxy"); let mut proxy_route = vec!["ip".into(), "route".into(), "add".into()];
let mut proxy_route = vec!["route".to_string(), "add".to_string()];
proxy_route.push(self.proxy_addr.to_string()); proxy_route.push(self.proxy_addr.to_string());
proxy_route.extend(default_route_args.clone()); proxy_route.extend(default_route_args.into_iter());
Command::new("ip") run_iproute(
.args(proxy_route) proxy_route,
.output() "failed to clone default route for proxy",
.map_err(|_| e)?; false,
)?;
Ok(()) Ok(())
} }
@ -115,31 +148,35 @@ impl Setup {
fn add_tunnel_routes(&self) -> Result<(), Error> { fn add_tunnel_routes(&self) -> Result<(), Error> {
for route in &self.routes { for route in &self.routes {
let e = Error::from(format!( run_iproute(
"failed to set up routing of {} through {}", [
route, self.tun "ip",
));
Command::new("ip")
.args([
"route", "route",
"add", "add",
route.to_string().as_str(), route.to_string().as_str(),
"dev", "dev",
self.tun.as_str(), self.tun.as_str(),
]) ],
.output() "failed to add route",
.map_err(|_| e)?; true,
)?;
} }
Ok(()) Ok(())
} }
fn shutdown(&self) { 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"); log::info!("Restoring network configuration");
let _ = Command::new("ip").args(["link", "del", tun_name]).output(); 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 { unsafe {
let umount_path = CString::new("/etc/resolv.conf").unwrap(); let umount_path = CString::new("/etc/resolv.conf").unwrap();
libc::umount(umount_path.as_ptr()); libc::umount(umount_path.as_ptr());
@ -147,32 +184,40 @@ impl Setup {
} }
pub fn setup(&mut self) -> Result<(), Error> { pub fn setup(&mut self) -> Result<(), Error> {
self.set_up = true;
unsafe { unsafe {
if libc::getuid() != 0 { if libc::getuid() != 0 {
return Err("Automatic setup requires root privileges".into()); 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 tun_name = self.tun.clone();
let proxy_ip = self.proxy_addr;
// TODO: This is not optimal. // TODO: This is not optimal.
ctrlc::set_handler(move || { ctrlc::set_handler(move || {
Self::shutdown_with_args(&tun_name); Self::shutdown_with_args(&tun_name, proxy_ip);
std::process::exit(0); std::process::exit(0);
})?; })?;
let e = Error::from("failed to create tunnel device"); run_iproute(
Command::new("ip") ["ip", "link", "set", self.tun.as_str(), "up"],
.args(["tuntap", "add", "name", self.tun.as_str(), "mode", "tun"]) "failed to bring up tunnel device",
.output() true,
.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.clone_default_route()?;
Self::setup_resolv_conf()?; Self::setup_resolv_conf()?;