Browse Source

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
sg/master
Sascha Grunert 9 years ago
parent
commit
4e391f657f
  1. 17
      .gitignore
  2. 12
      Cargo.toml
  3. 39
      build.rs
  4. 33
      src/bindgen.rs
  5. 81
      src/device.rs
  6. 80
      src/error.rs
  7. 88
      src/lib.rs
  8. 16
      tests/lib.rs

17
.gitignore vendored

@ -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

12
Cargo.toml

@ -0,0 +1,12 @@
[package]
name = "wireguard"
version = "0.1.0"
authors = ["The WireGuard developers", "Sascha Grunert <mail@saschagrunert.de>"]
build = "build.rs"
[build-dependencies]
bindgen = "0"
[dependencies]
futures = "0.1"
tokio-core = "0.1"

39
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<Error>> {
// 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(())
}

33
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],
},
}
}
}

81
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()?)
}
}

80
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)*)))
)
}

88
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)));
}
}
}

16
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();
}
Loading…
Cancel
Save