aboutsummaryrefslogtreecommitdiffstats
path: root/src/protocol
diff options
context:
space:
mode:
authorsopium <sopium@mysterious.site>2017-03-22 16:46:06 +0800
committerSascha Grunert <saschagrunert@users.noreply.github.com>2017-03-23 17:55:05 +0100
commit724772ec6dbc88cc88f73d87120307635824c362 (patch)
tree6aa883f506158e7ab7cf79fd557bddf5329c5ab9 /src/protocol
parentWarn users, due to already incoming emails (diff)
downloadwireguard-rs-724772ec6dbc88cc88f73d87120307635824c362.tar.xz
wireguard-rs-724772ec6dbc88cc88f73d87120307635824c362.zip
Add sopium's protocol implementation and wg-standalone
Diffstat (limited to 'src/protocol')
-rw-r--r--src/protocol/README.md136
-rw-r--r--src/protocol/anti_replay.rs148
-rw-r--r--src/protocol/controller.rs1107
-rw-r--r--src/protocol/cookie.rs139
-rw-r--r--src/protocol/handshake.rs298
-rw-r--r--src/protocol/ip.rs51
-rw-r--r--src/protocol/mod.rs46
-rw-r--r--src/protocol/re_exports.rs23
-rw-r--r--src/protocol/timer.rs197
-rw-r--r--src/protocol/types.rs139
10 files changed, 2284 insertions, 0 deletions
diff --git a/src/protocol/README.md b/src/protocol/README.md
new file mode 100644
index 0000000..c6b4512
--- /dev/null
+++ b/src/protocol/README.md
@@ -0,0 +1,136 @@
+This is a walk-through of the protocol implementation. Hope it will be
+helpful if you want to review/hack on it.
+
+## `anti_replay.rs`
+
+Anti replay algorithm from RFC 6479. It is mostly a straightforward
+translation from the C code there, with only one notable difference:
+the handling of seq number zero.
+
+It is reasonably well tested.
+
+## `cookie.rs`
+
+Cookie signing, verification, cookie reply message generation and
+parsing.
+
+It is reasonably well tested.
+
+## `timer.rs`
+
+Timer facility. Uses the hashed timing wheel algorithm. It is
+optimized for WireGuard use cases (frequent operations on a mostly
+fixed set of timers) with the "activated" atomic boolean flag.
+
+It is under tested.
+
+## `ip.rs`
+
+Parsing of IP(v6) packets.
+
+## `types.rs`
+
+Some common types and constants.
+
+## `handshake.rs`
+
+Handshake initiation and response message generation and parsing.
+
+It is reasonably well tested.
+
+## `controller.rs`
+
+This beast is the most complex... It manages all the states, timers
+and does the actuall IO. It also has a lot of locks and `Arc`s.
+
+`WgState` represents the state of a WireGuard interface. It contains a
+hash table that maps pubkeys to peers, another hash table that maps
+session IDs to peers, and routing tables that map allowed IP(v6)
+addresses to peers.
+
+The ID table changes often and needs to be carefully kept in sync with
+actuall peer states. (Or we will leak memory.) For this we use
+`IdMapGuard` which when dropped will automatically remove an ID from
+the map.
+
+`PeerState` represents the state of a peer. It is shared across the
+maps and timers with `Arc<RwLock<_>>` (or `Weak<RwLock<>>` for
+timers).
+
+The actually IO happens in two threads, one for processing UDP
+datagrams, another for processing packets from the TUN device. (It is
+possible to use more threads, but there does not seem to be much
+benefit without also using SO_REUSEPORT or multi queue tun device.)
+
+The UDP thread repeatedly `recv_from` the socket, and take action
+based the type of the message.
+
+The TUN thread repeatedly `read` packets from the TUN device, find the
+corresponding peer by looking up the route tables, encrypt them and
+send them out if a valid session exists, and/or initiate handshake if
+necessary.
+
+### Lock Order
+
+Because we use a lot of locks, care must be taken to avoid deadlock.
+We adhere to the following partial order:
+
+ info > pubkey_map > any peers > id_map > anything else
+ any peers > rt4 > rt6
+
+Locks whose order are not defined should not be held at the same time.
+
+### Timer Management
+
+Each peer is associated with the following timers:
+
+#### `rekey_no_recv`
+
+Initiate handshake because we have send a transport message but
+haven't received any in 15s (KEEPALIVE_TIMEOUT + REKEY_TIMEOUT).
+
+It is activated and set to 15s when we send a (non-keep-alive)
+transport message, unless it is already activated.
+
+It is de-activated when we receive a transport message.
+
+#### `keep_alive`
+
+Send a keep-alive message because we have received a transport message
+but haven't sent any in 10s (KEEPALIVE_TIMEOUT).
+
+It is activated and set to 10s when we receive a (non-keep-alive)
+transport message, unless it is already activated.
+
+It is de-activated when we send a transport message.
+
+#### `persistent_keep_alive`
+
+Persistent keep-alive.
+
+It is activated and set to the configured interval when a new session
+is established, or when we send a transport message.
+
+#### `clear`
+
+Clear handshake and all transport sessions, and de-activate all
+timers, because no new session has been established in 9min (3 *
+REJECT_AFTER_TIME).
+
+It is activated and set to 9 min when we initiate a handshake, unless
+it is already activated.
+
+It is activated and set to 9 min when a new session is established.
+
+#### Not managed by timer
+
+`REKEY_AFTER_TIME` is not managed by a timer. Instead, a `created`
+time-stamp is stored within each transport session, and compared with
+when sending messages, to decide on whether to rekey.
+
+The secret used to calculate cookies, which is supposed to change
+every two minutes, is also handled this way.
+
+### Testing
+
+This module is under tested.
diff --git a/src/protocol/anti_replay.rs b/src/protocol/anti_replay.rs
new file mode 100644
index 0000000..4fb2e11
--- /dev/null
+++ b/src/protocol/anti_replay.rs
@@ -0,0 +1,148 @@
+// Copyright 2017 Sopium
+
+// This file is part of WireGuard.rs.
+
+// WireGuard.rs is free software: you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+
+// WireGuard.rs is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with WireGuard.rs. If not, see <https://www.gnu.org/licenses/>.
+
+
+// This is RFC 6479.
+
+use std::cmp::min;
+
+// Power of 2.
+const BITMAP_BITLEN: u64 = 2048;
+
+const SIZE_OF_INTEGER: u64 = 32;
+const BITMAP_LEN: usize = (BITMAP_BITLEN / SIZE_OF_INTEGER) as usize;
+const BITMAP_INDEX_MASK: u64 = BITMAP_LEN as u64 - 1;
+// REDUNDANT_BIT_SHIFTS = log2(SIZE_OF_INTEGER).
+const REDUNDANT_BIT_SHIFTS: u64 = 5;
+const BITMAP_LOC_MASK: u64 = SIZE_OF_INTEGER - 1;
+/// Size of anti-replay window.
+pub const WINDOW_SIZE: u64 = BITMAP_BITLEN - SIZE_OF_INTEGER;
+
+pub struct AntiReplay {
+ last: u64,
+ bitmap: [u32; BITMAP_LEN],
+}
+
+impl Default for AntiReplay {
+ fn default() -> Self {
+ AntiReplay::new()
+ }
+}
+
+impl AntiReplay {
+ pub fn new() -> Self {
+ AntiReplay {
+ last: 0,
+ bitmap: [0; BITMAP_LEN],
+ }
+ }
+
+ /// Returns true if check is passed, i.e., not a replay or too old.
+ ///
+ /// Unlike RFC 6479, zero is allowed.
+ pub fn check(&self, seq: u64) -> bool {
+ // Larger is always good.
+ if seq > self.last {
+ return true;
+ }
+
+ if self.last - seq > WINDOW_SIZE {
+ return false;
+ }
+
+ let bit_location = seq & BITMAP_LOC_MASK;
+ let index = (seq >> REDUNDANT_BIT_SHIFTS) & BITMAP_INDEX_MASK;
+
+ self.bitmap[index as usize] & (1 << bit_location) == 0
+ }
+
+ /// Should only be called if check returns true.
+ pub fn update(&mut self, seq: u64) {
+ debug_assert!(self.check(seq));
+
+ let index = seq >> REDUNDANT_BIT_SHIFTS;
+
+ if seq > self.last {
+ let index_cur = self.last >> REDUNDANT_BIT_SHIFTS;
+ let diff = min(index - index_cur, BITMAP_LEN as u64);
+
+ for i in 0..diff {
+ let real_index = (index_cur + i + 1) & BITMAP_INDEX_MASK;
+ self.bitmap[real_index as usize] = 0;
+ }
+ self.last = seq;
+ }
+
+ let index = index & BITMAP_INDEX_MASK;
+ let bit_location = seq & BITMAP_LOC_MASK;
+ self.bitmap[index as usize] |= 1 << bit_location;
+ }
+
+ pub fn check_and_update(&mut self, seq: u64) -> bool {
+ if self.check(seq) {
+ self.update(seq);
+ true
+ } else {
+ false
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn anti_replay() {
+ let mut ar = AntiReplay::new();
+
+ for i in 0..20000 {
+ assert!(ar.check_and_update(i));
+ }
+
+ for i in (0..20000).rev() {
+ assert!(!ar.check(i));
+ }
+
+ assert!(ar.check_and_update(65536));
+ for i in (65536 - WINDOW_SIZE)..65535 {
+ assert!(ar.check_and_update(i));
+ }
+ for i in (65536 - 10 * WINDOW_SIZE)..65535 {
+ assert!(!ar.check(i));
+ }
+
+ ar.check_and_update(66000);
+ for i in 65537..66000 {
+ assert!(ar.check_and_update(i));
+ }
+ for i in 65537..66000 {
+ assert!(!ar.check_and_update(i));
+ }
+
+ // Test max u64.
+ let next = u64::max_value();
+ assert!(ar.check_and_update(next));
+ assert!(!ar.check(next));
+ for i in (next - WINDOW_SIZE)..next {
+ assert!(ar.check_and_update(i));
+ }
+ for i in (next - 20 * WINDOW_SIZE)..next {
+ assert!(!ar.check(i));
+ }
+ }
+}
diff --git a/src/protocol/controller.rs b/src/protocol/controller.rs
new file mode 100644
index 0000000..ed3b4e3
--- /dev/null
+++ b/src/protocol/controller.rs
@@ -0,0 +1,1107 @@
+// Copyright 2017 Sopium
+
+// This file is part of WireGuard.rs.
+
+// WireGuard.rs is free software: you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+
+// WireGuard.rs is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with WireGuard.rs. If not, see <https://www.gnu.org/licenses/>.
+
+extern crate byteorder;
+extern crate noise_protocol;
+extern crate noise_sodiumoxide;
+extern crate sodiumoxide;
+extern crate tai64;
+extern crate treebitmap;
+
+use self::byteorder::{ByteOrder, LittleEndian};
+use self::noise_protocol::{Cipher, U8Array};
+use self::noise_sodiumoxide::ChaCha20Poly1305;
+use self::sodiumoxide::randombytes::randombytes_into;
+use self::tai64::TAI64N;
+use self::treebitmap::{IpLookupTable, IpLookupTableOps};
+use protocol::*;
+use std::collections::HashMap;
+use std::mem::uninitialized;
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket};
+use std::ops::Deref;
+use std::sync::{Arc, Mutex, RwLock};
+use std::sync::atomic::{AtomicBool, AtomicU64};
+use std::sync::atomic::Ordering::Relaxed;
+use std::thread::{Builder, JoinHandle, spawn};
+use std::time::{Duration, Instant, SystemTime};
+use tun::Tun;
+
+// Some Constants.
+
+// That is, 2 ^ 64 - 2 ^ 16 - 1;
+const REKEY_AFTER_MESSAGES: u64 = 0xfffffffffffeffff;
+// That is, 2 ^ 64 - 2 ^ 4 - 1;
+const REJECT_AFTER_MESSAGES: u64 = 0xffffffffffffffef;
+
+// Timeouts, in seconds.
+
+const REKEY_AFTER_TIME: u64 = 120;
+const REJECT_AFTER_TIME: u64 = 180;
+const REKEY_TIMEOUT: u64 = 5;
+const KEEPALIVE_TIMEOUT: u64 = 10;
+
+// Increase if your MTU is larger...
+const BUFSIZE: usize = 1500;
+
+type SharedPeerState = Arc<RwLock<PeerState>>;
+
+// Locking order:
+//
+// info > pubkey_map > any peers > id_map > anything else
+// any peers > rt4 > rt6
+
+/// State of a WG interface.
+pub struct WgState {
+ info: RwLock<WgInfo>,
+
+ pubkey_map: RwLock<HashMap<X25519Pubkey, SharedPeerState>>,
+ id_map: RwLock<HashMap<Id, SharedPeerState>>,
+ // Also should be keep in sync. But these should change less often.
+ rt4: RwLock<IpLookupTable<Ipv4Addr, SharedPeerState>>,
+ rt6: RwLock<IpLookupTable<Ipv6Addr, SharedPeerState>>,
+
+ // The secret used to calc cookie.
+ cookie_secret: Mutex<([u8; 32], Instant)>,
+
+ timer_controller: TimerController,
+}
+
+/// Removes `Id` from `id_map` when dropped.
+struct IdMapGuard {
+ wg: Arc<WgState>,
+ id: Id,
+}
+
+impl Drop for IdMapGuard {
+ fn drop(&mut self) {
+ if let Ok(mut id_map) = self.wg.id_map.try_write() {
+ id_map.remove(&self.id);
+ } else {
+ let wg = self.wg.clone();
+ let id = self.id;
+ spawn(move || { wg.id_map.write().unwrap().remove(&id); });
+ }
+ }
+}
+
+impl IdMapGuard {
+ fn new(wg: Arc<WgState>, id: Id) -> Self {
+ Self { wg: wg, id: id }
+ }
+}
+
+/// State of a paticular peer.
+struct PeerState {
+ info: PeerInfo,
+ last_handshake: Option<TAI64N>,
+ cookie: Option<(Cookie, Instant)>,
+ last_mac1: Option<[u8; 16]>,
+ handshake: Option<Handshake>,
+
+ // XXX: use a Vec? or ArrayVec?
+ transport0: Option<Transport>,
+ transport1: Option<Transport>,
+ transport2: Option<Transport>,
+
+ // Rekey because of send but not recv in...
+ rekey_no_recv: TimerHandle,
+ // Keep alive because of recv but not send in...
+ keep_alive: TimerHandle,
+ // Persistent keep-alive.
+ persistent_keep_alive: TimerHandle,
+ // Clear all sessions if no new handshake in REJECT_AFTER_TIME * 3.
+ clear: TimerHandle,
+}
+
+struct Handshake {
+ self_id: IdMapGuard,
+ hs: HS,
+ // Resend after REKEY_TIMEOUT.
+ #[allow(dead_code)]
+ resend: TimerHandle,
+}
+
+type SecretKey = <ChaCha20Poly1305 as Cipher>::Key;
+
+/// A WireGuard transport session.
+struct Transport {
+ self_id: IdMapGuard,
+ peer_id: Id,
+ is_initiator: bool,
+ // If we are responder, should not send until received one packet.
+ is_initiator_or_has_received: AtomicBool,
+ // Also should not send after REJECT_AFTER_TIME,
+ // or after REJECT_AFTER_MESSAGES.
+ not_too_old: AtomicBool,
+ created: Instant,
+
+ send_key: SecretKey,
+ send_counter: AtomicU64,
+
+ recv_key: SecretKey,
+ recv_ar: Mutex<AntiReplay>,
+}
+
+// TODO determine / detect load.
+fn is_under_load() -> bool {
+ false
+}
+
+fn udp_process_handshake_init(wg: Arc<WgState>, sock: &UdpSocket, p: &[u8], addr: SocketAddr) {
+ if p.len() != 148 {
+ return;
+ }
+
+ // Lock info.
+ let info = wg.info.read().unwrap();
+
+ if is_under_load() {
+ let cookie = calc_cookie(&wg.get_cookie_secret(), addr.to_string().as_bytes());
+ if !cookie_verify(p, &cookie) {
+ debug!("Mac2 verify failed, send cookie reply.");
+ let peer_id = Id::from_slice(&p[4..8]);
+ let mac1 = &p[116..132];
+ let reply = cookie_reply(info.psk.as_ref(), &info.pubkey, &cookie, peer_id, mac1);
+ sock.send_to(&reply, addr).unwrap();
+ return;
+ } else {
+ debug!("Mac2 verify OK.");
+ }
+ }
+
+ if let Ok(mut r) = process_initiation(info.deref(), p) {
+ let r_pubkey = r.handshake_state.get_rs().unwrap();
+ if let Some(peer0) = wg.find_peer_by_pubkey(&r_pubkey) {
+ // Lock peer.
+ let mut peer = peer0.write().unwrap();
+
+ // Compare timestamp.
+ if Some(r.timestamp) > peer.last_handshake {
+ peer.last_handshake = Some(r.timestamp);
+ } else {
+ debug!("Handshake timestamp smaller.");
+ return;
+ }
+
+ let self_id = Id::gen();
+ let mut response = responde(info.deref(), &mut r, self_id);
+
+ // Save mac1.
+ let mut mac1 = [0u8; 16];
+ mac1.copy_from_slice(&response[60..76]);
+ peer.last_mac1 = Some(mac1);
+
+ cookie_sign(&mut response, peer.get_cookie());
+ sock.send_to(&response, addr).unwrap();
+
+ let t = Transport::new_from_hs(IdMapGuard::new(wg.clone(), self_id),
+ r.peer_id,
+ r.handshake_state);
+ peer.set_endpoint(addr);
+ peer.push_transport(t);
+ // Lock id_map.
+ wg.id_map.write().unwrap().insert(self_id, peer0.clone());
+ debug!("Handshake successful as responder.");
+ } else {
+ debug!("Get handshake init, but can't find peer by pubkey.");
+ }
+ } else {
+ debug!("Get handshake init, but authentication/decryption failed.");
+ }
+}
+
+fn udp_process_handshake_resp(wg: &WgState, sock: &UdpSocket, p: &[u8], addr: SocketAddr) {
+ if p.len() != 92 {
+ return;
+ }
+
+ // Lock info.
+ let info = wg.info.read().unwrap();
+
+ if is_under_load() {
+ let cookie = calc_cookie(&wg.get_cookie_secret(), addr.to_string().as_bytes());
+ if !cookie_verify(p, &cookie) {
+ debug!("Mac2 verify failed, send cookie reply.");
+ let peer_id = Id::from_slice(&p[4..8]);
+ let mac1 = &p[60..76];
+ let reply = cookie_reply(info.psk.as_ref(), &info.pubkey, &cookie, peer_id, mac1);
+ sock.send_to(&reply, addr).unwrap();
+ return;
+ } else {
+ debug!("Mac2 verify OK.");
+ }
+ }
+
+ let self_id = Id::from_slice(&p[8..12]);
+
+ if let Some(peer0) = wg.find_peer_by_id(self_id) {
+ let (peer_id, hs) = {
+ // Lock peer.
+ let peer = peer0.read().unwrap();
+ if peer.handshake.is_none() {
+ debug!("Get handshake response message, but don't know id.");
+ return;
+ }
+ let handshake = peer.handshake.as_ref().unwrap();
+ if handshake.self_id.id != self_id {
+ debug!("Get handshake response message, but don't know id.");
+ return;
+ }
+
+ let mut hs = handshake.hs.clone();
+ if let Ok(peer_id) = process_response(info.deref(), &mut hs, p) {
+ (peer_id, hs)
+ } else {
+ debug!("Get handshake response message, auth/decryption failed.");
+ return;
+ }
+ // Release peer.
+ };
+ debug!("Handshake successful as initiator.");
+ // Lock peer.
+ let mut peer = peer0.write().unwrap();
+ let handle = peer.handshake.take().unwrap().self_id;
+ let t = Transport::new_from_hs(handle, peer_id, hs);
+ peer.push_transport(t);
+ peer.set_endpoint(addr);
+ // Send an empty packet for key confirmation.
+ do_keep_alive(&peer, sock);
+ // Lock id_map.
+ wg.id_map.write().unwrap().insert(self_id, peer0.clone());
+ } else {
+ debug!("Get handshake response message, but don't know id.");
+ }
+}
+
+fn udp_process_cookie_reply(wg: &WgState, p: &[u8]) {
+ let self_id = Id::from_slice(&p[4..8]);
+
+ // Lock info.
+ let info = wg.info.read().unwrap();
+
+ if let Some(peer) = wg.find_peer_by_id(self_id) {
+ // Lock peer.
+ let mut peer = peer.write().unwrap();
+ if let Some(mac1) = peer.last_mac1 {
+ if let Ok(cookie) = process_cookie_reply(info.psk.as_ref(),
+ &peer.info.peer_pubkey,
+ &mac1,
+ p) {
+ peer.cookie = Some((cookie, Instant::now()));
+ } else {
+ debug!("Process cookie reply: auth/decryption failed.");
+ }
+ }
+ }
+}
+
+fn udp_process_transport(wg: &WgState, tun: &Tun, p: &[u8], addr: SocketAddr) {
+ if p.len() < 32 {
+ return;
+ }
+
+ let self_id = Id::from_slice(&p[4..8]);
+
+ let maybe_peer0 = wg.find_peer_by_id(self_id);
+
+ if maybe_peer0.is_none() {
+ debug!("Get transport message, but don't know id.");
+ return;
+ }
+
+ let peer0 = maybe_peer0.unwrap();
+ let should_set_endpoint = {
+ // Lock peer.
+ let peer = peer0.read().unwrap();
+ if let Some(t) = peer.find_transport_by_id(self_id) {
+ let mut buff: [u8; BUFSIZE] = unsafe { uninitialized() };
+ let decrypted = &mut buff[..p.len() - 32];
+ if t.decrypt(p, decrypted).is_ok() {
+ if let Ok((len, src, _)) = parse_ip_packet(decrypted) {
+ // Reverse path filtering.
+ let peer1 = wg.find_peer_by_ip(src);
+ if peer1.is_none() || !Arc::ptr_eq(&peer0, &peer1.unwrap()) {
+ debug!("Get transport message: allowed IPs check failed.");
+ } else {
+ tun.write(&decrypted[..len as usize]).unwrap();
+ }
+ }
+ peer.on_recv(decrypted.len() == 0);
+ peer.info.endpoint != Some(addr)
+ } else {
+ debug!("Get transport message, decryption failed.");
+ false
+ }
+ } else {
+ false
+ }
+ // Release peer.
+ };
+ if should_set_endpoint {
+ // Lock peer.
+ peer0.write()
+ .unwrap()
+ .set_endpoint(addr);
+ }
+}
+
+/// Start a new thread to recv and process UDP packets.
+///
+/// This thread runs forever.
+pub fn start_udp_processing(wg: Arc<WgState>, sock: Arc<UdpSocket>, tun: Arc<Tun>) -> JoinHandle<()> {
+ Builder::new().name("UDP".to_string()).spawn(move || {
+ let mut p = [0u8; BUFSIZE];
+ loop {
+ let (len, addr) = sock.recv_from(&mut p).unwrap();
+
+ if len < 12 {
+ continue;
+ }
+
+ let type_ = p[0];
+ let p = &p[..len];
+
+ match type_ {
+ 1 => udp_process_handshake_init(wg.clone(), sock.as_ref(), p, addr),
+ 2 => udp_process_handshake_resp(wg.as_ref(), sock.as_ref(), p, addr),
+ 3 => udp_process_cookie_reply(wg.as_ref(), p),
+ 4 => udp_process_transport(wg.as_ref(), tun.as_ref(), p, addr),
+ _ => (),
+ }
+ }
+ }).unwrap()
+}
+
+// Packets >= MAX_PADDING won't be padded.
+// 1280 should be a reasonable conservative choice.
+const MAX_PADDING: usize = 1280;
+
+const PADDING_MASK: usize = 0b1111;
+
+fn pad_len(len: usize) -> usize {
+ if len >= MAX_PADDING {
+ len
+ } else {
+ // Next multiply of 16.
+ (len & !PADDING_MASK) + if len & PADDING_MASK == 0 {
+ 0
+ } else {
+ 16
+ }
+ }
+}
+
+#[cfg(test)]
+#[test]
+fn padding() {
+ assert_eq!(pad_len(0), 0);
+ for i in 1..16 {
+ assert_eq!(pad_len(i), 16);
+ }
+
+ for i in 17..32 {
+ assert_eq!(pad_len(i), 32);
+ }
+
+ for i in 1265..1280 {
+ assert_eq!(pad_len(i), 1280);
+ }
+}
+
+/// Start a new thread to read and process packets from TUN device.
+///
+/// This thread runs forever.
+pub fn start_tun_packet_processing(wg: Arc<WgState>, sock: Arc<UdpSocket>, tun: Arc<Tun>) -> JoinHandle<()> {
+ Builder::new().name("TUN".to_string()).spawn(move || {
+ let mut pkt = [0u8; BUFSIZE];
+ loop {
+ let len = tun.read(&mut pkt).unwrap();
+ let padded_len = pad_len(len);
+ // Do not leak other packets' data!
+ for b in &mut pkt[len..padded_len] {
+ *b = 0;
+ }
+ let pkt = &pkt[..padded_len];
+
+ let parse_result = parse_ip_packet(pkt);
+ if parse_result.is_err() {
+ error!("Get packet from TUN device, but failed to parse it!");
+ continue;
+ }
+ let dst = parse_result.unwrap().2;
+
+ let peer = wg.find_peer_by_ip(dst);
+ if peer.is_none() {
+ // TODO ICMP no route to host.
+ debug!("No route to host: {}", dst);
+ continue;
+ }
+ let peer0 = peer.unwrap();
+ let should_handshake = {
+ // Lock peer.
+ let peer = peer0.read().unwrap();
+ if peer.get_endpoint().is_none() {
+ // TODO ICMP host unreachable?
+ continue;
+ }
+
+ if let Some(t) = peer.find_transport_to_send() {
+ let mut encrypted: [u8; BUFSIZE] = unsafe { uninitialized() };
+ let encrypted = &mut encrypted[..pkt.len() + 32];
+ let (result, should_handshake) = t.encrypt(pkt, encrypted);
+ if result.is_ok() {
+ sock.send_to(encrypted, peer.get_endpoint().unwrap()).unwrap();
+ peer.on_send(false);
+ }
+ // Optimization: don't bother `do_handshake` if there is already
+ // an ongoing handshake.
+ should_handshake && peer.handshake.is_none()
+ } else {
+ // TODO: queue packets.
+
+ // Optimization: don't bother `do_handshake` if there is already
+ // an ongoing handshake.
+ peer.handshake.is_none()
+ }
+ // Release peer.
+ };
+
+ if should_handshake {
+ do_handshake(wg.clone(), peer0, sock.clone());
+ }
+ }
+ }).unwrap()
+}
+
+/// Start handshake.
+///
+/// Better not hold any locks when calling this.
+//
+/// Nothing happens if there is already an ongoing handshake for this peer.
+/// Nothing happens if we don't know peer endpoint.
+fn do_handshake(wg: Arc<WgState>, peer0: SharedPeerState, sock: Arc<UdpSocket>) {
+ // Lock info.
+ let info = wg.info.read().unwrap();
+
+ // Lock peer.
+ let mut peer = peer0.write().unwrap();
+ if peer.handshake.is_some() {
+ return;
+ }
+ let endpoint = if peer.get_endpoint().is_none() {
+ return;
+ } else {
+ peer.get_endpoint().unwrap()
+ };
+
+ debug!("Handshake init.");
+
+ let id = Id::gen();
+ // Lock id_map.
+ wg.id_map.write().unwrap().insert(id, peer0.clone());
+ let handle = IdMapGuard::new(wg.clone(), id);
+
+ let (mut i, hs) = initiate(info.deref(), &peer.info, id);
+ cookie_sign(&mut i, peer.get_cookie());
+
+ sock.send_to(&i, endpoint).unwrap();
+ let mut mac1 = [0u8; 16];
+ mac1.copy_from_slice(&i[116..132]);
+ peer.last_mac1 = Some(mac1);
+
+ let resend = {
+ let wg = wg.clone();
+ let sock = sock.clone();
+ let peer = Arc::downgrade(&peer0);
+ Box::new(move || {
+ debug!("Timer: resend.");
+ peer.upgrade().map(|p| {
+ p.write().unwrap().handshake = None;
+ do_handshake(wg.clone(), p, sock.clone());
+ });
+ })
+ };
+
+ let resend = wg.timer_controller.register_delay(Duration::from_secs(REKEY_TIMEOUT), resend);
+ resend.activate();
+
+ peer.handshake = Some(Handshake {
+ self_id: handle,
+ hs: hs,
+ resend: resend,
+ });
+
+ peer.clear.adjust_and_activate_if_not_activated(3 * REJECT_AFTER_TIME);
+}
+
+fn do_keep_alive(peer: &PeerState, sock: &UdpSocket) {
+ let e = peer.get_endpoint();
+ if e.is_none() {
+ return;
+ }
+ let e = e.unwrap();
+
+ let t = peer.find_transport_to_send();
+ if t.is_none() {
+ return;
+ }
+ let t = t.unwrap();
+
+ let mut out = [0u8; 32];
+ if t.encrypt(&[], &mut out).0.is_err() {
+ return;
+ }
+
+ debug!("Keep alive.");
+ sock.send_to(&out, e).unwrap();
+
+ peer.on_send(true);
+}
+
+// Cannot be methods because we need `Arc<WgState>`.
+
+/// Query state of the WG interface.
+pub fn wg_query_state(wg: Arc<WgState>) -> WgStateOut {
+ let peers = {
+ // Lock pubkey map.
+ let pubkey_map = wg.pubkey_map.read().unwrap();
+
+ pubkey_map.values().map(|p| {
+ // Lock peer.
+ let peer = p.read().unwrap();
+
+ PeerStateOut {
+ public_key: peer.info.peer_pubkey,
+ endpoint: peer.info.endpoint,
+ last_handshake_time: peer.get_last_handshake_time(),
+ // TODO: Implement tx/rx bytes counting.
+ rx_bytes: 0,
+ tx_bytes: 0,
+ persistent_keepalive_interval: peer.info.keep_alive_interval,
+ allowed_ips: peer.info.allowed_ips.clone(),
+ }
+ // Release peer.
+ }).collect()
+ // Release pubkey map.
+ };
+
+ // Lock info.
+ let info = wg.info.read().unwrap();
+ WgStateOut {
+ private_key: info.key.clone(),
+ public_key: info.pubkey,
+ preshared_key: info.psk,
+ peers: peers,
+ }
+ // Release info.
+}
+
+/// Change WG interface configuration.
+///
+/// All existing sessions and handshakes will be cleared, on the assumption that
+/// our crypto keys has changed!
+pub fn wg_change_info<F>(wg: Arc<WgState>, f: F)
+ where F: FnOnce(&mut WgInfo),
+{
+ // Lock info.
+ let mut info = wg.info.write().unwrap();
+
+ // Lock pubkey map.
+ let pubkey_map = wg.pubkey_map.read().unwrap();
+
+ for p in pubkey_map.values() {
+ // Lock peer.
+ p.write().unwrap().clear();
+ // Release peer.
+ }
+
+ drop(pubkey_map);
+ // Release pubkey_map.
+
+ f(&mut info);
+ // Release info.
+}
+
+/// Remove a peer.
+///
+/// Returns: whether there was indeed such a peer, with that public key,
+/// that has been removed.
+pub fn wg_remove_peer(wg: Arc<WgState>, peer_pubkey: &X25519Pubkey) -> bool {
+ // Remove from pubkey_map.
+ // Lock pubkey_map.
+ let mut pubkey_map = wg.pubkey_map.write().unwrap();
+ let p = pubkey_map.remove(peer_pubkey);
+ if p.is_none() {
+ // Release pubkey_map.
+ return false;
+ }
+ let p = p.unwrap();
+ drop(pubkey_map);
+ // Release pubkey_map.
+
+ // Lock peer.
+ let mut peer = p.write().unwrap();
+ // This will remove peer from `id_map` through `IdMapGuard`.
+ peer.clear();
+
+ // Remove from rt4 / rt6.
+
+ // Lock rt4.
+ let mut rt4 = wg.rt4.write().unwrap();
+ // Lock rt6.
+ let mut rt6 = wg.rt6.write().unwrap();
+ for &(a, m) in &peer.info.allowed_ips {
+ match a {
+ IpAddr::V4(a) => rt4.remove(a, m),
+ IpAddr::V6(a) => rt6.remove(a, m),
+ };
+ }
+
+ true
+}
+
+/// Change configuration of a peer.
+///
+/// If (and only if) peer public key is changed, ongoing handshake and all
+/// transport sessions will be cleared.
+///
+/// Returns whether there was indeed such a peer, with that public key,
+/// that has been changed.
+pub fn wg_change_peer<F>(wg: Arc<WgState>, peer_pubkey: &X25519Pubkey, f: F) -> bool
+ where F: FnOnce(&mut PeerInfo),
+{
+ let peer = wg.find_peer_by_pubkey(peer_pubkey);
+ if peer.is_none() {
+ return false;
+ }
+ let peer0 = peer.unwrap();
+
+ // Lock peer.
+ let mut peer = peer0.write().unwrap();
+ let old_pubkey = peer.info.peer_pubkey;
+ let old_allowed_ips = peer.info.allowed_ips.clone();
+
+ f(&mut peer.info);
+
+ if old_pubkey != peer.info.peer_pubkey {
+ peer.clear();
+ }
+
+ if old_allowed_ips != peer.info.allowed_ips {
+ // Lock rt4.
+ let mut rt4 = wg.rt4.write().unwrap();
+ // Lock rt6.
+ let mut rt6 = wg.rt6.write().unwrap();
+
+ for (a, m) in old_allowed_ips {
+ match a {
+ IpAddr::V4(a) => rt4.remove(a, m),
+ IpAddr::V6(a) => rt6.remove(a, m),
+ };
+ }
+
+ for &(a, m) in &peer.info.allowed_ips {
+ match a {
+ IpAddr::V4(a4) => rt4.insert(a4, m, peer0.clone()),
+ IpAddr::V6(a6) => rt6.insert(a6, m, peer0.clone()),
+ };
+ }
+ }
+
+ true
+}
+
+/// Add a peer to a WG interface.
+/// The peer should not already exist.
+pub fn wg_add_peer(wg: Arc<WgState>, peer: &PeerInfo, sock: Arc<UdpSocket>) {
+ let register = |a| wg.timer_controller.register_delay(Duration::from_secs(0), a);
+ let dummy_action = || Box::new(|| {});
+
+ // Lock pubkey_map.
+ let mut pubkey_map = wg.pubkey_map.write().unwrap();
+
+ let ps = PeerState {
+ info: peer.clone(),
+ last_handshake: None,
+ last_mac1: None,
+ cookie: None,
+ handshake: None,
+ transport0: None,
+ transport1: None,
+ transport2: None,
+ rekey_no_recv: register(dummy_action()),
+ keep_alive: register(dummy_action()),
+ persistent_keep_alive: register(dummy_action()),
+ clear: register(dummy_action()),
+ };
+ let ps = Arc::new(RwLock::new(ps));
+
+ // Init timers.
+ {
+ let weak_ps = Arc::downgrade(&ps);
+ // Lock peer.
+ let mut psw = ps.write().unwrap();
+ // Same with rekey.
+ psw.rekey_no_recv = {
+ let wg = wg.clone();
+ let weak_ps = weak_ps.clone();
+ let sock = sock.clone();
+ register(Box::new(move || {
+ weak_ps.upgrade().map(|p| {
+ debug!("Timer: rekey_no_recv.");
+ do_handshake(wg.clone(), p, sock.clone());
+ });
+ }))
+ };
+ psw.keep_alive = {
+ let weak_ps = weak_ps.clone();
+ let sock = sock.clone();
+ register(Box::new(move || {
+ weak_ps.upgrade().map(|p| {
+ debug!("Timer: keep_alive.");
+ do_keep_alive(&p.read().unwrap(), &sock);
+ });
+ }))
+ };
+ psw.persistent_keep_alive = {
+ let weak_ps = weak_ps.clone();
+ let sock = sock.clone();
+ register(Box::new(move || {
+ weak_ps.upgrade().map(|p| {
+ debug!("Timer: persistent_keep_alive.");
+ do_keep_alive(&p.read().unwrap(), &sock);
+ });
+ }))
+ };
+ psw.clear = {
+ let weak_ps = weak_ps.clone();
+ register(Box::new(move || {
+ weak_ps.upgrade().map(|p| {
+ debug!("Timer: clear.");
+ p.write().unwrap().clear();
+ });
+ }))
+ };
+ }
+
+ let mut rt4 = wg.rt4.write().unwrap();
+ let mut rt6 = wg.rt6.write().unwrap();
+
+ for &(a, prefix) in &peer.allowed_ips {
+ match a {
+ IpAddr::V4(a4) => rt4.insert(a4, prefix, ps.clone()),
+ IpAddr::V6(a6) => rt6.insert(a6, prefix, ps.clone()),
+ };
+ }
+ pubkey_map.insert(peer.peer_pubkey, ps);
+}
+
+impl WgState {
+ /// Create a new `WgState` from `WgInfo`.
+ pub fn new(info: WgInfo) -> WgState {
+ let mut cookie = [0u8; 32];
+ randombytes_into(&mut cookie);
+
+ WgState {
+ info: RwLock::new(info),
+ pubkey_map: RwLock::new(HashMap::with_capacity(1)),
+ id_map: RwLock::new(HashMap::with_capacity(4)),
+ rt4: RwLock::new(IpLookupTable::new()),
+ rt6: RwLock::new(IpLookupTable::new()),
+ cookie_secret: Mutex::new((cookie, Instant::now())),
+ timer_controller: TimerController::new(),
+ }
+ }
+
+ /// Create a new `WgState`, and add some peers.
+ pub fn new_with_peers(info: WgInfo, peers: &[PeerInfo], sock: Arc<UdpSocket>) -> Arc<WgState> {
+ let wg = Arc::new(WgState::new(info));
+
+ for p in peers {
+ wg_add_peer(wg.clone(), p, sock.clone())
+ }
+
+ wg
+ }
+
+ // These methods helps a lot in avoiding deadlocks.
+
+ fn find_peer_by_id(&self, id: Id) -> Option<SharedPeerState> {
+ self.id_map.read().unwrap().get(&id).cloned()
+ }
+
+ fn find_peer_by_pubkey(&self, pk: &X25519Pubkey) -> Option<SharedPeerState> {
+ self.pubkey_map.read().unwrap().get(pk).cloned()
+ }
+
+ /// Find peer by ip address, consulting the routing tables.
+ fn find_peer_by_ip(&self, addr: IpAddr) -> Option<SharedPeerState> {
+ match addr {
+ IpAddr::V4(ip4) => {
+ self.rt4.read().unwrap().longest_match(ip4).map(|x| x.2.clone())
+ }
+ IpAddr::V6(ip6) => {
+ self.rt6.read().unwrap().longest_match(ip6).map(|x| x.2.clone())
+ }
+ }
+ }
+
+ fn get_cookie_secret(&self) -> [u8; 32] {
+ let mut cs = self.cookie_secret.lock().unwrap();
+ let now = Instant::now();
+ if now.duration_since(cs.1) <= Duration::from_secs(120) {
+ cs.0
+ } else {
+ randombytes_into(&mut cs.0);
+ cs.1 = now;
+ cs.0
+ }
+ }
+}
+
+impl PeerState {
+ fn get_endpoint(&self) -> Option<SocketAddr> {
+ self.info.endpoint
+ }
+
+ fn set_endpoint(&mut self, a: SocketAddr) {
+ self.info.endpoint = Some(a)
+ }
+
+ fn get_cookie(&self) -> Option<&Cookie> {
+ if self.cookie.is_none() {
+ return None;
+ }
+ if self.cookie.as_ref().unwrap().1.elapsed() >= Duration::from_secs(120) {
+ return None;
+ }
+ Some(&self.cookie.as_ref().unwrap().0)
+ }
+
+ fn get_last_handshake_time(&self) -> Option<SystemTime> {
+ self.transport0.as_ref().map(|t| {
+ let dur = t.created.elapsed();
+ SystemTime::now() - dur
+ })
+ }
+
+ fn clear(&mut self) {
+ self.handshake = None;
+ self.transport0 = None;
+ self.transport1 = None;
+ self.transport2 = None;
+
+ self.rekey_no_recv.de_activate();
+ self.keep_alive.de_activate();
+ self.persistent_keep_alive.de_activate();
+ self.clear.de_activate();
+ }
+
+ // rekey = is_initiator.
+ fn on_new_transport(&self) {
+ self.clear.adjust_and_activate(3 * REJECT_AFTER_TIME);
+ self.info.keep_alive_interval.as_ref().map(|i| {
+ self.persistent_keep_alive.adjust_and_activate(*i as u64);
+ });
+ }
+
+ fn on_recv(&self, is_keepalive: bool) {
+ self.rekey_no_recv.de_activate();
+ if !is_keepalive {
+ self.keep_alive.adjust_and_activate_if_not_activated(KEEPALIVE_TIMEOUT);
+ }
+ }
+
+ fn on_send(&self, is_keepalive: bool) {
+ self.keep_alive.de_activate();
+ if !is_keepalive {
+ self.rekey_no_recv.
+ adjust_and_activate_if_not_activated(KEEPALIVE_TIMEOUT + REKEY_TIMEOUT);
+ }
+ self.info.keep_alive_interval.as_ref().map(|i| {
+ self.persistent_keep_alive.adjust_and_activate(*i as u64);
+ });
+ }
+
+ fn push_transport(&mut self, t: Transport) {
+ self.on_new_transport();
+
+ self.transport2 = self.transport1.take();
+ self.transport1 = self.transport0.take();
+ self.transport0 = Some(t);
+ }
+
+ /// Find a transport to send packet.
+ fn find_transport_to_send(&self) -> Option<&Transport> {
+ // If there exists any transport, we rely on timers to init handshake.
+
+ if let Some(ref t) = self.transport0 {
+ if t.get_should_send() {
+ return Some(t);
+ }
+ } else {
+ return None;
+ }
+
+ if let Some(ref t) = self.transport1 {
+ if t.get_should_send() {
+ return Some(t);
+ }
+ } else {
+ return None;
+ }
+
+ if let Some(ref t) = self.transport2 {
+ if t.get_should_send() {
+ return Some(t);
+ }
+ }
+
+ None
+ }
+
+ fn find_transport_by_id(&self, id: Id) -> Option<&Transport> {
+ if let Some(ref t) = self.transport0 {
+ if t.get_self_id() == id {
+ return Some(t);
+ }
+ } else {
+ return None;
+ }
+
+ if let Some(ref t) = self.transport1 {
+ if t.get_self_id() == id {
+ return Some(t);
+ }
+ } else {
+ return None;
+ }
+
+ if let Some(ref t) = self.transport2 {
+ if t.get_self_id() == id {
+ return Some(t);
+ }
+ }
+ None
+ }
+}
+
+impl Transport {
+ fn new_from_hs(self_id: IdMapGuard, peer_id: Id, hs: HS) -> Self {
+ let (x, y) = hs.get_ciphers();
+ let (s, r) = if hs.get_is_initiator() {
+ (x, y)
+ } else {
+ (y, x)
+ };
+ let sk = s.extract().0;
+ let rk = r.extract().0;
+
+ Transport {
+ self_id: self_id,
+ peer_id: peer_id,
+ is_initiator: hs.get_is_initiator(),
+ is_initiator_or_has_received: AtomicBool::new(hs.get_is_initiator()),
+ not_too_old: AtomicBool::new(true),
+ send_key: sk,
+ recv_key: rk,
+ created: Instant::now(),
+ recv_ar: Mutex::new(AntiReplay::new()),
+ send_counter: AtomicU64::new(0),
+ }
+ }
+
+ fn get_should_send(&self) -> bool {
+ self.is_initiator_or_has_received.load(Relaxed) && self.not_too_old.load(Relaxed)
+ }
+
+ fn get_self_id(&self) -> Id {
+ self.self_id.id
+ }
+
+ /// Expect packet with padding.
+ ///
+ /// Returns: Whether the operation is successful. Whether we should initiate handshake.
+ ///
+ /// Length: out.len() = msg.len() + 32.
+ fn encrypt(&self, msg: &[u8], out: &mut [u8]) -> (Result<(), ()>, bool) {
+ let c = self.send_counter.fetch_add(1, Relaxed);
+ let mut should_rekey = false;
+ if self.is_initiator && c >= REKEY_AFTER_MESSAGES {
+ should_rekey = true;
+ }
+ if c >= REJECT_AFTER_MESSAGES {
+ self.not_too_old.store(false, Relaxed);
+ return (Err(()), should_rekey);
+ }
+
+ let age = self.created.elapsed();
+
+ if age >= Duration::from_secs(REKEY_AFTER_TIME) {
+ should_rekey = true;
+ }
+ if age >= Duration::from_secs(REJECT_AFTER_TIME) {
+ self.not_too_old.store(false, Relaxed);
+ return (Err(()), should_rekey);
+ }
+
+ out[0..4].copy_from_slice(&[4, 0, 0, 0]);
+ out[4..8].copy_from_slice(self.peer_id.as_slice());
+ LittleEndian::write_u64(&mut out[8..16], c);
+
+ <ChaCha20Poly1305 as Cipher>::encrypt(&self.send_key, c, &[], msg, &mut out[16..]);
+
+ (Ok(()), should_rekey)
+ }
+
+ /// Returns packet maybe with padding.
+ ///
+ /// Length: out.len() + 32 = msg.len().
+ fn decrypt(&self, msg: &[u8], out: &mut [u8]) -> Result<(), ()> {
+ if self.created.elapsed() >= Duration::from_secs(REJECT_AFTER_TIME) {
+ return Err(());
+ }
+
+ if msg.len() < 32 {
+ return Err(());
+ }
+
+ if msg[0..4] != [4, 0, 0, 0] {
+ return Err(());
+ }
+
+ if self.created.elapsed() >= Duration::from_secs(REJECT_AFTER_TIME) {
+ return Err(());
+ }
+
+ let counter = LittleEndian::read_u64(&msg[8..16]);
+
+ if counter >= REJECT_AFTER_MESSAGES {
+ return Err(());
+ }
+
+ <ChaCha20Poly1305 as Cipher>::decrypt(&self.recv_key, counter, &[], &msg[16..], out)?;
+
+ if !self.recv_ar.lock().unwrap().check_and_update(counter) {
+ return Err(());
+ }
+
+ self.is_initiator_or_has_received.store(true, Relaxed);
+
+ Ok(())
+ }
+}
diff --git a/src/protocol/cookie.rs b/src/protocol/cookie.rs
new file mode 100644
index 0000000..008d03f
--- /dev/null
+++ b/src/protocol/cookie.rs
@@ -0,0 +1,139 @@
+// Copyright 2017 Sopium
+
+// This file is part of WireGuard.rs.
+
+// WireGuard.rs is free software: you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+
+// WireGuard.rs is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with WireGuard.rs. If not, see <https://www.gnu.org/licenses/>.
+
+extern crate blake2_rfc;
+extern crate sodiumoxide;
+
+use self::blake2_rfc::blake2s::blake2s;
+use self::sodiumoxide::randombytes::randombytes_into;
+use self::sodiumoxide::utils::memcmp;
+use crypto::xchacha20poly1305::{decrypt, encrypt};
+use protocol::{Id, X25519Pubkey};
+
+pub type Cookie = [u8; 16];
+
+/// Calc cookie according to a secret and a bytes representation of peer address.
+///
+/// This is a pure function.
+pub fn calc_cookie(secret: &[u8], remote_addr: &[u8]) -> Cookie {
+ let mut out = [0u8; 16];
+ let r = blake2s(16, secret, remote_addr);
+ out.copy_from_slice(r.as_bytes());
+ out
+}
+
+/// Generate cookie reply message (64 bytes).
+pub fn cookie_reply(psk: Option<&[u8; 32]>,
+ pubkey: &X25519Pubkey,
+ cookie: &Cookie,
+ peer_index: Id,
+ mac1: &[u8])
+ -> [u8; 64] {
+ let mut out = [0u8; 64];
+
+ // Type and zeros.
+ out[0..4].copy_from_slice(&[3, 0, 0, 0]);
+ // Receiver index.
+ out[4..8].copy_from_slice(peer_index.as_slice());
+
+ {
+ let (nonce, encrypted_cookie) = out[8..64].split_at_mut(24);
+ randombytes_into(nonce);
+
+ // Calc encryption key.
+ let temp = blake2s(32, psk.map_or(&[], |p| p.as_ref()), pubkey.as_ref());
+
+ // Encrypt cookie.
+ encrypt(temp.as_bytes(), nonce, mac1, cookie, encrypted_cookie);
+ }
+
+ out
+}
+
+pub fn process_cookie_reply(psk: Option<&[u8; 32]>,
+ peer_pubkey: &X25519Pubkey,
+ mac1: &[u8],
+ msg: &[u8])
+ -> Result<Cookie, ()> {
+ if msg.len() != 64 {
+ return Err(());
+ }
+
+ if &msg[..4] != &[3, 0, 0, 0] {
+ return Err(());
+ }
+
+ // msg[4..8] is sender index, skip.
+
+ let nonce = &msg[8..32];
+
+ let ciphertext = &msg[32..64];
+
+ // Calc encryption key.
+ let temp = blake2s(32, psk.map_or(&[], |p| p.as_ref()), peer_pubkey);
+
+ let mut cookie = [0u8; 16];
+ decrypt(temp.as_bytes(), nonce, mac1, ciphertext, &mut cookie)?;
+ Ok(cookie)
+}
+
+pub fn cookie_sign(m: &mut [u8], cookie: Option<&Cookie>) {
+ if cookie.is_none() {
+ return;
+ }
+ let len = m.len();
+ let (m1, m2) = m.split_at_mut(len - 16);
+ let mac2 = blake2s(16, cookie.unwrap(), m1);
+ m2.copy_from_slice(mac2.as_bytes());
+}
+
+pub fn cookie_verify(m: &[u8], cookie: &Cookie) -> bool {
+ if m.len() < 16 {
+ return false;
+ }
+ let (m, mac2) = m.split_at(m.len() - 16);
+ let mac2_ = blake2s(16, cookie, m);
+ memcmp(mac2_.as_bytes(), mac2)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn cookie() {
+ let mut psk = [0u8; 32];
+ randombytes_into(&mut psk);
+
+ let mut pk = [0u8; 32];
+ randombytes_into(&mut pk);
+
+ let mut mac1 = [0u8; 16];
+ randombytes_into(&mut mac1);
+
+ let mut secret = [0u8; 32];
+ randombytes_into(&mut secret);
+
+ let cookie = calc_cookie(&secret, b"1.2.3.4");
+
+ let reply = cookie_reply(Some(&psk), &pk, &cookie, Id::gen(), &mac1);
+
+ let cookie1 = process_cookie_reply(Some(&psk), &pk, &mac1, &reply).unwrap();
+
+ assert_eq!(&cookie, &cookie1);
+ }
+}
diff --git a/src/protocol/handshake.rs b/src/protocol/handshake.rs
new file mode 100644
index 0000000..f8023ce
--- /dev/null
+++ b/src/protocol/handshake.rs
@@ -0,0 +1,298 @@
+// Copyright 2017 Sopium
+
+// This file is part of WireGuard.rs.
+
+// WireGuard.rs is free software: you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+
+// WireGuard.rs is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with WireGuard.rs. If not, see <https://www.gnu.org/licenses/>.
+
+extern crate blake2_rfc;
+extern crate noise_protocol;
+extern crate noise_sodiumoxide;
+extern crate sodiumoxide;
+extern crate tai64;
+
+use self::blake2_rfc::blake2s::Blake2s;
+use self::noise_protocol::*;
+use self::noise_protocol::patterns::noise_ik;
+use self::noise_sodiumoxide::{ChaCha20Poly1305, X25519};
+use self::sodiumoxide::utils::memcmp;
+use self::tai64::TAI64N;
+use protocol::*;
+
+const PROLOGUE: &'static [u8] = b"WireGuard v0 zx2c4 Jason@zx2c4.com";
+
+pub type HS = HandshakeState<X25519, ChaCha20Poly1305, NoiseBlake2s>;
+
+#[derive(Clone)]
+pub struct NoiseBlake2s(Blake2s);
+
+impl Default for NoiseBlake2s {
+ fn default() -> Self {
+ NoiseBlake2s(Blake2s::new(32))
+ }
+}
+
+impl Hash for NoiseBlake2s {
+ type Output = [u8; 32];
+ type Block = [u8; 64];
+
+ fn name() -> &'static str {
+ "BLAKE2s"
+ }
+
+ fn input(&mut self, data: &[u8]) {
+ self.0.update(data);
+ }
+
+ fn result(&mut self) -> Self::Output {
+ Self::Output::from_slice(self.0
+ .clone()
+ .finalize()
+ .as_bytes())
+ }
+}
+
+/// Calc mac/hash of some data, with an optional key.
+///
+/// This is exactly the mac function defined in WireGuard paper.
+fn mac<K>(key: Option<K>, data: &[&[u8]]) -> [u8; 16]
+ where K: AsRef<[u8]>
+{
+ let mut mac = [0u8; 16];
+ let mut blake2s = Blake2s::with_key(16, key.as_ref().map_or(&[], |k| k.as_ref()));
+ for d in data {
+ blake2s.update(d);
+ }
+ mac.copy_from_slice(blake2s.finalize().as_bytes());
+ mac
+}
+
+/// Generate handshake initiation message.
+///
+/// Will generate a new ephemeral key and use current timestamp.
+///
+/// Returns: Message, noise handshake state.
+pub fn initiate(wg: &WgInfo, peer: &PeerInfo, self_index: Id) -> ([u8; 148], HS) {
+ let mut msg = [0u8; 148];
+
+ let mut hs = {
+ let mut hsbuilder = HandshakeStateBuilder::<X25519>::new();
+ hsbuilder.set_pattern(noise_ik());
+ hsbuilder.set_is_initiator(true);
+ hsbuilder.set_prologue(PROLOGUE);
+ if let Some(ref psk) = wg.psk {
+ hsbuilder.set_psk(psk.as_slice());
+ }
+ hsbuilder.set_s(wg.key.clone());
+ hsbuilder.set_rs(peer.peer_pubkey);
+ hsbuilder.build_handshake_state()
+ };
+
+ // Type and reserved zeros.
+ msg[0..4].copy_from_slice(&[1, 0, 0, 0]);
+ // Self index.
+ msg[4..8].copy_from_slice(self_index.as_slice());
+
+ // Noise part: e, s, timestamp.
+ let timestamp = TAI64N::now();
+ hs.write_message(&timestamp.to_external(), &mut msg[8..116]);
+
+ // Mac1.
+ let mac1 = mac(wg.psk.as_ref(),
+ &[peer.peer_pubkey.as_slice(), &msg[..116]]);
+ msg[116..132].copy_from_slice(&mac1);
+
+ (msg, hs)
+}
+
+pub struct InitProcessResult {
+ pub peer_id: Id,
+ pub timestamp: TAI64N,
+ pub handshake_state: HS,
+}
+
+/// Process a handshake initiation message.
+///
+/// Will generate a new ephemeral key.
+pub fn process_initiation(wg: &WgInfo, msg: &[u8]) -> Result<InitProcessResult, ()> {
+ if msg.len() != 148 {
+ return Err(());
+ }
+
+ // Check mac1.
+ let mac1 = mac(wg.psk.as_ref(), &[&wg.pubkey, &msg[..116]]);
+ if !memcmp(&mac1, &msg[116..132]) {
+ return Err(());
+ }
+
+ // Check type and zeros.
+ if &msg[0..4] != &[1, 0, 0, 0] {
+ return Err(());
+ }
+
+ // Peer index.
+ let peer_index = Id::from_slice(&msg[4..8]);
+
+ let mut hs: HS = {
+ let mut hsbuilder = HandshakeStateBuilder::<X25519>::new();
+ hsbuilder.set_is_initiator(false);
+ hsbuilder.set_prologue(PROLOGUE);
+ hsbuilder.set_pattern(noise_ik());
+ if let Some(ref psk) = wg.psk {
+ hsbuilder.set_psk(psk);
+ }
+ hsbuilder.set_s(wg.key.clone());
+ hsbuilder.build_handshake_state()
+ };
+
+ // Noise message, contains encrypted timestamp.
+ let mut timestamp = [0u8; 12];
+ hs.read_message(&msg[8..116], &mut timestamp).map_err(|_| ())?;
+ let timestamp = TAI64N::from_external(&timestamp).ok_or(())?;
+
+ Ok(InitProcessResult {
+ peer_id: peer_index,
+ timestamp: timestamp,
+ handshake_state: hs,
+ })
+}
+
+/// Generate handshake response message.
+pub fn responde(wg: &WgInfo, result: &mut InitProcessResult, self_id: Id) -> [u8; 92] {
+ let mut response = [0u8; 92];
+
+ // Type and zeros.
+ response[0..4].copy_from_slice(&[2, 0, 0, 0]);
+ response[4..8].copy_from_slice(self_id.as_slice());
+ response[8..12].copy_from_slice(result.peer_id.as_slice());
+
+ let mut hs = &mut result.handshake_state;
+
+ hs.write_message(&[], &mut response[12..60]);
+
+ let mac1 = mac(wg.psk.as_ref(), &[hs.get_rs().as_ref().unwrap(), &response[..60]]);
+ response[60..76].copy_from_slice(&mac1);
+
+ response
+}
+
+/// Process handshake response message.
+///
+/// Returns peer index.
+pub fn process_response(wg: &WgInfo, hs: &mut HS, msg: &[u8]) -> Result<Id, ()> {
+ if msg.len() != 92 {
+ return Err(());
+ }
+
+ // Check mac1.
+ let mac1 = mac(wg.psk.as_ref(), &[&wg.pubkey, &msg[..60]]);
+
+ if !memcmp(&mac1, &msg[60..76]) {
+ return Err(());
+ }
+
+ // Check type and zeros.
+ if &msg[0..4] != &[2, 0, 0, 0] {
+ return Err(());
+ }
+
+ // Peer index.
+ let peer_index = Id::from_slice(&msg[4..8]);
+
+ // msg[8..12] is self index, skip.
+
+ let mut out = [];
+
+ hs.read_message(&msg[12..60], &mut out).map_err(|_| ())?;
+
+ Ok(peer_index)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn wg_handshake_init_responde() {
+ let k = X25519::genkey();
+ let init = WgInfo {
+ psk: None,
+ pubkey: X25519::pubkey(&k),
+ key: k,
+ };
+
+ let k = X25519::genkey();
+ let resp = WgInfo {
+ psk: None,
+ pubkey: X25519::pubkey(&k),
+ key: k,
+ };
+
+ let init_peer = PeerInfo {
+ peer_pubkey: Clone::clone(&resp.pubkey),
+ endpoint: None,
+ allowed_ips: vec![],
+ keep_alive_interval: None,
+ };
+
+ let si = Id::gen();
+ let (m0, mut ihs) = initiate(&init, &init_peer, si);
+ let mut result0 = process_initiation(&resp, &m0).unwrap();
+ let ri = Id::gen();
+ let m1 = responde(&resp, &mut result0, ri);
+ let ri1 = process_response(&init, &mut ihs, &m1).unwrap();
+
+ assert_eq!(result0.peer_id, si);
+ assert_eq!(ri1, ri);
+
+ assert_eq!(ihs.get_hash(), result0.handshake_state.get_hash());
+ }
+
+ #[test]
+ fn wg_handshake_init_responde_with_psk() {
+ let psk = [0xc7; 32];
+
+ let k = X25519::genkey();
+ let init = WgInfo {
+ psk: Some(psk),
+ pubkey: X25519::pubkey(&k),
+ key: k,
+ };
+
+ let k = X25519::genkey();
+ let resp = WgInfo {
+ psk: Some(psk),
+ pubkey: X25519::pubkey(&k),
+ key: k,
+ };
+
+ let init_peer = PeerInfo {
+ peer_pubkey: Clone::clone(&resp.pubkey),
+ endpoint: None,
+ allowed_ips: vec![],
+ keep_alive_interval: None,
+ };
+
+ let si = Id::gen();
+ let (m0, mut ihs) = initiate(&init, &init_peer, si);
+ let mut result0 = process_initiation(&resp, &m0).unwrap();
+ let ri = Id::gen();
+ let m1 = responde(&resp, &mut result0, ri);
+ let ri1 = process_response(&init, &mut ihs, &m1).unwrap();
+
+ assert_eq!(result0.peer_id, si);
+ assert_eq!(ri1, ri);
+
+ assert_eq!(ihs.get_hash(), result0.handshake_state.get_hash());
+ }
+}
diff --git a/src/protocol/ip.rs b/src/protocol/ip.rs
new file mode 100644
index 0000000..f6b9920
--- /dev/null
+++ b/src/protocol/ip.rs
@@ -0,0 +1,51 @@
+// Copyright 2017 Sopium
+
+// This file is part of WireGuard.rs.
+
+// WireGuard.rs is free software: you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+
+// WireGuard.rs is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with WireGuard.rs. If not, see <https://www.gnu.org/licenses/>.
+
+extern crate pnet;
+
+use self::pnet::packet::ipv4::Ipv4Packet;
+use self::pnet::packet::ipv6::Ipv6Packet;
+use std::net::IpAddr;
+
+// IP packet parsing.
+
+/// Parses IPv4/v6 packet, returns total length, source, destination.
+pub fn parse_ip_packet(packet: &[u8]) -> Result<(u16, IpAddr, IpAddr), ()> {
+ if packet.len() < 20 {
+ return Err(());
+ }
+
+ let v = packet[0] >> 4;
+
+ if v == 4 {
+ // IPv4.
+ let p = Ipv4Packet::new(packet).ok_or(())?;
+ let len = p.get_total_length();
+ let src = p.get_source();
+ let dst = p.get_destination();
+ Ok((len, IpAddr::V4(src), IpAddr::V4(dst)))
+ } else if v == 6 {
+ // IPv6.
+ let p = Ipv6Packet::new(packet).ok_or(())?;
+ let len = p.get_payload_length() + 40;
+ let src = p.get_source();
+ let dst = p.get_destination();
+ Ok((len, IpAddr::V6(src), IpAddr::V6(dst)))
+ } else {
+ Err(())
+ }
+}
diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs
new file mode 100644
index 0000000..c8eff1b
--- /dev/null
+++ b/src/protocol/mod.rs
@@ -0,0 +1,46 @@
+// Copyright 2017 Sopium
+
+// This file is part of WireGuard.rs.
+
+// WireGuard.rs is free software: you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+
+// WireGuard.rs is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with WireGuard.rs. If not, see <https://www.gnu.org/licenses/>.
+
+//! WireGuard protocol implementation.
+
+/// Handshake messages generation and parsing.
+mod handshake;
+/// Anti-Replay algorithm.
+mod anti_replay;
+/// Cookie reply messages generation and parsing.
+mod cookie;
+/// Common types.
+mod types;
+/// IP packet parsing.
+mod ip;
+/// The timer state machine, and actual IO stuff.
+mod controller;
+/// A generic timer, but optimised for operations mostly used in WG.
+mod timer;
+
+/// Re-export some types and functions from other crates, so users
+/// of this module won't have to manually pull in all these crates.
+pub mod re_exports;
+
+use self::anti_replay::*;
+pub use self::controller::*;
+use self::cookie::*;
+use self::handshake::*;
+use self::ip::*;
+use self::timer::*;
+pub use self::types::{WgInfo, PeerInfo};
+use self::types::*;
diff --git a/src/protocol/re_exports.rs b/src/protocol/re_exports.rs
new file mode 100644
index 0000000..08a3b0c
--- /dev/null
+++ b/src/protocol/re_exports.rs
@@ -0,0 +1,23 @@
+// Copyright 2017 Sopium
+
+// This file is part of WireGuard.rs.
+
+// WireGuard.rs is free software: you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+
+// WireGuard.rs is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with WireGuard.rs. If not, see <https://www.gnu.org/licenses/>.
+
+extern crate noise_protocol;
+extern crate noise_sodiumoxide;
+
+pub use self::noise_protocol::{Cipher, DH, Hash, U8Array};
+pub use self::noise_sodiumoxide::{ChaCha20Poly1305, Sensitive, X25519};
+pub use self::noise_sodiumoxide::init as sodium_init;
diff --git a/src/protocol/timer.rs b/src/protocol/timer.rs
new file mode 100644
index 0000000..d2bcfcb
--- /dev/null
+++ b/src/protocol/timer.rs
@@ -0,0 +1,197 @@
+// Copyright 2017 Sopium
+
+// This file is part of WireGuard.rs.
+
+// WireGuard.rs is free software: you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+
+// WireGuard.rs is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with WireGuard.rs. If not, see <https://www.gnu.org/licenses/>.
+
+use std::collections::HashSet;
+use std::hash::{Hash, Hasher};
+use std::ops::Deref;
+use std::sync::{Arc, Mutex};
+use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
+use std::thread;
+use std::time::{Duration, Instant};
+
+type Action = Box<Fn() + Send + Sync>;
+
+struct Timer {
+ activated: AtomicBool,
+ // Actually, this is not used outside the big whole wheel mutex.
+ // But how to tell the compiler that???
+ rounds: AtomicUsize,
+ action: Action,
+}
+
+pub struct TimerHandle {
+ controller: Arc<Mutex<Wheel>>,
+ pos: AtomicUsize,
+ timer: ArcTimer,
+}
+
+// A single round will be ~ 16 seconds.
+// WireGuard timers are mostly in this range.
+const WHEEL_SLOTS: usize = 128;
+const TICK_MS: usize = 128;
+
+// This is hashed timing wheel.
+pub struct TimerController(Arc<Mutex<Wheel>>);
+
+struct Wheel {
+ // Usually a linked list is used in each slot.
+ // Use hash table for now, for implementation simplicity.
+ // It's slower but should still have same complexity.
+ wheel: Vec<HashSet<ArcTimer>>,
+ cur: usize,
+}
+
+impl TimerController {
+ pub fn new() -> Self {
+ let con = Arc::new(Mutex::new(Wheel {
+ wheel: ::std::iter::repeat(HashSet::new()).take(WHEEL_SLOTS).collect(),
+ cur: 0,
+ }));
+ let con1 = con.clone();
+
+ thread::Builder::new().name("timer".to_string()).spawn(move || {
+ loop {
+ let tick_start = Instant::now();
+
+ let mut to_run = Vec::new();
+ let mut wheel = con.lock().unwrap();
+ let cur = wheel.cur;
+ wheel.cur = (wheel.cur + 1) % WHEEL_SLOTS;
+ {
+ let slot = &mut wheel.wheel[cur];
+
+ slot.retain(|t| {
+ Arc::strong_count(&t.0) > 1
+ });
+
+ for t in slot.iter() {
+ // `fetch_sub` always wraps, doesn't it?
+ let rounds = t.rounds.fetch_sub(1, Ordering::Relaxed);
+ if t.activated.load(Ordering::Relaxed) && rounds == 0 {
+ to_run.push(t.clone());
+ t.activated.store(false, Ordering::Relaxed);
+ }
+ }
+ }
+ drop(wheel);
+
+ for t in to_run {
+ (t.action)();
+ }
+
+ let spent = tick_start.elapsed();
+ #[allow(non_snake_case)]
+ let TICK = Duration::from_millis(TICK_MS as u64);
+
+ if spent >= TICK {
+ warn!("timer tick processing time exceeds TICK!");
+ } else {
+ thread::sleep(TICK - spent);
+ }
+ }
+ }).unwrap();
+
+ TimerController(con1)
+ }
+
+ pub fn register_delay(&self, delay: Duration, action: Action) -> TimerHandle {
+ let (offset, rounds) = calc_offset_and_rounds(delay);
+
+ let mut wheel = self.0.lock().unwrap();
+ let pos = (wheel.cur + offset) % WHEEL_SLOTS;
+
+ let timer = Arc::new(Timer {
+ activated: AtomicBool::new(false),
+ rounds: AtomicUsize::new(rounds),
+ action: action,
+ });
+
+ wheel.wheel[pos].insert(ArcTimer(timer.clone()));
+
+ TimerHandle {
+ controller: self.0.clone(),
+ pos: AtomicUsize::new(pos),
+ timer: ArcTimer(timer),
+ }
+ }
+}
+
+impl TimerHandle {
+ pub fn activate(&self) {
+ self.timer.activated.store(true, Ordering::Relaxed);
+ }
+
+ pub fn de_activate(&self) {
+ self.timer.activated.store(false, Ordering::Relaxed);
+ }
+
+ pub fn adjust_and_activate(&self, secs: u64) {
+ let (offset, rounds) = calc_offset_and_rounds(Duration::from_secs(secs));
+
+ let mut wheel = self.controller.lock().unwrap();
+ let old_pos = self.pos.load(Ordering::Relaxed);
+ let new_pos = (wheel.cur + offset) % WHEEL_SLOTS;
+ self.pos.store(new_pos, Ordering::Relaxed);
+ self.timer.rounds.store(rounds, Ordering::Relaxed);
+
+ let t = wheel.wheel[old_pos].take(&self.timer).unwrap();
+ wheel.wheel[new_pos].insert(t);
+
+ self.timer.activated.store(true, Ordering::Release);
+ }
+
+ pub fn adjust_and_activate_if_not_activated(&self, secs: u64) {
+ if !self.timer.activated.load(Ordering::Relaxed) {
+ self.adjust_and_activate(secs);
+ }
+ }
+}
+
+fn calc_offset_and_rounds(delay: Duration) -> (usize, usize) {
+ let delay_ms = delay.as_secs() * 1000 + delay.subsec_nanos() as u64 / 1000000;
+ let ticks = ::std::cmp::max(delay_ms as usize / TICK_MS, 1);
+ let offset = ticks % WHEEL_SLOTS;
+ let rounds = ticks / WHEEL_SLOTS;
+ (offset, rounds)
+}
+
+/// Hash and Eq by pointer address.
+#[derive(Clone)]
+struct ArcTimer(Arc<Timer>);
+
+impl Deref for ArcTimer {
+ type Target = Timer;
+
+ fn deref(&self) -> &Timer {
+ self.0.deref()
+ }
+}
+
+impl Hash for ArcTimer {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ (self.0.deref() as *const Timer).hash(state);
+ }
+}
+
+impl PartialEq for ArcTimer {
+ fn eq(&self, other: &ArcTimer) -> bool {
+ Arc::ptr_eq(&self.0, &other.0)
+ }
+}
+
+impl Eq for ArcTimer {
+}
diff --git a/src/protocol/types.rs b/src/protocol/types.rs
new file mode 100644
index 0000000..cc8db4d
--- /dev/null
+++ b/src/protocol/types.rs
@@ -0,0 +1,139 @@
+// Copyright 2017 Sopium
+
+// This file is part of WireGuard.rs.
+
+// WireGuard.rs is free software: you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+
+// WireGuard.rs is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with WireGuard.rs. If not, see <https://www.gnu.org/licenses/>.
+
+extern crate noise_protocol;
+extern crate noise_sodiumoxide;
+extern crate sodiumoxide;
+
+use self::noise_protocol::DH;
+use self::noise_sodiumoxide::X25519;
+use self::sodiumoxide::randombytes::randombytes_into;
+use std::net::{IpAddr, SocketAddr};
+use std::ops::Deref;
+use std::time::SystemTime;
+
+/// X25519 private key.
+pub type X25519Key = <X25519 as DH>::Key;
+/// X25519 pubkey key.
+pub type X25519Pubkey = <X25519 as DH>::Pubkey;
+
+/// Config info about a WireGuard peer.
+#[derive(Clone)]
+pub struct PeerInfo {
+ /// Peer public key.
+ pub peer_pubkey: X25519Pubkey,
+ /// Peer endpoint.
+ pub endpoint: Option<SocketAddr>,
+ /// Allowed source IPs.
+ pub allowed_ips: Vec<(IpAddr, u32)>,
+ /// Persistent keep-alive interval.
+ /// Valid values: 1 - 0xfffe.
+ pub keep_alive_interval: Option<u16>,
+}
+
+/// Config info about a WireGuard interface.
+pub struct WgInfo {
+ /// Optional pre-shared key.
+ pub psk: Option<[u8; 32]>,
+ /// Self private key.
+ pub key: X25519Key,
+ /// Self public key.
+ /// Should correspond to self private key.
+ pub pubkey: X25519Pubkey,
+}
+
+/// State of WireGuard interface.
+pub struct WgStateOut {
+ /// Self public key.
+ pub public_key: X25519Pubkey,
+ /// Self private key.
+ pub private_key: X25519Key,
+ /// Pre-shared key.
+ pub preshared_key: Option<[u8; 32]>,
+ /// Peers.
+ pub peers: Vec<PeerStateOut>,
+}
+
+/// State of a peer.
+pub struct PeerStateOut {
+ /// Public key.
+ pub public_key: X25519Pubkey,
+ /// Endpoint.
+ pub endpoint: Option<SocketAddr>,
+ /// Last handshake time.
+ pub last_handshake_time: Option<SystemTime>,
+ /// Received bytes.
+ pub rx_bytes: u64,
+ /// Sent bytes.
+ pub tx_bytes: u64,
+ /// Persistent keep-alive interval.
+ pub persistent_keepalive_interval: Option<u16>,
+ /// Allowed IP addresses.
+ pub allowed_ips: Vec<(IpAddr, u32)>,
+}
+
+impl WgInfo {
+ /// Create a new `WgInfo`.
+ pub fn new(psk: Option<[u8; 32]>, key: X25519Key) -> Self {
+ let pk = <X25519 as DH>::pubkey(&key);
+ WgInfo {
+ psk: psk,
+ key: key,
+ pubkey: pk,
+ }
+ }
+}
+
+/// Sender index or receiver index.
+///
+/// WireGuard treats an index as a `u32` in little endian.
+/// Why not just treat it as a 4-byte array?
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct Id(pub [u8; 4]);
+
+impl Id {
+ /// Generate a new random ID.
+ pub fn gen() -> Id {
+ let mut id = [0u8; 4];
+ randombytes_into(&mut id);
+ Id(id)
+ }
+
+ /// Create Id from a slice.
+ ///
+ /// # Panics
+ ///
+ /// Slice must be 4 bytes long.
+ pub fn from_slice(id: &[u8]) -> Id {
+ let mut ret = Id([0u8; 4]);
+ ret.0.copy_from_slice(id);
+ ret
+ }
+
+ /// As slice.
+ pub fn as_slice(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+impl Deref for Id {
+ type Target = [u8];
+
+ fn deref(&self) -> &[u8] {
+ self.as_slice()
+ }
+}