use boringtun::noise::{Tunn, TunnResult};
use boringtun::x25519::{PublicKey, StaticSecret};
use tracing::debug;
const SCRATCH_LEN: usize = 65_536;
pub struct WgTunnel {
tunn: Tunn,
scratch: Box<[u8]>,
}
impl WgTunnel {
#[must_use]
pub fn new(local_secret: &StaticSecret, peer_public: &PublicKey, index: u32) -> Self {
let secret = StaticSecret::from(local_secret.to_bytes());
let tunn = Tunn::new(secret, *peer_public, None, None, index, None);
Self {
tunn,
scratch: vec![0u8; SCRATCH_LEN].into_boxed_slice(),
}
}
pub fn decapsulate(
&mut self,
datagram: &[u8],
mut to_peer: impl FnMut(&[u8]),
mut to_host: impl FnMut(&[u8]),
) {
if self.decapsulate_once(datagram, &mut to_peer, &mut to_host) {
while self.decapsulate_once(&[], &mut to_peer, &mut to_host) {}
}
}
fn decapsulate_once(
&mut self,
input: &[u8],
to_peer: &mut impl FnMut(&[u8]),
to_host: &mut impl FnMut(&[u8]),
) -> bool {
match self.tunn.decapsulate(None, input, &mut self.scratch) {
TunnResult::WriteToNetwork(b) => {
to_peer(b);
true
}
TunnResult::WriteToTunnelV4(b, _) | TunnResult::WriteToTunnelV6(b, _) => {
to_host(b);
false
}
TunnResult::Done => false,
TunnResult::Err(e) => {
debug!(?e, "WireGuard decapsulate error");
false
}
}
}
pub fn encapsulate(&mut self, packet: &[u8], mut to_peer: impl FnMut(&[u8])) {
match self.tunn.encapsulate(packet, &mut self.scratch) {
TunnResult::WriteToNetwork(b) => to_peer(b),
TunnResult::Err(e) => debug!(?e, "WireGuard encapsulate error"),
TunnResult::Done
| TunnResult::WriteToTunnelV4(..)
| TunnResult::WriteToTunnelV6(..) => {}
}
}
pub fn tick(&mut self, mut to_peer: impl FnMut(&[u8])) {
if let TunnResult::WriteToNetwork(b) = self.tunn.update_timers(&mut self.scratch) {
to_peer(b);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tunnel::keys::WgKeypair;
fn ipv4_packet(marker: u8) -> Vec<u8> {
vec![
0x45, 0x00, 0x00, 0x18, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, 0x00, 0x00, 10, 0, 0, 2, 1, 1, 1, 1, marker, marker, marker, marker, ]
}
fn collect(f: impl FnOnce(&mut dyn FnMut(&[u8]))) -> Vec<u8> {
let mut out = Vec::new();
f(&mut |b| out.extend_from_slice(b));
out
}
#[test]
fn handshake_then_bidirectional_data() {
let npxc_kp = WgKeypair::generate();
let guest_kp = WgKeypair::generate();
let mut npxc = WgTunnel::new(npxc_kp.secret(), guest_kp.public(), 1);
let mut guest = WgTunnel::new(guest_kp.secret(), npxc_kp.public(), 2);
let init = collect(|peer| guest.encapsulate(&ipv4_packet(0x01), peer));
assert!(!init.is_empty(), "expected a handshake initiation");
let mut host_during_hs = 0usize;
let resp = {
let mut out = Vec::new();
npxc.decapsulate(&init, |b| out.extend_from_slice(b), |_| host_during_hs += 1);
out
};
assert_eq!(host_during_hs, 0, "no host packet during the handshake");
assert!(!resp.is_empty(), "expected a handshake response");
let mut flushed: Vec<Vec<u8>> = Vec::new();
guest.decapsulate(&resp, |b| flushed.push(b.to_vec()), |_| {});
for datagram in &flushed {
npxc.decapsulate(datagram, |_| {}, |_| {});
}
let up = ipv4_packet(0xAA);
let enc_up = collect(|peer| guest.encapsulate(&up, peer));
let mut got_up = Vec::new();
npxc.decapsulate(&enc_up, |_| {}, |b| got_up.extend_from_slice(b));
assert_eq!(got_up, up, "npxc must decrypt the upstream packet");
let down = ipv4_packet(0xBB);
let enc_down = collect(|peer| npxc.encapsulate(&down, peer));
let mut got_down = Vec::new();
guest.decapsulate(&enc_down, |_| {}, |b| got_down.extend_from_slice(b));
assert_eq!(got_down, down, "guest must decrypt the downstream packet");
}
#[test]
fn data_before_handshake_yields_no_host_packets() {
let npxc_kp = WgKeypair::generate();
let guest_kp = WgKeypair::generate();
let mut npxc = WgTunnel::new(npxc_kp.secret(), guest_kp.public(), 1);
let mut host_packets = 0usize;
npxc.decapsulate(&[0u8; 64], |_| {}, |_| host_packets += 1);
assert_eq!(host_packets, 0);
}
}