5 changed files with 205 additions and 1 deletions
@ -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<IpCidr>, |
||||
|
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<String>, |
||||
|
proxy_addr: &IpAddr, |
||||
|
routes: impl IntoIterator<Item = IpCidr>, |
||||
|
) -> 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::<String>::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(()) |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue