diff --git a/Cargo.lock b/Cargo.lock index f7cf03a..ec141ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1034,6 +1034,71 @@ dependencies = [ "tempfile", ] +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-proto" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842c6770fc4bb33dd902f41829c61ef872b8e38de1405aa0b938b27b8fba12c3" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" +dependencies = [ + "bytes", + "futures", + "libc", + "log", + "tokio", +] + [[package]] name = "nix" version = "0.26.2" @@ -1167,6 +1232,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "pbkdf2" version = "0.11.0" @@ -1306,6 +1377,24 @@ dependencies = [ "winreg", ] +[[package]] +name = "rtnetlink" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" +dependencies = [ + "futures", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-packet-utils", + "netlink-proto", + "netlink-sys", + "nix", + "thiserror", + "tokio", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1808,6 +1897,7 @@ dependencies = [ "log", "nix", "reqwest", + "rtnetlink", "serde", "socket2", "ssri", diff --git a/tun/Cargo.toml b/tun/Cargo.toml index 2979c18..fa465cd 100644 --- a/tun/Cargo.toml +++ b/tun/Cargo.toml @@ -23,6 +23,9 @@ tokio = ["tokio/net", "dep:futures"] [target.'cfg(feature = "tokio")'.dev-dependencies] tokio = { features = ["rt", "macros"] } +[target.'cfg(linux)'.dependencies] +rtnetlink = "0.13.1" + [target.'cfg(windows)'.dependencies] lazy_static = "1.4" libloading = "0.7" diff --git a/tun/src/lib.rs b/tun/src/lib.rs index 151c10d..5e0b208 100644 --- a/tun/src/lib.rs +++ b/tun/src/lib.rs @@ -9,6 +9,7 @@ mod imp; pub(crate) mod imp; mod options; +pub mod routing; #[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(feature = "tokio")] diff --git a/tun/src/routing.rs b/tun/src/routing.rs new file mode 100644 index 0000000..a4dcf2f --- /dev/null +++ b/tun/src/routing.rs @@ -0,0 +1,148 @@ +use std::{io, mem}; +use crate::TunInterface; +use std::net::{IpAddr, SocketAddr}; +use std::process::Command; +use fehler::throws; +// use rtnetlink; +use std::io::{Error, Write}; +use libc::{rt_msghdr, c_uchar, rt_metrics, sockaddr_dl}; +use std::net::{Ipv4Addr, SocketAddrV4}; +use socket2::SockAddr; + +struct Handle { + pub inner: socket2::Socket, +} + +impl Handle { + #[throws] + pub fn new() -> Self { + let inner = socket2::Socket::new(socket2::Domain::from(libc::PF_ROUTE), socket2::Type::RAW, None)?; + Self { inner } + } +} + +#[derive(Debug)] +pub struct Route<'a> { + pub destination: IpAddr, // Default: 0.0.0.0 + pub destination_prefix: u8, + + pub interface: &'a TunInterface, + + // #[cfg(target_os = "linux")] + // pub ifindex: Option, + // + // #[cfg(target_os = "linux")] + // pub table: u8, +} + +impl<'a> Route<'a> { + #[throws] + pub fn new(destination: IpAddr, destination_prefix: u8, interface: &'a TunInterface) -> Self { + let mut handle = Handle::new()?; + + let mut hdr: rt_msghdr = unsafe { mem::zeroed() }; + hdr.rtm_version = libc::RTM_VERSION as c_uchar; + hdr.rtm_type = libc::RTM_ADD as c_uchar; + hdr.rtm_flags = libc::RTF_STATIC | libc::RTF_UP; + hdr.rtm_addrs = libc::RTA_DST | libc::RTA_NETMASK | libc::RTA_GATEWAY | libc::RTA_IFP; + hdr.rtm_seq = 1; + + let destination_addr = SockAddr::from(SocketAddr::new(destination, 0)); + let gateway_addr = SockAddr::from(SocketAddrV4::new(interface.ipv4_addr()?, 0)); + + let index = interface.index()?; + let (_, if_addr) = unsafe { + SockAddr::init(|storage, len| { + *len = mem::size_of::() as u32; + + let mut addr: &mut libc::sockaddr_dl = &mut *storage.cast(); + addr.sdl_len = *len as u8; + addr.sdl_family = libc::AF_LINK as u8; + addr.sdl_index = index as u16; + Ok(()) + })? + }; + let mask_addr = SockAddr::from(SocketAddrV4::new(Ipv4Addr::new(255, 255, 255, 255), 0)); + + hdr.rtm_msglen = ( + (mem::size_of::() as u32) + destination_addr.len() + gateway_addr.len() + if_addr.len() + mask_addr.len() + ) as u16; + + println!("0z {:#?}", if_addr.len()); + let buffer = vec![]; + let mut cursor = std::io::Cursor::new(buffer); + cursor.write_all(unsafe { + std::slice::from_raw_parts(&hdr as *const _ as *const _, mem::size_of::()) + })?; + + println!("one"); + write_addr(&mut cursor, destination_addr)?; + println!("two"); + write_addr(&mut cursor, gateway_addr)?; + println!("three"); + write_addr(&mut cursor, if_addr)?; + println!("4"); + write_addr(&mut cursor, mask_addr)?; + println!("five"); + + let buf = cursor.into_inner(); + println!("cbuf len: {:#?}, calcsize: {:#?}", buf.len(), hdr.rtm_msglen); + + handle.inner.write_all(&buf)?; + + // Create handle + Self { + destination, + destination_prefix, + interface + } + } + + #[cfg(target_os = "linux")] + pub fn through_interface(&self, iface: TunInterface) { + // High-level overview: + // 1. Create a socket + iface.socket.bind(iface.addr); + + // 2. Bind the socket to the interface + // 3. Add the route + // 4. Close the socket + // 5. Return the result + } +} + +#[throws] +#[cfg(target_platform = "linux")] +pub async fn add_route(route: Route) { + let conn = rtnetlink::new_connection()?; + + let handle = conn.route(); + let route_add_request = handle + .add() + .v4() + .destination_prefix(route.destination, route.destination_prefix) + .output_interface(route.interface.index()?) + .execute().await?; +} + +#[throws] +#[cfg(target_vendor="apple")] +pub fn add_route(route: Route) { + // Construct + // Construct RT message + Command::new("route") + .arg("add") + .arg("-host") + .arg(route.destination.to_string()) + .arg("-interface") + .arg(route.interface.name().expect("the interface's name")) + .spawn() + .expect("failed to execute add route process"); +} + +#[throws] +fn write_addr(sock: &mut T, addr: socket2::SockAddr) { + let len = addr.len() as usize; + let ptr = addr.as_ptr() as *const _; + sock.write_all(unsafe { std::slice::from_raw_parts(ptr, len) })?; +} \ No newline at end of file diff --git a/tun/src/unix/apple/mod.rs b/tun/src/unix/apple/mod.rs index f4fd1e2..9dea3d3 100644 --- a/tun/src/unix/apple/mod.rs +++ b/tun/src/unix/apple/mod.rs @@ -1,5 +1,5 @@ use byteorder::{ByteOrder, NetworkEndian}; -use fehler::throws; +use fehler::{throw, throws}; use libc::{c_char, iovec, writev, AF_INET, AF_INET6}; use tracing::info; use socket2::{Domain, SockAddr, Socket, Type}; @@ -9,6 +9,7 @@ use std::os::fd::{AsRawFd, RawFd}; use std::{io::Error, mem}; use tracing::instrument; + mod kern_control; mod sys; @@ -54,6 +55,11 @@ impl TunInterface { #[throws] #[instrument] pub fn name(&self) -> String { + ifname_to_string(self.ifname()?) + } + + #[throws] + fn ifname(&self) -> [libc::c_char; libc::IFNAMSIZ] { let mut buf = [0 as c_char; sys::IFNAMSIZ]; let mut len = buf.len() as sys::socklen_t; sys::syscall!(getsockopt( @@ -63,7 +69,20 @@ impl TunInterface { buf.as_mut_ptr() as *mut sys::c_void, &mut len, ))?; - ifname_to_string(buf) + buf + } + + + #[throws] + #[instrument] + pub fn index(&self) -> usize { + let ifname = self.ifname()?; + let index = unsafe { libc::if_nametoindex(ifname.as_ptr()) }; + if index == 0 { + throw!(Error::last_os_error()) + } + + index as usize } #[throws] diff --git a/tun/tests/routing.rs b/tun/tests/routing.rs new file mode 100644 index 0000000..12c22b7 --- /dev/null +++ b/tun/tests/routing.rs @@ -0,0 +1,25 @@ +use fehler::throws; +use std::io::Error; +use std::net::{IpAddr, Ipv4Addr}; +use tun::routing::{Route, add_route}; +use tun::TunInterface; + +#[test] +#[throws] +fn test_create() -> std::io::Result<()> { + let tun = TunInterface::new()?; + let name = tun.name()?; + println!("Interface name: {name}"); + + let addr = Ipv4Addr::new(10, 0, 0, 1); + tun.set_ipv4_addr(addr)?; + + let dest = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + + let route = Route::new(dest, 24, &tun)?; + add_route(route)?; + + loop {} + + Ok(()) +}