diff options
author | sopium <sopium@mysterious.site> | 2017-03-22 16:46:06 +0800 |
---|---|---|
committer | Sascha Grunert <saschagrunert@users.noreply.github.com> | 2017-03-23 17:55:05 +0100 |
commit | 724772ec6dbc88cc88f73d87120307635824c362 (patch) | |
tree | 6aa883f506158e7ab7cf79fd557bddf5329c5ab9 /src/protocol/controller.rs | |
parent | Warn users, due to already incoming emails (diff) | |
download | wireguard-rs-724772ec6dbc88cc88f73d87120307635824c362.tar.xz wireguard-rs-724772ec6dbc88cc88f73d87120307635824c362.zip |
Add sopium's protocol implementation and wg-standalone
Diffstat (limited to 'src/protocol/controller.rs')
-rw-r--r-- | src/protocol/controller.rs | 1107 |
1 files changed, 1107 insertions, 0 deletions
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(()) + } +} |