diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a2af03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +Cargo.lock + +# swap +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags +# backup files from cargo fmt +*.bk diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..97c8267 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "wireguard" +version = "0.1.0" +authors = ["The WireGuard developers", "Sascha Grunert "] +build = "build.rs" + +[build-dependencies] +bindgen = "0" + +[dependencies] +futures = "0.1" +tokio-core = "0.1" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..f0a17fb --- /dev/null +++ b/build.rs @@ -0,0 +1,39 @@ +extern crate bindgen; + +use bindgen::Builder; + +use std::env; +use std::fs::File; +use std::io::Write; +use std::error::Error; +use std::path::PathBuf; + +static HEADERS: &[&str] = &["net/if.h", "linux/if_tun.h", "sys/ioctl.h"]; + +fn main() { + run().expect("Could not execute build script."); +} + +fn run() -> Result<(), Box> { + // Create a wrapper header file + let out_path = PathBuf::from(env::var("OUT_DIR")?); + let wrapper_path = out_path.join("wrapper.h"); + let mut wrapper = File::create(&wrapper_path)?; + for header in HEADERS { + writeln!(wrapper, "#include <{}>", header)?; + } + + // Generate the bindungs + let wrapper_path_str = wrapper_path.to_str().expect("Wrapper include path corrupt."); + let bindings = Builder::default() + .no_unstable_rust() + .generate_comments(true) + .hide_type("pthread_mutex_t") + .header(wrapper_path_str) + .generate() + .expect("Unable to generate bindings"); + + // Write the bindungs to the output directory + bindings.write_to_file(out_path.join("bindings.rs"))?; + Ok(()) +} diff --git a/src/bindgen.rs b/src/bindgen.rs new file mode 100644 index 0000000..212e4ac --- /dev/null +++ b/src/bindgen.rs @@ -0,0 +1,33 @@ +//! Bindgen source code +#![allow(non_upper_case_globals, non_camel_case_types, non_snake_case, dead_code)] + +pub const TUNSETIFF: u64 = (1 << 0 + 8 + 8 + 14) | (84 << 0 + 8) | (202 << 0) | (4 << 0 + 8 + 8); + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +impl ifreq { + /// Create a new `ifreq` + pub fn new() -> Self { + ifreq { + ifr_ifrn: ifreq__bindgen_ty_1 { + ifrn_name: __BindgenUnionField::new(), + bindgen_union_field: [0; IFNAMSIZ as usize], + }, + ifr_ifru: ifreq__bindgen_ty_2 { + ifru_addr: __BindgenUnionField::new(), + ifru_dstaddr: __BindgenUnionField::new(), + ifru_broadaddr: __BindgenUnionField::new(), + ifru_netmask: __BindgenUnionField::new(), + ifru_hwaddr: __BindgenUnionField::new(), + ifru_flags: __BindgenUnionField::new(), + ifru_ivalue: __BindgenUnionField::new(), + ifru_mtu: __BindgenUnionField::new(), + ifru_map: __BindgenUnionField::new(), + ifru_slave: __BindgenUnionField::new(), + ifru_newname: __BindgenUnionField::new(), + ifru_data: __BindgenUnionField::new(), + bindgen_union_field: [0u64; 3usize], + }, + } + } +} diff --git a/src/device.rs b/src/device.rs new file mode 100644 index 0000000..5b5dfff --- /dev/null +++ b/src/device.rs @@ -0,0 +1,81 @@ +//! Tunnel device handling +use std::path::PathBuf; +use std::io::{Read, Write}; +use std::fs::{File, OpenOptions}; +use std::os::unix::io::AsRawFd; + +use bindgen::*; +use error::WgResult; + +#[derive(Debug)] +/// A certain device +pub struct Device { + /// The interface name + pub name: String, + + /// The tunnel device file descriptor + pub fd: File, +} + +impl Device { + /// Create a new tunneling `Device` + pub fn new(name: &str) -> WgResult { + // Get a file descriptor to the operating system + let fd = OpenOptions::new().read(true).write(true).open("/dev/net/tun")?; + + // Get the default interface options + let mut ifr = ifreq::new(); + + { + // Set the interface name + let ifr_name = unsafe { ifr.ifr_ifrn.ifrn_name.as_mut() }; + for (index, character) in name.as_bytes().iter().enumerate() { + if index >= IFNAMSIZ as usize - 1 { + bail!("Interface name too long."); + } + ifr_name[index] = *character as i8; + } + + // Set the interface flags + let ifr_flags = unsafe { ifr.ifr_ifru.ifru_flags.as_mut() }; + *ifr_flags = (IFF_TUN | IFF_NO_PI) as i16; + } + + // Create the tunnel device + if unsafe { ioctl(fd.as_raw_fd(), TUNSETIFF, &ifr) < 0 } { + bail!("Device creation failed."); + } + + Ok(Device { + name: name.to_owned(), + fd: fd, + }) + } + + /// Create a dummy device for testing + pub fn dummy(name: &str) -> WgResult { + let fd = OpenOptions::new().read(true) + .write(true) + .create(true) + .open(PathBuf::from("/tmp").join(name))?; + Ok(Device { + name: name.to_owned(), + fd: fd, + }) + } + + /// Reads a frame from the device, returns the number of bytes read + pub fn read(&mut self, mut buffer: &mut [u8]) -> WgResult { + Ok(self.fd.read(&mut buffer)?) + } + + /// Write a frame to the device + pub fn write(&mut self, data: &[u8]) -> WgResult { + Ok(self.fd.write(data)?) + } + + /// Flush the device + pub fn flush(&mut self) -> WgResult<()> { + Ok(self.fd.flush()?) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..a6c9770 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,80 @@ +//! Everything related to error handling +use std::error::Error; +use std::{fmt, io, net, convert}; + +/// Common Tunnel Result type +pub type WgResult = Result; + +/// The global Error type for wiki +pub struct WgError { + /// A further description for the error + pub description: String, + + /// The cause for this error + pub cause: Option>, +} + +/// Representation of an error case +impl WgError { + /// Creates a new `WgError` + pub fn new(description: &str) -> Self { + WgError { + description: description.to_string(), + cause: None, + } + } + + /// Returns the corresponding `io::ErrorKind` for this error + pub fn kind(&self) -> io::ErrorKind { + io::ErrorKind::Other + } +} + +impl fmt::Display for WgError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description) + } +} + +impl fmt::Debug for WgError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl convert::From for io::Error { + fn from(tunnel_error: WgError) -> Self { + io::Error::new(io::ErrorKind::Other, tunnel_error.description) + } +} + +impl Error for WgError { + fn description(&self) -> &str { + &self.description + } +} + +macro_rules! from_error { + ($($p:ty,)*) => ( + $(impl From<$p> for WgError { + fn from(err: $p) -> Self { + WgError { + description: err.description().to_owned(), + cause: Some(Box::new(err)), + } + } + })* + ) +} + +from_error! { + io::Error, + net::AddrParseError, +} + +macro_rules! bail { + ($($fmt:tt)*) => ( + #[cfg_attr(feature = "cargo-clippy", allow(useless_format))] + return Err(::error::WgError::new(&format!($($fmt)*))) + ) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6efd5b2 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,88 @@ +//! The WireGuard implementation in Rust + +#[macro_use] +extern crate tokio_core; +extern crate futures; + +#[macro_use] +pub mod error; +pub mod device; +mod bindgen; + +use device::Device; +use error::WgResult; + +use std::io; +use std::net::SocketAddr; + +use futures::{Future, Poll}; +use tokio_core::net::UdpSocket; +use tokio_core::reactor::Handle; + +/// The main tunnel structure +pub struct Wireguard { + /// A tunneling device + device: Device, + + /// The VPN server socket + server: UdpSocket, + + /// An internal packet buffer + buffer: Vec, + + /// Things to send + to_send: Option<(usize, SocketAddr)>, +} + +impl Wireguard { + /// Creates a new `Wireguard` instance + pub fn new(handle: &Handle) -> WgResult { + // Create a tunneling device + let device = Device::dummy("wg")?; + + // Create a server for the tunnel + let addr = "127.0.0.1:8080".to_owned().parse()?; + let server = UdpSocket::bind(&addr, handle)?; + + Ok(Wireguard { + device: device, + server: server, + buffer: vec![0; 1500], + to_send: None, + }) + } +} + +impl Future for Wireguard { + type Item = (); + type Error = io::Error; + + fn poll(&mut self) -> Poll<(), io::Error> { + loop { + // Check if a message needs to be processed + if let Some((size, peer)) = self.to_send { + // Write the message to the tunnel device + let send_bytes = try_nb!(self.device.write(&self.buffer[..size])); + + // Set `to_send` to `None` if done + self.to_send = None; + println!("Wrote {}/{} bytes from {} to tunnel device", + send_bytes, + size, + peer); + + + // Read from the tunnel device and write to the client + // let read_bytes = try_nb!(self.device.read(&mut self.buffer)); + // try_nb!(self.server.send_to(&self.buffer[..read_bytes], &peer)); + // println!("Read {} bytes from tunnel device", read_bytes); + } + + // Flush the device file descriptor + try_nb!(self.device.flush()); + + // If `to_send` is `None`, we can receive the next message from the client + self.to_send = Some(try_nb!(self.server.recv_from(&mut self.buffer))); + } + } +} diff --git a/tests/lib.rs b/tests/lib.rs new file mode 100644 index 0000000..2bba4ed --- /dev/null +++ b/tests/lib.rs @@ -0,0 +1,16 @@ +extern crate wireguard; +extern crate tokio_core; + +use tokio_core::reactor::Core; +use wireguard::Wireguard; + +#[test] +fn server() { + // Setup tokio + let mut core = Core::new().unwrap(); + let handle = core.handle(); + + // Run the core with the tunnel + let tunnel = Wireguard::new(&handle).unwrap(); + core.run(tunnel).unwrap(); +}