#![allow(dead_code)]
use boringtun::noise::{Tunn, TunnResult};
use dashmap::DashMap;
use parking_lot::RwLock;
use std::net::{IpAddr, SocketAddr};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::net::UdpSocket;
use tokio::sync::Mutex as AsyncMutex;
use tokio::task::JoinHandle;
use crate::OverlayError;
#[async_trait::async_trait]
pub(crate) trait IpChannel: Send + Sync + 'static {
async fn recv_packet(&self, buf: &mut [u8]) -> Result<usize, OverlayError>;
async fn send_packet(&self, pkt: &[u8]) -> Result<(), OverlayError>;
fn mtu(&self) -> u32;
}
#[derive(Clone)]
pub(crate) struct PeerState {
pub(crate) tunn: Arc<AsyncMutex<Tunn>>,
pub(crate) endpoint: Arc<RwLock<Option<SocketAddr>>>,
pub(crate) last_handshake_sec: Arc<AtomicU64>,
pub(crate) allowed_ips: Arc<Vec<ipnet::IpNet>>,
pub(crate) persistent_keepalive: Option<u16>,
}
pub(crate) fn decode_key_b64(b64: &str) -> Result<[u8; 32], Box<dyn std::error::Error>> {
use base64::{engine::general_purpose::STANDARD, Engine as _};
let bytes = STANDARD.decode(b64)?;
if bytes.len() != 32 {
return Err(format!(
"invalid WireGuard key length: expected 32 bytes, got {}",
bytes.len()
)
.into());
}
let mut out = [0u8; 32];
out.copy_from_slice(&bytes);
Ok(out)
}
pub(crate) fn parse_dst_ip(packet: &[u8]) -> Option<IpAddr> {
if packet.is_empty() {
return None;
}
match packet[0] >> 4 {
4 if packet.len() >= 20 => {
let b: [u8; 4] = packet[16..20].try_into().ok()?;
Some(IpAddr::from(b))
}
6 if packet.len() >= 40 => {
let b: [u8; 16] = packet[24..40].try_into().ok()?;
Some(IpAddr::from(b))
}
_ => None,
}
}
pub(crate) fn build_tunn(
our_priv: &[u8; 32],
peer_pub: &[u8; 32],
preshared: Option<[u8; 32]>,
persistent_keepalive: Option<u16>,
) -> Tunn {
let priv_secret = boringtun::x25519::StaticSecret::from(*our_priv);
let peer_pub_key = boringtun::x25519::PublicKey::from(*peer_pub);
Tunn::new(
priv_secret,
peer_pub_key,
preshared,
persistent_keepalive,
0,
None,
)
}
pub(crate) async fn ingress_loop<C: IpChannel>(
udp: Arc<UdpSocket>,
chan: Arc<C>,
peers: Arc<DashMap<[u8; 32], PeerState>>,
) {
let mut inbuf = vec![0u8; 65536];
loop {
let (n, src) = match udp.recv_from(&mut inbuf).await {
Ok(p) => p,
Err(e) => {
tracing::error!(error = %e, "UDP recv failed; ingress loop exiting");
break;
}
};
let snapshot: Vec<([u8; 32], PeerState)> = peers
.iter()
.map(|e| (*e.key(), e.value().clone()))
.collect();
for (pk, state) in snapshot {
let mut out = vec![0u8; 65536];
let mut handled = false;
{
let mut tunn = state.tunn.lock().await;
match tunn.decapsulate(Some(src.ip()), &inbuf[..n], &mut out) {
TunnResult::WriteToTunnelV4(pkt, _) | TunnResult::WriteToTunnelV6(pkt, _) => {
let pkt_owned = pkt.to_vec();
drop(tunn);
if let Err(e) = chan.send_packet(&pkt_owned).await {
tracing::warn!(error = %e, "Wintun send failed");
}
*state.endpoint.write() = Some(src);
state.last_handshake_sec.store(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
Ordering::Relaxed,
);
handled = true;
}
TunnResult::WriteToNetwork(resp) => {
let resp_owned = resp.to_vec();
drop(tunn);
if let Err(e) = udp.send_to(&resp_owned, src).await {
tracing::warn!(error = %e, "UDP reply send failed");
}
*state.endpoint.write() = Some(src);
handled = true;
}
TunnResult::Done | TunnResult::Err(_) => {
}
}
}
if handled {
loop {
let mut drain = vec![0u8; 65536];
let mut tunn = state.tunn.lock().await;
match tunn.decapsulate(None, &[], &mut drain) {
TunnResult::WriteToNetwork(resp) => {
let resp_owned = resp.to_vec();
drop(tunn);
if let Err(e) = udp.send_to(&resp_owned, src).await {
tracing::warn!(error = %e, "UDP drain send failed");
}
}
TunnResult::WriteToTunnelV4(pkt, _)
| TunnResult::WriteToTunnelV6(pkt, _) => {
let pkt_owned = pkt.to_vec();
drop(tunn);
if let Err(e) = chan.send_packet(&pkt_owned).await {
tracing::warn!(error = %e, "Wintun drain send failed");
}
}
TunnResult::Done | TunnResult::Err(_) => break,
}
}
let _ = pk; break;
}
}
}
}
pub(crate) async fn egress_loop<C: IpChannel>(
chan: Arc<C>,
udp: Arc<UdpSocket>,
peers: Arc<DashMap<[u8; 32], PeerState>>,
) {
let mut buf = vec![0u8; 65536];
loop {
let n = match chan.recv_packet(&mut buf).await {
Ok(n) => n,
Err(e) => {
tracing::error!(error = %e, "Wintun recv failed; egress loop exiting");
break;
}
};
let Some(dst_ip) = parse_dst_ip(&buf[..n]) else {
continue;
};
let state = peers.iter().find_map(|entry| {
if entry
.value()
.allowed_ips
.iter()
.any(|net| net.contains(&dst_ip))
{
Some(entry.value().clone())
} else {
None
}
});
let Some(state) = state else {
tracing::trace!(%dst_ip, "no matching overlay peer");
continue;
};
let endpoint = *state.endpoint.read();
let Some(endpoint) = endpoint else {
tracing::trace!(%dst_ip, "peer has no endpoint yet; dropping");
continue;
};
let mut out = vec![0u8; 65536 + 32];
let mut tunn = state.tunn.lock().await;
match tunn.encapsulate(&buf[..n], &mut out) {
TunnResult::WriteToNetwork(pkt) => {
let pkt_owned = pkt.to_vec();
drop(tunn);
if let Err(e) = udp.send_to(&pkt_owned, endpoint).await {
tracing::warn!(error = %e, "UDP send failed");
}
}
TunnResult::Done
| TunnResult::WriteToTunnelV4(_, _)
| TunnResult::WriteToTunnelV6(_, _) => {
}
TunnResult::Err(e) => {
tracing::warn!(?e, "encapsulate error");
}
}
}
}
pub(crate) async fn timers_loop(udp: Arc<UdpSocket>, peers: Arc<DashMap<[u8; 32], PeerState>>) {
let mut interval = tokio::time::interval(Duration::from_millis(250));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
interval.tick().await;
let snapshot: Vec<PeerState> = peers.iter().map(|e| e.value().clone()).collect();
for state in snapshot {
let endpoint = *state.endpoint.read();
let mut out = vec![0u8; 148];
let mut tunn = state.tunn.lock().await;
match tunn.update_timers(&mut out) {
TunnResult::WriteToNetwork(pkt) => {
let pkt_owned = pkt.to_vec();
drop(tunn);
if let Some(ep) = endpoint {
if let Err(e) = udp.send_to(&pkt_owned, ep).await {
tracing::debug!(error = %e, "timers UDP send failed");
}
}
}
TunnResult::Done
| TunnResult::WriteToTunnelV4(_, _)
| TunnResult::WriteToTunnelV6(_, _) => {}
TunnResult::Err(e) => {
tracing::debug!(?e, "update_timers error");
}
}
}
}
}
pub(crate) struct TunnDriver {
ingress: JoinHandle<()>,
egress: JoinHandle<()>,
timers: JoinHandle<()>,
}
impl TunnDriver {
pub(crate) fn spawn<C: IpChannel>(
udp: Arc<UdpSocket>,
chan: Arc<C>,
peers: Arc<DashMap<[u8; 32], PeerState>>,
) -> Self {
let peers_ingress = peers.clone();
let udp_ingress = udp.clone();
let chan_ingress = chan.clone();
let ingress = tokio::spawn(async move {
ingress_loop(udp_ingress, chan_ingress, peers_ingress).await;
});
let peers_egress = peers.clone();
let udp_egress = udp.clone();
let egress = tokio::spawn(async move {
egress_loop(chan, udp_egress, peers_egress).await;
});
let timers = tokio::spawn(async move {
timers_loop(udp, peers).await;
});
Self {
ingress,
egress,
timers,
}
}
pub(crate) fn abort_all(&self) {
self.ingress.abort();
self.egress.abort();
self.timers.abort();
}
}
#[cfg(test)]
mod tests {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[test]
fn test_parse_dst_ip_v4() {
let mut pkt = vec![0u8; 20];
pkt[0] = 0x45;
pkt[16..20].copy_from_slice(&[10, 0, 0, 7]);
assert_eq!(
super::parse_dst_ip(&pkt),
Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 7)))
);
}
#[test]
fn test_parse_dst_ip_v6() {
let mut pkt = vec![0u8; 40];
pkt[0] = 0x60;
pkt[24] = 0xfd;
pkt[25] = 0x00;
pkt[39] = 0x01;
let expected = IpAddr::V6(Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1));
assert_eq!(super::parse_dst_ip(&pkt), Some(expected));
}
#[test]
fn test_parse_dst_ip_truncated_returns_none() {
let pkt = vec![0x45u8; 10];
assert_eq!(super::parse_dst_ip(&pkt), None);
assert_eq!(super::parse_dst_ip(&[]), None);
}
#[test]
fn test_parse_dst_ip_unknown_version_returns_none() {
let pkt = vec![0x70u8; 64];
assert_eq!(super::parse_dst_ip(&pkt), None);
}
#[test]
fn test_decode_key_b64_roundtrip() {
use base64::{engine::general_purpose::STANDARD, Engine as _};
let raw = [0x42u8; 32];
let b64 = STANDARD.encode(raw);
let decoded = super::decode_key_b64(&b64).expect("decode");
assert_eq!(decoded, raw);
}
#[test]
fn test_decode_key_b64_wrong_length_errors() {
use base64::{engine::general_purpose::STANDARD, Engine as _};
let short = STANDARD.encode([0u8; 16]);
assert!(super::decode_key_b64(&short).is_err());
}
}