diff options
author | Jake McGinty <me@jake.su> | 2017-12-22 13:59:24 -0600 |
---|---|---|
committer | Jake McGinty <me@jake.su> | 2017-12-22 14:00:06 -0600 |
commit | e6aadd208877f7b187046d0656f72dd39501a0a1 (patch) | |
tree | d27e80246ad503615909eda752281f26db5ff457 /src | |
download | wireguard-rs-e6aadd208877f7b187046d0656f72dd39501a0a1.tar.xz wireguard-rs-e6aadd208877f7b187046d0656f72dd39501a0a1.zip |
early, early rough draft
Diffstat (limited to 'src')
-rw-r--r-- | src/error.rs | 27 | ||||
-rw-r--r-- | src/interface/config.rs | 300 | ||||
-rw-r--r-- | src/interface/mod.rs | 317 | ||||
-rw-r--r-- | src/main.rs | 100 | ||||
-rw-r--r-- | src/protocol/mod.rs | 3 | ||||
-rw-r--r-- | src/protocol/peer.rs | 189 | ||||
-rw-r--r-- | src/types.rs | 107 |
7 files changed, 1043 insertions, 0 deletions
diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..4492444 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,27 @@ +use daemonize; +use std::io; + + error_chain! { + foreign_links { + Io(io::Error) #[doc = "Error during IO"]; + Daemonize(daemonize::DaemonizeError) #[doc = "Error during IO"]; + } + + errors { +// Launch(phase: LaunchStage) { +// description("An error occurred during startup") +// display("Startup aborted: {:?} did not complete successfully", phase) +// } +// +// ConfigLoad(path: String) { +// description("Config file not found") +// display("Unable to read file `{}`", path) +// } + } + } + +// impl From<LaunchStage> for ErrorKind { +// fn from(v: LaunchStage) -> Self { +// ErrorKind::Launch(v) +// } +// } diff --git a/src/interface/config.rs b/src/interface/config.rs new file mode 100644 index 0000000..6a76a23 --- /dev/null +++ b/src/interface/config.rs @@ -0,0 +1,300 @@ +//! The configuration logic for userspace WireGuard. + +// Dev notes: +// * Configuration service should use channels to report updates it receives over its interface. + +use bytes::BytesMut; +use error::Result; +use std; +use std::fs::{create_dir, remove_file}; +use std::iter::Iterator; +use std::path::{Path, PathBuf}; +use std::io; +use std::str; +use std::net::{SocketAddr, IpAddr}; +use types::{PeerInfo, InterfaceInfo}; +use hex::{FromHex}; + +use futures::{Future, Stream}; +use futures::unsync::mpsc; +use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_io::codec::{Encoder, Decoder}; + +#[derive(Debug)] +pub enum Command { + Set(usize, Vec<UpdateEvent>), + Get(usize) +} + +#[derive(Debug)] +#[allow(dead_code)] +pub enum UpdateEvent { + PrivateKey([u8; 32]), + ListenPort(u16), + UpdatePeer(PeerInfo), + RemovePeer([u8; 32]), + RemoveAllPeers, +} + +impl UpdateEvent { + fn from(items: Vec<(String, String)>) -> Vec<UpdateEvent> { + let mut events = vec![]; + let mut public_key: Option<[u8; 32]> = None; + let mut preshared_key: Option<[u8; 32]> = None; + let mut allowed_ips: Vec<(IpAddr, u32)> = vec![]; + let mut keep_alive_interval: Option<u16> = None; + let mut endpoint: Option<SocketAddr> = None; + + for (key, value) in items { + match key.as_ref() { + "private_key" => { + let key = <[u8; 32]>::from_hex(&value).unwrap(); + events.push(UpdateEvent::PrivateKey(key)); + }, + "listen_port" => { events.push(UpdateEvent::ListenPort(value.parse().unwrap())); }, + "public_key" => { + if let Some(ref pubkey) = public_key { + events.push(UpdateEvent::UpdatePeer(PeerInfo { + pub_key: pubkey.clone(), + psk: preshared_key.clone(), + endpoint: endpoint.clone(), + allowed_ips: allowed_ips.clone(), + keep_alive_interval: keep_alive_interval.clone(), + })); + } + let key = <[u8; 32]>::from_hex(&value).unwrap(); + public_key = Some(key); + }, + "preshared_key" => { preshared_key = Some(<[u8; 32]>::from_hex(&value).unwrap()); }, + "allowed_ip" => { + let (ip, cidr) = value.split_at(value.find('/').unwrap()); + allowed_ips.push((ip.parse().unwrap(), (&cidr[1..]).parse().unwrap())) + }, + "persistent_keepalive_interval" => { + keep_alive_interval = Some(value.parse().unwrap()); + }, + "endpoint" => { endpoint = Some(value.parse().unwrap()); }, + _ => {} + } + } + + if let Some(ref pubkey) = public_key { + events.push(UpdateEvent::UpdatePeer(PeerInfo { + pub_key: pubkey.clone(), + psk: preshared_key.clone(), + endpoint: endpoint.clone(), + allowed_ips: allowed_ips.clone(), + keep_alive_interval: keep_alive_interval.clone(), + })); + } + debug!("events {:?}", events); + events + } +} + +pub struct ConfigurationCodec; + +impl Decoder for ConfigurationCodec { + type Item = Command; + type Error = io::Error; + + fn decode(&mut self, buf: &mut BytesMut) -> std::result::Result<Option<Self::Item>, Self::Error> { + // Determine we have a full command ready for parsing. + let mut items = Vec::new(); + let utf8 = String::from_utf8(buf.to_vec()).unwrap(); + let mut data_iter = utf8.split("\n\n"); + let blob = data_iter.next().unwrap(); + if data_iter.next().is_none() { + return Ok(None) + } + + // Parse the key-value pairs into something more usable + for line in blob.split('\n') { + let mut entry = line.split('='); + items.push((entry.next().unwrap().to_owned(), entry.next().unwrap().to_owned())); + } + buf.split_to(blob.len()+1); + + let (ref cmd, ref version) = items.remove(0); + let command = if cmd == "get" { + Command::Get(version.parse().unwrap()) + } else { + Command::Set(version.parse().unwrap(), UpdateEvent::from(items)) + }; + + Ok(Some(command)) + } +} + +impl Encoder for ConfigurationCodec { + type Item = String; + type Error = io::Error; + + fn encode(&mut self, msg: Self::Item, buf: &mut BytesMut) -> std::result::Result<(), Self::Error> { + buf.extend(msg.as_bytes()); + buf.extend(b"\n\n"); + Ok(()) + } +} + + +//pub struct ConfigurationService { +// interface_name: String, +// peers: Rc<RefCell<HashMap<[u8; 32], Rc<RefCell<Peer>>>>>, +// interface_info: Rc<RefCell<InterfaceInfo>>, +// tx: mpsc::Sender<UpdateEvent>, +//} + +//impl Service for ConfigurationService { +// type Request = Command; +// type Response = String; +// type Error = io::Error; +// type Future = Box<Future<Item=Self::Response, Error=Self::Error>>; +// +// fn call(&self, req: Self::Request) -> Self::Future { +// debug!("{:?}", req); +// match req { +// Command::Get(version) => { +// // see: https://www.wireguard.com/xplatform/ +// // this is just bullshit fillin +// let buf = "private_key=e84b5a6d2717c1003a13b431570353dbaca9146cf150c5f8575680feba52027a +//listen_port=12912 +//public_key=b85996fecc9c7f1fc6d2572a76eda11d59bcd20be8e543b15ce4bd85a8e75a33 +//preshared_key=188515093e952f5f22e865cef3012e72f8b5f0b598ac0309d5dacce3b70fcf52 +//allowed_ip=192.168.4.4/32 +//endpoint=[abcd:23::33%2]:51820 +//public_key=58402e695ba1772b1cc9309755f043251ea77fdcf10fbe63989ceb7e19321376 +//tx_bytes=38333 +//rx_bytes=2224 +//allowed_ip=192.168.4.6/32 +//persistent_keepalive_interval=111 +//endpoint=182.122.22.19:3233 +//public_key=662e14fd594556f522604703340351258903b64f35553763f19426ab2a515c58 +//endpoint=5.152.198.39:51820 +//allowed_ip=192.168.4.10/32 +//allowed_ip=192.168.4.11/32 +//tx_bytes=1212111 +//rx_bytes=1929999999 +//errno=0 +//\n"; +// Box::new(future::ok(buf.into())) +// }, +// Command::Set(version, items) => { +// let mut public_key = None; +// let mut preshared_key = None; +// let mut allowed_ips: Vec<(IpAddr, u32)> = vec![]; +// let mut persistent_keepalive_interval: Option<u16> = None; +// let mut endpoint: Option<SocketAddr> = None; +// +// for (key, value) in items { +// match key.as_ref() { +//// "private_key" => { config.key = Some(value); }, +//// "fwmark" => { config.fwmark = Some(value.parse().unwrap()); }, +//// "listen_port" => { config.listen_port = Some(value.parse().unwrap()); }, +// "public_key" => { +// if let Some(ref pubkey) = public_key { +//// config.peers.push(Peer { +//// peer_pubkey: [0u8; 32], +//// psk: preshared_key, +//// endpoint: endpoint, +//// allowed_ips: allowed_ips.clone(), +//// keep_alive_interval: persistent_keepalive_interval, +//// }); +// } +// public_key = Some(value); +// }, +// "preshared_key" => { preshared_key = Some([0u8; 32]); }, +// "allowed_ip" => { +// let (ip, cidr) = value.split_at(value.find('/').unwrap()); +// debug!("parsed allowed ip as ({}, {})", ip, &cidr[1..]); +// allowed_ips.push((ip.parse().unwrap(), (&cidr[1..]).parse().unwrap())) +// }, +// "persistent_keepalive_interval" => { +// debug!("persistent_keepalive_interval"); +// persistent_keepalive_interval = Some(value.parse().unwrap()); +// }, +// "endpoint" => { endpoint = Some(value.parse().unwrap()); }, +// _ => {} +// } +// } +// Box::new(future::ok("errno=0\nerrno=0\n\n".into())) +// }, +// _ => { +// Box::new(future::ok("errno=1\nerrno=1\n\n".into())) +// } +// } +// } +//} + +pub struct ConfigurationServiceManager { + interface_name: String, +} + +impl ConfigurationServiceManager { + pub fn new(interface_name: &str) -> Self { + ConfigurationServiceManager { + interface_name: interface_name.into(), + } + } + + /// Creates a new `WireGuard` instance + pub fn get_path(name: &str) -> Result<PathBuf> { + // let _tun = Tun::create(Some("hey")); + // Create the socket directory if not existing + let mut socket_path = Self::get_run_path().join("wireguard"); + + if !socket_path.exists() { + debug!("Creating socket path: {}", socket_path.display()); + create_dir(&socket_path)?; + } + debug!("Setting chmod 0700 of socket path: {}", + socket_path.display()); + Self::chmod(&socket_path, 0o700)?; + + // Finish the socket path + socket_path.push(&name); + socket_path.set_extension("sock"); + if socket_path.exists() { + debug!("Removing existing socket: {}", socket_path.display()); + remove_file(&socket_path)?; + } + + Ok(socket_path) + } + + #[cfg(unix)] + /// Sets the permissions to a given `Path` + fn chmod(path: &Path, perms: u32) -> Result<()> { + use std::os::unix::prelude::PermissionsExt; + use std::fs::{set_permissions, Permissions}; + set_permissions(path, Permissions::from_mode(perms))?; + Ok(()) + } + + #[cfg(windows)] + /// Sets the permissions to a given `Path` + fn chmod(_path: &Path, _perms: u32) -> Result<()> { + Ok(()) + } + + /// Returns the path where the socket and pid file will be stored + pub fn get_run_path() -> PathBuf { + if Path::new("/run").exists() { + PathBuf::from("/run") + } else { + PathBuf::from("/var").join("run") + } + } +} + +impl Drop for ConfigurationServiceManager { + fn drop(&mut self) { + let mut socket_path = Self::get_run_path().join("wireguard"); + socket_path.push(&self.interface_name); + socket_path.set_extension("sock"); + if socket_path.exists() { + debug!("Removing socket on drop: {}", socket_path.display()); + let _ = remove_file(&socket_path); + } + } +} diff --git a/src/interface/mod.rs b/src/interface/mod.rs new file mode 100644 index 0000000..8cbbfb3 --- /dev/null +++ b/src/interface/mod.rs @@ -0,0 +1,317 @@ +mod config; + +use self::config::{ConfigurationServiceManager, UpdateEvent, Command, ConfigurationCodec}; +use base64; +use hex; +use byteorder::{ByteOrder, BigEndian, LittleEndian}; +use snow::NoiseBuilder; +use protocol::Peer; +use std::io; +use std::rc::Rc; +use std::cell::RefCell; +use std::collections::HashMap; +use std::net::SocketAddr; +use std::time::Duration; +use types::{InterfaceInfo}; + +use pnet::packet::ipv4::Ipv4Packet; + +use futures::{Future, Stream, Sink, future, unsync, sync, stream}; +use tokio_core::reactor::{Core, Handle}; +use tokio_core::net::{UdpSocket, UdpCodec}; +use tokio_utun::{UtunStream, UtunCodec}; +use tokio_io::{AsyncRead}; +use tokio_io::codec::{Framed, Encoder, Decoder}; +use tokio_uds::{UnixListener}; +use tokio_timer::{Interval, Timer}; + +fn debug_packet(header: &str, packet: &[u8]) { + let packet = Ipv4Packet::new(packet); + debug!("{} {:?}", header, packet); +} + +pub struct Interface { + name: String, + info: Rc<RefCell<InterfaceInfo>>, + peers: Rc<RefCell<HashMap<[u8; 32], Rc<RefCell<Peer>>>>>, + ids: Rc<RefCell<HashMap<u32, Rc<RefCell<Peer>>>>>, +} + +struct VecUdpCodec; +impl UdpCodec for VecUdpCodec { + type In = (SocketAddr, Vec<u8>); + type Out = (SocketAddr, Vec<u8>); + + fn decode(&mut self, src: &SocketAddr, buf: &[u8]) -> io::Result<Self::In> { + Ok((*src, buf.to_vec())) + } + + fn encode(&mut self, msg: Self::Out, buf: &mut Vec<u8>) -> SocketAddr { + let (addr, mut data) = msg; + buf.append(&mut data); + addr + } +} + +struct VecUtunCodec; +#[allow(dead_code)] +enum UtunPacket { + Inet4(Vec<u8>), + Inet6(Vec<u8>), +} +impl UtunCodec for VecUtunCodec { + type In = Vec<u8>; + type Out = Vec<u8>; + + fn decode(&mut self, buf: &[u8]) -> io::Result<Self::In> { + debug!("utun packet type {}", buf[3]); + Ok(buf[4..].to_vec()) + } + + fn encode(&mut self, mut msg: Self::Out, buf: &mut Vec<u8>) { + buf.extend_from_slice(&[0u8, 0, 0, 2]); + buf.append(&mut msg); + } +} + +impl Interface { + pub fn new(name: &str) -> Self { + let info = Rc::new(RefCell::new(InterfaceInfo::default())); + let peers = Rc::new(RefCell::new(HashMap::new())); + let ids = Rc::new(RefCell::new(HashMap::new())); + let _config_service = ConfigurationServiceManager::new(name); + Interface { + name: name.to_owned(), + info, + peers, + ids, + } + } + + pub fn start(&mut self) { + let mut core = Core::new().unwrap(); + + let (utun_tx, utun_rx) = unsync::mpsc::channel::<Vec<u8>>(1024); + let udp_socket = UdpSocket::bind(&([0,0,0,0], 0).into(), &core.handle()).unwrap(); + let (tx, rx) = unsync::mpsc::channel::<(SocketAddr, Vec<u8>)>(1024); + let (udp_writer, udp_reader) = udp_socket.framed(VecUdpCodec{}).split(); + let udp_read_fut = udp_reader.for_each({ + let ids_ref = self.ids.clone(); + let handle = core.handle(); + let tx = tx.clone(); + let interface_info = self.info.clone(); + move |(_socket_addr, packet)| { + debug!("got a UDP packet of length {}, packet type {}", packet.len(), packet[0]); + match packet[0] { + 1 => { + info!("got handshake initialization."); + }, + 2 => { + let their_index = LittleEndian::read_u32(&packet[4..]); + let our_index = LittleEndian::read_u32(&packet[8..]); + let mut ids = ids_ref.borrow_mut(); + let peer_ref = ids.get(&our_index).unwrap().clone(); + let mut peer = peer_ref.borrow_mut(); + peer.sessions.next.as_mut().unwrap().their_index = their_index; + let payload_len = peer.next_noise().expect("pending noise session") + .read_message(&packet[12..60], &mut []).unwrap(); + assert!(payload_len == 0); + peer.ratchet_session().unwrap(); + info!("got handshake response, ratcheted session."); + let tx = tx.clone(); + + let interface_info = interface_info.borrow(); + let noise = NoiseBuilder::new("Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s".parse().unwrap()) + .local_private_key(&interface_info.private_key.expect("no private key!")) + .remote_public_key(&peer.info.pub_key) + .prologue("WireGuard v1 zx2c4 Jason@zx2c4.com".as_bytes()) + .psk(2, &peer.info.psk.expect("no psk!")) + .build_initiator().unwrap(); + peer.set_next_session(noise.into()); + + let _ = ids.insert(peer.our_next_index().unwrap(), peer_ref.clone()); + + let init_packet = peer.get_handshake_packet(); + let endpoint = peer.info.endpoint.unwrap().clone(); + + let timer = Timer::default(); + let sleep = timer.sleep(Duration::from_secs(120)); + let boop = sleep.and_then({ + let handle = handle.clone(); + let peer_ref = peer_ref.clone(); + let interface_info = interface_info.clone(); + move |_| { + info!("sending rekey!"); + handle.spawn(tx.clone().send((endpoint, init_packet)) + .map(|_| ()) + .map_err(|_| ())); + Ok(()) + } + }).map_err(|_|()); + handle.spawn(boop); + }, + 4 => { + let our_index_received = LittleEndian::read_u32(&packet[4..]); + let nonce = LittleEndian::read_u64(&packet[8..]); + + let mut raw_packet = [0u8; 1500]; + let ids = ids_ref.borrow(); + let lookup = ids.get(&our_index_received); + if let Some(ref peer) = lookup { + let mut peer = peer.borrow_mut(); + // info!("retrieved peer with pubkey {}", base64::encode(&peer.pubkey)); + // info!("ok going to try to decrypt"); + + peer.rx_bytes += packet.len(); + let noise = peer.current_noise().expect("current noise session"); + noise.set_receiving_nonce(nonce).unwrap(); + let payload_len = noise.read_message(&packet[16..], &mut raw_packet).unwrap(); + debug_packet("received TRANSPORT: ", &raw_packet[..payload_len]); + handle.spawn(utun_tx.clone().send(raw_packet[..payload_len].to_owned()) + .map(|_| ()) + .map_err(|_| ())); + } + }, + _ => unimplemented!() + } + Ok(()) + } + }).map_err(|_| ()); + + let udp_write_fut = udp_writer.sink_map_err(|_| ()).send_all( + rx.map(|(addr, packet)| { + debug!("sending encrypted UDP packet"); + (addr, packet) + }).map_err(|_| ())).map_err(|_| ()); + + let utun_stream = UtunStream::connect(&self.name, &core.handle()).unwrap().framed(VecUtunCodec{}); + let (utun_writer, utun_reader) = utun_stream.split(); + let utun_fut = utun_reader.for_each({ + let ids = self.ids.clone(); + let utun_handle = core.handle(); + let udp_tx = tx.clone(); + move |packet| { + debug_packet("received UTUN packet: ", &packet); + let mut ping_packet = [0u8; 1500]; + let ids = ids.borrow(); + let (_key, peer) = ids.iter().next().unwrap(); // TODO destination IP peer lookup + let mut peer = peer.borrow_mut(); + ping_packet[0] = 4; + let their_index = peer.their_current_index().expect("no current index for them"); + let endpoint = peer.info.endpoint.unwrap(); + peer.tx_bytes += packet.len(); + let noise = peer.current_noise().expect("current noise session"); + LittleEndian::write_u32(&mut ping_packet[4..], their_index); + LittleEndian::write_u64(&mut ping_packet[8..], noise.sending_nonce().unwrap()); + let len = noise.write_message(&packet, &mut ping_packet[16..]).expect("failed to encrypt outgoing UDP packet"); + utun_handle.spawn(udp_tx.clone().send((endpoint, ping_packet[..(16+len)].to_owned())) + .map(|_| ()) + .map_err(|_| ())); + Ok(()) + } + }).map_err(|_| ()); + + let utun_write_fut = utun_writer.sink_map_err(|_| ()).send_all( + utun_rx.map(|packet| { + debug_packet("sending UTUN: ", &packet); + packet + }).map_err(|_| ())).map_err(|_| ()); + + let handle = core.handle(); + let listener = UnixListener::bind(ConfigurationServiceManager::get_path(&self.name).unwrap(), &handle).unwrap(); + let (config_tx, config_rx) = sync::mpsc::channel::<UpdateEvent>(1024); + let h = handle.clone(); + let config_server = listener.incoming().for_each({ + let config_tx = config_tx.clone(); + let info = self.info.clone(); + let peers = self.peers.clone(); + move |(stream, _)| { + let (sink, stream) = stream.framed(ConfigurationCodec {}).split(); + debug!("UnixServer connection."); + + let handle = h.clone(); + let responses = stream.and_then({ + let config_tx = config_tx.clone(); + let info = info.clone(); + let peers = peers.clone(); + move |command| { + match command { + Command::Set(_version, items) => { + config_tx.clone().send_all(stream::iter_ok(items)).wait().unwrap(); + future::ok("errno=0\nerrno=0\n\n".to_string()) + }, + Command::Get(_version) => { + let info = info.borrow(); + let peers = peers.borrow(); + let mut s = String::new(); + if let Some(private_key) = info.private_key { + s.push_str(&format!("private_key={}\n", hex::encode(private_key))); + } + + for (_, peer) in peers.iter() { + s.push_str(&peer.borrow().to_config_string()); + } + future::ok(format!("{}errno=0\n\n", s)) + } + } + } + }); + + let fut = sink.send_all(responses).map(|_| ()).map_err(|_| ()); + + handle.spawn(fut); + + Ok(()) + } + }).map_err(|_| ()); + + let config_fut = config_rx.for_each({ + let tx = tx.clone(); + let handle = handle.clone(); + move |event| { + let interface_info = self.info.clone(); + match event { + UpdateEvent::PrivateKey(private_key) => { + let mut interface_info = interface_info.borrow_mut(); + interface_info.private_key = Some(private_key); + debug!("set new private key"); + }, + UpdateEvent::ListenPort(port) => { + let mut interface_info = interface_info.borrow_mut(); + interface_info.listen_port = Some(port); + debug!("set new listen port"); + }, + UpdateEvent::UpdatePeer(info) => { + info!("added new peer: {}", info); + let interface_info = interface_info.borrow(); + let mut noise = NoiseBuilder::new("Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s".parse().unwrap()) + .local_private_key(&interface_info.private_key.expect("no private key!")) + .remote_public_key(&info.pub_key) + .prologue("WireGuard v1 zx2c4 Jason@zx2c4.com".as_bytes()) + .psk(2, &info.psk.expect("no psk!")) + .build_initiator().unwrap(); + + let mut peer = Peer::new(info.clone()); + peer.set_next_session(noise.into()); + + let init_packet = peer.get_handshake_packet(); + let our_index = peer.our_next_index().unwrap(); + let peer = Rc::new(RefCell::new(peer)); + + let _ = self.ids.borrow_mut().insert(our_index, peer.clone()); + let _ = self.peers.borrow_mut().insert(info.pub_key, peer); + + handle.spawn(tx.clone().send((info.endpoint.unwrap(), init_packet)) + .map(|_| ()) + .map_err(|_| ())); + }, + _ => unimplemented!() + } + + future::ok(()) + } + }).map_err(|_| ()); + + core.run(utun_fut.join(utun_write_fut.join(udp_read_fut.join(udp_write_fut.join(config_fut.join(config_server)))))).unwrap(); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0f3f9e4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,100 @@ +#![allow(unused_imports)] + +#[macro_use] extern crate log; +extern crate env_logger; + +#[macro_use] extern crate structopt_derive; +#[macro_use] extern crate error_chain; + + +extern crate daemonize; +extern crate rand; +extern crate nix; +extern crate structopt; + +extern crate mio; + +extern crate bytes; +extern crate futures; +extern crate tokio_core; +extern crate tokio_io; +extern crate tokio_proto; +extern crate tokio_service; +extern crate tokio_uds; +extern crate tokio_utun; +extern crate tokio_timer; + +extern crate snow; +extern crate base64; +extern crate hex; +extern crate time; +extern crate byteorder; +extern crate crypto; +extern crate pnet; + +mod error; +mod interface; +mod protocol; +mod types; + +use daemonize::Daemonize; +use error::{ErrorKind, Error, Result}; +use std::path::PathBuf; +use interface::Interface; +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "wgrs", about = "WireGuard - a network tunnel")] +struct Opt { + /// A flag, true if used in the command line. + #[structopt(short = "d", long = "debug", help = "Activate debug mode")] + debug: bool, + + /// An argument of type float, with a default value. + #[structopt(short = "f", long = "foreground", help = "Run in the foreground")] + foreground: bool, + + /// Needed parameter, the first on the command line. + #[structopt(help = "WireGuard interface name", default_value = "utun4")] + interface: String, + + /// An optional parameter, will be `None` if not present on the + /// command line. + #[structopt(help = "Output file, stdout if not present")] + output: Option<String>, +} + +fn main() { + env_logger::init().unwrap(); + let opt = Opt::from_args(); + + if !opt.foreground { + daemonize().expect("failed to daemonize"); + } + + Interface::new(&opt.interface).start(); +// WireGuard::start(interface_name).expect("failed to start WireGuard interface"); +} + +fn daemonize() -> Result<()> { + if !nix::unistd::getuid().is_root() { + bail!("You are not the root user which can spawn the daemon."); + } + + debug!("Starting daemon."); + + let pid_path = PathBuf::new(); // TODO temporary + +// let pid_path = WireGuard::get_run_path(); + + let daemonize = Daemonize::new() + .pid_file(pid_path.join("wireguard.pid")) + .chown_pid_file(true) + .working_directory(pid_path) + .user("nobody") + .group("daemon") + .umask(0o077); + + daemonize.start()?; + Ok(()) +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs new file mode 100644 index 0000000..bae5340 --- /dev/null +++ b/src/protocol/mod.rs @@ -0,0 +1,3 @@ +mod peer; + +pub use self::peer::Peer; diff --git a/src/protocol/peer.rs b/src/protocol/peer.rs new file mode 100644 index 0000000..e26e1bf --- /dev/null +++ b/src/protocol/peer.rs @@ -0,0 +1,189 @@ +use byteorder::{ByteOrder, BigEndian, LittleEndian}; +use crypto::blake2s::Blake2s; +use snow::{self, NoiseBuilder}; +use pnet::packet::Packet; +use pnet::packet::ip::IpNextHeaderProtocols; +use pnet::packet::ipv4::{self, MutableIpv4Packet}; +use pnet::packet::icmp::{self, MutableIcmpPacket, IcmpTypes, echo_reply, echo_request}; +use std::{self, io}; +use std::fmt::{self, Debug, Display, Formatter}; +use std::net::{Ipv4Addr, IpAddr, SocketAddr, ToSocketAddrs}; +use std::str::FromStr; +use std::thread::JoinHandle; +use base64; +use hex; +use time; +use rand::{self, Rng}; +use types::PeerInfo; + +use futures::{self, Future}; +use tokio_core::reactor::Handle; +use tokio_core::net::{UdpSocket, UdpCodec}; + +pub struct Peer { + pub info: PeerInfo, + pub sessions: Sessions, + pub tx_bytes: usize, + pub rx_bytes: usize, +} + +pub struct Session { + pub noise: snow::Session, + pub our_index: u32, + pub their_index: u32, +} + +impl Session { + #[allow(dead_code)] + pub fn with_their_index(session: snow::Session, their_index: u32) -> Session { + Session { + noise: session, + our_index: rand::thread_rng().gen::<u32>(), + their_index, + } + } + + pub fn into_transport_mode(self) -> Session { + Session { + noise: self.noise.into_transport_mode().unwrap(), + our_index: self.our_index, + their_index: self.their_index, + } + } +} + +impl From<snow::Session> for Session { + fn from(session: snow::Session) -> Self { + Session { + noise: session, + our_index: 0, + their_index: 0, + } + } +} + +pub struct Sessions { + pub past: Option<Session>, + pub current: Option<Session>, + pub next: Option<Session>, +} + +impl Display for Peer { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Peer({})", self.info) + } +} + +impl Debug for Peer { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Peer( endpoint: {:?}, pubkey: [redacted], psk: [redacted] )", self.info.endpoint) + } +} + +fn memcpy(out: &mut [u8], data: &[u8]) { + out[..data.len()].copy_from_slice(data); +} + +impl Peer { + pub fn new(info: PeerInfo) -> Peer { + Peer { + info, + sessions: Sessions { + past: None, + current: None, + next: None + }, + tx_bytes: 0, + rx_bytes: 0, + } + } + + pub fn set_next_session(&mut self, session: Session) { + let _ = std::mem::replace(&mut self.sessions.next, Some(session)); + } + + pub fn ratchet_session(&mut self) -> Result<(), ()> { + let next = std::mem::replace(&mut self.sessions.next, None).ok_or(())?; + let next = next.into_transport_mode(); + + let current = std::mem::replace(&mut self.sessions.current, Some(next)); + let _ = std::mem::replace(&mut self.sessions.past, current); + Ok(()) + } + + pub fn current_noise(&mut self) -> Option<&mut snow::Session> { + if let Some(ref mut session) = self.sessions.current { + Some(&mut session.noise) + } else { + None + } + } + + pub fn next_noise(&mut self) -> Option<&mut snow::Session> { + if let Some(ref mut session) = self.sessions.next { + Some(&mut session.noise) + } else { + None + } + } + + pub fn our_next_index(&self) -> Option<u32> { + if let Some(ref session) = self.sessions.next { + Some(session.our_index) + } else { + None + } + } + + pub fn our_current_index(&self) -> Option<u32> { + if let Some(ref session) = self.sessions.current { + Some(session.our_index) + } else { + None + } + } + + pub fn their_current_index(&self) -> Option<u32> { + if let Some(ref session) = self.sessions.current { + Some(session.their_index) + } else { + None + } + } + + pub fn get_handshake_packet(&mut self) -> Vec<u8> { + let now = time::get_time(); + let mut tai64n = [0; 12]; + BigEndian::write_i64(&mut tai64n[0..], 4611686018427387914 + now.sec); + BigEndian::write_i32(&mut tai64n[8..], now.nsec); + let mut initiation_packet = vec![0; 148]; + initiation_packet[0] = 1; /* Type: Initiation */ + initiation_packet[1] = 0; /* Reserved */ + initiation_packet[2] = 0; /* Reserved */ + initiation_packet[3] = 0; /* Reserved */ + LittleEndian::write_u32(&mut initiation_packet[4..], self.our_next_index().unwrap()); + self.sessions.next.as_mut().unwrap().noise.write_message(&tai64n, &mut initiation_packet[8..]).unwrap(); + let mut mac_key_input = [0; 40]; + let mut mac_key = [0; 32]; + memcpy(&mut mac_key_input, b"mac1----"); + memcpy(&mut mac_key_input[8..], &self.info.pub_key); + Blake2s::blake2s(&mut mac_key, &mac_key_input, &[0; 0]); + let mut mac = [0; 16]; + Blake2s::blake2s(&mut mac, &initiation_packet[0..116], &mac_key); + memcpy(&mut initiation_packet[116..], &mac); + + initiation_packet + } + + pub fn to_config_string(&self) -> String { + let mut s = format!("public_key={}\n", hex::encode(&self.info.pub_key)); + if let Some(ref psk) = self.info.psk { + s.push_str(&format!("preshared_key={}\n", hex::encode(psk))); + } + if let Some(ref endpoint) = self.info.endpoint { + s.push_str(&format!("endpoint={}:{}\n", endpoint.ip().to_string(),endpoint.port())); + } + s.push_str(&format!("tx_bytes={}\nrx_bytes={}\n", self.tx_bytes, self.rx_bytes)); + s + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..29b02bb --- /dev/null +++ b/src/types.rs @@ -0,0 +1,107 @@ +use base64; +use std::io::{self, Cursor, Read, Write}; +use byteorder::{BigEndian, LittleEndian, WriteBytesExt, ReadBytesExt}; +use std::fmt::{self, Display, Formatter}; +use std::net::{Ipv4Addr, IpAddr, SocketAddr}; + +#[derive(Clone, Debug)] +pub struct PeerInfo { + pub pub_key: [u8; 32], + pub psk: Option<[u8; 32]>, + pub endpoint: Option<SocketAddr>, + pub allowed_ips: Vec<(IpAddr, u32)>, + pub keep_alive_interval: Option<u16>, +} + +impl Display for PeerInfo { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let encoded = base64::encode(&self.pub_key); + write!(f, "{}...{}", &encoded[..4], &encoded[encoded.len()-4..]) + } +} + +#[derive(Clone, Debug, Default)] +pub struct InterfaceInfo { + pub private_key: Option<[u8; 32]>, + pub pub_key: Option<[u8; 32]>, + pub listen_port: Option<u16>, +} + +pub enum Message { + HandshakeInitiation(HandshakeInitiationMessage), +// HandshakeResponse(HandshakeResponseMessage), +// Transport(TransportMessage), +// CookieReply(CookieReplyMessage), + Other(Vec<u8>) +} + +// TODO use TryFrom +impl<'a> From<&'a [u8]> for Message { + fn from(bytes: &'a [u8]) -> Self { + use self::Message::*; + let mut cursor = Cursor::new(bytes); + match cursor.read_u8().unwrap() { + 1 => HandshakeInitiation(HandshakeInitiationMessage::from(&bytes[4..])), + _ => Other((&bytes[4..]).to_owned()) + } + } +} + +impl From<Message> for Vec<u8> { + fn from(message: Message) -> Self { + use self::Message::*; + match message { + HandshakeInitiation(message) => { + let mut bytes = vec![1u8, 0, 0, 0]; + bytes.append(&mut message.into()); + bytes + }, + _ => unimplemented!() + } + } +} + +pub struct HandshakeInitiationMessage { + pub sender_i: u32, + pub payload: [u8; 76], + pub mac1: [u8; 16], + pub mac2: [u8; 16] +} + +impl HandshakeInitiationMessage { + pub fn new() -> Self { + HandshakeInitiationMessage { + sender_i: 0, + payload: [0u8; 76], + mac1: [0u8; 16], + mac2: [0u8; 16], + } + } +} + +impl<'a> From<&'a [u8]> for HandshakeInitiationMessage { + + fn from(bytes: &'a [u8]) -> Self { + let mut message = HandshakeInitiationMessage::new(); + let mut cursor = Cursor::new(bytes); + + message.sender_i = cursor.read_u32::<LittleEndian>().unwrap(); + cursor.read_exact(&mut message.payload[..]).unwrap(); + cursor.read_exact(&mut message.mac1[..]).unwrap(); + cursor.read_exact(&mut message.mac2[..]).unwrap(); + message + } +} + +impl From<HandshakeInitiationMessage> for Vec<u8> { + fn from(message: HandshakeInitiationMessage) -> Self { + let mut cursor = vec![]; + cursor.write_all(&[1u8, 0, 0, 0]).unwrap(); + cursor.write_u32::<LittleEndian>(message.sender_i).unwrap(); + cursor.write_all(&message.payload).unwrap(); + cursor.write_all(&message.mac1).unwrap(); + cursor.write_all(&message.mac2).unwrap(); + + cursor + } +}
\ No newline at end of file |