diff options
author | 2017-02-21 12:36:37 +0100 | |
---|---|---|
committer | 2017-02-21 12:36:37 +0100 | |
commit | 4e391f657f144d98cc47dc1c84ee8a8563a53fb4 (patch) | |
tree | 813d6b281f9cfe018a82d752fcce6a8e57202c4a /src | |
parent | Added README.md (diff) | |
download | wireguard-rs-4e391f657f144d98cc47dc1c84ee8a8563a53fb4.tar.xz wireguard-rs-4e391f657f144d98cc47dc1c84ee8a8563a53fb4.zip |
Added initial implementation
- Using tokio.rs for async I/O handling
- Using bindgen for ioctl and c structure handling
- Added basic device handling with dummy support
- Added error handling
- Added simple unit test for testing the server
Diffstat (limited to 'src')
-rw-r--r-- | src/bindgen.rs | 33 | ||||
-rw-r--r-- | src/device.rs | 81 | ||||
-rw-r--r-- | src/error.rs | 80 | ||||
-rw-r--r-- | src/lib.rs | 88 |
4 files changed, 282 insertions, 0 deletions
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<Self> { + // 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<Self> { + 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<usize> { + Ok(self.fd.read(&mut buffer)?) + } + + /// Write a frame to the device + pub fn write(&mut self, data: &[u8]) -> WgResult<usize> { + 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<T> = Result<T, WgError>; + +/// 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<Box<Error>>, +} + +/// 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<WgError> 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<u8>, + + /// Things to send + to_send: Option<(usize, SocketAddr)>, +} + +impl Wireguard { + /// Creates a new `Wireguard` instance + pub fn new(handle: &Handle) -> WgResult<Self> { + // 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))); + } + } +} |