[NC-43] Route packets back to WG peer (#3965)

* Initial work on reverse nat

* wip

* Refine key gen

* Rename to wg_tunnel

* Forward packet to peer

* Remove source_addr

* Check if allowed to write to tunnel

* Extract out network_table

* Move map struc definitions to udp_listener

* Delegate ip network table calls

* Fix mac compilation

* Add TunTaskTx type
This commit is contained in:
Jon Häggblad
2023-10-10 11:01:03 +02:00
committed by GitHub
parent 1e900c32df
commit 7577ec9cb2
8 changed files with 186 additions and 89 deletions
Generated
+2
View File
@@ -7470,6 +7470,8 @@ dependencies = [
"dashmap",
"etherparse",
"futures",
"ip_network",
"ip_network_table",
"log",
"nym-task",
"tap",
+2
View File
@@ -22,6 +22,8 @@ bytes = "1.5.0"
dashmap = "5.5.3"
etherparse = "0.13.0"
futures = "0.3.28"
ip_network = "0.4.1"
ip_network_table = "0.2.0"
log.workspace = true
nym-task = { path = "../task" }
tap.workspace = true
+18 -10
View File
@@ -1,40 +1,48 @@
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
use nym_task::TaskClient;
mod error;
mod event;
mod network_table;
mod platform;
mod setup;
mod tun;
mod udp_listener;
mod wg_tunnel;
// Currently the module related to setting up the virtual network device is platform specific.
#[cfg(target_os = "linux")]
use platform::linux::tun_device;
type ActivePeers =
dashmap::DashMap<std::net::SocketAddr, tokio::sync::mpsc::UnboundedSender<crate::event::Event>>;
#[derive(Clone)]
struct TunTaskTx(tokio::sync::mpsc::UnboundedSender<Vec<u8>>);
impl TunTaskTx {
fn send(&self, packet: Vec<u8>) -> Result<(), tokio::sync::mpsc::error::SendError<Vec<u8>>> {
self.0.send(packet)
}
}
#[cfg(target_os = "linux")]
pub async fn start_wireguard(
task_client: TaskClient,
task_client: nym_task::TaskClient,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
use std::sync::Arc;
// The set of active tunnels indexed by the peer's address
let active_peers = std::sync::Arc::new(ActivePeers::new());
let active_peers = Arc::new(udp_listener::ActivePeers::new());
let peers_by_ip = Arc::new(std::sync::Mutex::new(network_table::NetworkTable::new()));
// Start the tun device that is used to relay traffic outbound
let tun_task_tx = tun_device::start_tun_device(active_peers.clone());
let tun_task_tx = tun_device::start_tun_device(peers_by_ip.clone());
// Start the UDP listener that clients connect to
udp_listener::start_udp_listener(tun_task_tx, active_peers, task_client).await?;
udp_listener::start_udp_listener(tun_task_tx, active_peers, peers_by_ip, task_client).await?;
Ok(())
}
#[cfg(not(target_os = "linux"))]
pub async fn start_wireguard(
_task_client: TaskClient,
_task_client: nym_task::TaskClient,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
todo!("WireGuard is currently only supported on Linux")
}
+25
View File
@@ -0,0 +1,25 @@
use std::net::IpAddr;
use ip_network::IpNetwork;
use ip_network_table::IpNetworkTable;
#[derive(Default)]
pub(crate) struct NetworkTable<T> {
ips: IpNetworkTable<T>,
}
impl<T> NetworkTable<T> {
pub(crate) fn new() -> Self {
Self {
ips: IpNetworkTable::new(),
}
}
pub fn insert<N: Into<IpNetwork>>(&mut self, network: N, data: T) -> Option<T> {
self.ips.insert(network, data)
}
pub fn longest_match<I: Into<IpAddr>>(&self, ip: I) -> Option<(IpNetwork, &T)> {
self.ips.longest_match(ip)
}
}
@@ -1,14 +1,17 @@
use std::{net::Ipv4Addr, sync::Arc};
use etherparse::{InternetSlice, SlicedPacket};
use tap::TapFallible;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
sync::mpsc::{self, UnboundedSender},
sync::mpsc::{self},
};
use crate::{
event::Event,
setup::{TUN_BASE_NAME, TUN_DEVICE_ADDRESS, TUN_DEVICE_NETMASK},
ActivePeers,
udp_listener::PeersByIp,
TunTaskTx,
};
fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> tokio_tun::Tun {
@@ -25,7 +28,7 @@ fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> t
.expect("Failed to setup tun device, do you have permission?")
}
pub fn start_tun_device(_active_peers: Arc<ActivePeers>) -> UnboundedSender<Vec<u8>> {
pub(crate) fn start_tun_device(peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>) -> TunTaskTx {
let tun = setup_tokio_tun_device(
format!("{}%d", TUN_BASE_NAME).as_str(),
TUN_DEVICE_ADDRESS.parse().unwrap(),
@@ -37,6 +40,7 @@ pub fn start_tun_device(_active_peers: Arc<ActivePeers>) -> UnboundedSender<Vec<
// Channels to communicate with the other tasks
let (tun_task_tx, mut tun_task_rx) = mpsc::unbounded_channel::<Vec<u8>>();
let tun_task_tx = TunTaskTx(tun_task_tx);
tokio::spawn(async move {
let mut buf = [0u8; 1024];
@@ -55,8 +59,16 @@ pub fn start_tun_device(_active_peers: Arc<ActivePeers>) -> UnboundedSender<Vec<
};
log::info!("iface: read Packet({src_addr} -> {dst_addr}, {len} bytes)");
// TODO: route packet to the correct peer.
log::info!("...forward packet to the correct peer (NOT YET IMPLEMENTED)");
// Route packet to the correct peer.
if let Some(peer_tx) = peers_by_ip.lock().unwrap().longest_match(dst_addr).map(|(_, tx)| tx) {
log::info!("Forward packet to wg tunnel");
peer_tx
.send(Event::IpPacket(packet.to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
} else {
log::info!("No peer found, packet dropped");
}
},
Err(err) => {
log::info!("iface: read error: {err}");
+36 -22
View File
@@ -1,3 +1,5 @@
use std::net::IpAddr;
use base64::{engine::general_purpose, Engine as _};
use boringtun::x25519;
use log::info;
@@ -15,35 +17,47 @@ pub const TUN_DEVICE_NETMASK: &str = "255.255.255.0";
// Corresponding public key: "WM8s8bYegwMa0TJ+xIwhk+dImk2IpDUKslDBCZPizlE="
const PRIVATE_KEY: &str = "AEqXrLFT4qjYq3wmX0456iv94uM6nDj5ugp6Jedcflg=";
// The public keys of the registered peers (clients)
const PEERS: &[&str; 1] = &[
// Corresponding private key: "ILeN6gEh6vJ3Ju8RJ3HVswz+sPgkcKtAYTqzQRhTtlo="
"NCIhkgiqxFx1ckKl3Zuh595DzIFl8mxju1Vg995EZhI=",
// Another key
// "mxV/mw7WZTe+0Msa0kvJHMHERDA/cSskiZWQce+TdEs=",
];
// The public keys of the registered peer (clients)
// Corresponding private key: "ILeN6gEh6vJ3Ju8RJ3HVswz+sPgkcKtAYTqzQRhTtlo="
const PEER: &str = "NCIhkgiqxFx1ckKl3Zuh595DzIFl8mxju1Vg995EZhI=";
pub fn init_static_dev_keys() -> (x25519::StaticSecret, x25519::PublicKey) {
// TODO: this is a temporary solution for development
let static_private_bytes: [u8; 32] = general_purpose::STANDARD
.decode(PRIVATE_KEY)
// The AllowedIPs for the connected peer, which is one a single IP and the same as the IP that the
// peer has configured on their side.
const ALLOWED_IPS: &str = "10.0.0.2";
fn decode_base64_key(base64_key: &str) -> [u8; 32] {
general_purpose::STANDARD
.decode(base64_key)
.unwrap()
.try_into()
.unwrap();
.unwrap()
}
pub fn server_static_private_key() -> x25519::StaticSecret {
// TODO: this is a temporary solution for development
let static_private_bytes: [u8; 32] = decode_base64_key(PRIVATE_KEY);
let static_private = x25519::StaticSecret::try_from(static_private_bytes).unwrap();
let static_public = x25519::PublicKey::from(&static_private);
info!(
"wg public key: {}",
general_purpose::STANDARD.encode(static_public)
);
// TODO: A single static public key is used for all peers during development
let peer_static_public_bytes: [u8; 32] = general_purpose::STANDARD
.decode(PEERS[0])
.unwrap()
.try_into()
.unwrap();
let peer_static_public = x25519::PublicKey::try_from(peer_static_public_bytes).unwrap();
(static_private, peer_static_public)
static_private
}
pub fn peer_static_public_key() -> x25519::PublicKey {
// A single static public key is used during development
let peer_static_public_bytes: [u8; 32] = decode_base64_key(PEER);
let peer_static_public = x25519::PublicKey::try_from(peer_static_public_bytes).unwrap();
info!(
"peer public key: {}",
general_purpose::STANDARD.encode(peer_static_public)
);
peer_static_public
}
pub fn peer_allowed_ips() -> ip_network::IpNetwork {
let key: IpAddr = ALLOWED_IPS.parse().unwrap();
let cidr = 0u8;
ip_network::IpNetwork::new_truncate(key, cidr).unwrap()
}
+28 -7
View File
@@ -1,22 +1,31 @@
use std::{net::SocketAddr, sync::Arc};
use dashmap::DashMap;
use futures::StreamExt;
use log::error;
use nym_task::TaskClient;
use tap::TapFallible;
use tokio::{net::UdpSocket, sync::mpsc::UnboundedSender};
use tokio::{
net::UdpSocket,
sync::mpsc::{self},
};
use crate::{
event::Event,
setup::{WG_ADDRESS, WG_PORT},
ActivePeers,
network_table::NetworkTable,
setup::{self, WG_ADDRESS, WG_PORT},
TunTaskTx,
};
const MAX_PACKET: usize = 65535;
pub async fn start_udp_listener(
tun_task_tx: UnboundedSender<Vec<u8>>,
pub(crate) type ActivePeers = DashMap<SocketAddr, mpsc::UnboundedSender<Event>>;
pub(crate) type PeersByIp = NetworkTable<mpsc::UnboundedSender<Event>>;
pub(crate) async fn start_udp_listener(
tun_task_tx: TunTaskTx,
active_peers: Arc<ActivePeers>,
peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>,
mut task_client: TaskClient,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let wg_address = SocketAddr::new(WG_ADDRESS.parse().unwrap(), WG_PORT);
@@ -24,7 +33,9 @@ pub async fn start_udp_listener(
let udp_socket = Arc::new(UdpSocket::bind(wg_address).await?);
// Setup some static keys for development
let (static_private, peer_static_public) = crate::setup::init_static_dev_keys();
let static_private = setup::server_static_private_key();
let peer_static_public = setup::peer_static_public_key();
let peer_allowed_ips = setup::peer_allowed_ips();
tokio::spawn(async move {
// Each tunnel is run in its own task, and the task handle is stored here so we can remove
@@ -44,6 +55,7 @@ pub async fn start_udp_listener(
Ok(addr) => {
log::info!("Removing peer: {addr:?}");
active_peers.remove(&addr);
// TODO: remove from peers_by_ip
}
Err(err) => {
error!("WireGuard UDP listener: error receiving shutdown from peer: {err}");
@@ -61,13 +73,22 @@ pub async fn start_udp_listener(
.unwrap();
} else {
log::info!("udp: received {len} bytes from {addr} from unknown peer, starting tunnel");
let (join_handle, peer_tx) = crate::tun::start_wg_tunnel(
// TODO: this is a temporary solution for development since this
// assumes we know the peer_static_public this corresponds to.
// TODO: rework this before production! This is likely not secure!
log::warn!("Assuming peer_static_public is known");
log::warn!("SECURITY: Rework me to do proper handshake before creating the tunnel!");
let (join_handle, peer_tx) = crate::wg_tunnel::start_wg_tunnel(
addr,
udp_socket.clone(),
static_private.clone(),
peer_static_public,
peer_allowed_ips,
tun_task_tx.clone(),
);
peers_by_ip.lock().unwrap().insert(peer_allowed_ips, peer_tx.clone());
peer_tx.send(Event::WgPacket(buf[..len].to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
@@ -6,7 +6,6 @@ use boringtun::{
x25519,
};
use bytes::Bytes;
use etherparse::{InternetSlice, SlicedPacket};
use log::{debug, error, info, warn};
use tap::TapFallible;
use tokio::{
@@ -15,7 +14,7 @@ use tokio::{
time::timeout,
};
use crate::{error::WgError, event::Event};
use crate::{error::WgError, event::Event, network_table::NetworkTable, TunTaskTx};
const MAX_PACKET: usize = 65535;
@@ -27,10 +26,10 @@ pub struct WireGuardTunnel {
udp: Arc<UdpSocket>,
// Peer endpoint
endpoint: SocketAddr,
endpoint: Arc<tokio::sync::RwLock<SocketAddr>>,
// The source address of the last packet received from the peer
source_addr: Arc<std::sync::RwLock<Option<std::net::Ipv4Addr>>>,
// AllowedIPs for this peer
allowed_ips: NetworkTable<()>,
// `boringtun` tunnel, used for crypto & WG protocol
wg_tunnel: Arc<tokio::sync::Mutex<Tunn>>,
@@ -40,7 +39,7 @@ pub struct WireGuardTunnel {
close_rx: broadcast::Receiver<()>,
// Send data to the task that handles sending data through the tun device
tun_task_tx: mpsc::UnboundedSender<Vec<u8>>,
tun_task_tx: TunTaskTx,
}
impl Drop for WireGuardTunnel {
@@ -51,12 +50,13 @@ impl Drop for WireGuardTunnel {
}
impl WireGuardTunnel {
pub fn new(
pub(crate) fn new(
udp: Arc<UdpSocket>,
endpoint: SocketAddr,
static_private: x25519::StaticSecret,
peer_static_public: x25519::PublicKey,
tunnel_tx: mpsc::UnboundedSender<Vec<u8>>,
peer_allowed_ips: ip_network::IpNetwork,
tunnel_tx: TunTaskTx,
) -> (Self, mpsc::UnboundedSender<Event>) {
let local_addr = udp.local_addr().unwrap();
let peer_addr = udp.peer_addr();
@@ -85,11 +85,14 @@ impl WireGuardTunnel {
// Signal close tunnel
let (close_tx, close_rx) = broadcast::channel(1);
let mut allowed_ips = NetworkTable::new();
allowed_ips.insert(peer_allowed_ips, ());
let tunnel = WireGuardTunnel {
peer_rx,
udp,
endpoint,
source_addr: Default::default(),
endpoint: Arc::new(tokio::sync::RwLock::new(endpoint)),
allowed_ips,
wg_tunnel,
close_tx,
close_rx,
@@ -134,7 +137,7 @@ impl WireGuardTunnel {
},
}
}
info!("WireGuard tunnel ({}): closed", self.endpoint);
info!("WireGuard tunnel ({}): closed", self.endpoint.read().await);
}
async fn wg_tunnel_lock(&self) -> Result<tokio::sync::MutexGuard<'_, Tunn>, WgError> {
@@ -143,16 +146,11 @@ impl WireGuardTunnel {
.map_err(|_| WgError::UnableToGetTunnel)
}
fn set_source_addr(&self, source_addr: std::net::Ipv4Addr) {
let to_update = {
let stored_source_addr = self.source_addr.read().unwrap();
stored_source_addr
.map(|sa| sa != source_addr)
.unwrap_or(true)
};
if to_update {
log::info!("wg tunnel set_source_addr: {source_addr}");
*self.source_addr.write().unwrap() = Some(source_addr);
#[allow(unused)]
async fn set_endpoint(&self, addr: SocketAddr) {
if *self.endpoint.read().await != addr {
log::info!("wg tunnel update endpoint: {addr}");
*self.endpoint.write().await = addr;
}
}
@@ -161,8 +159,9 @@ impl WireGuardTunnel {
let mut tunnel = self.wg_tunnel_lock().await?;
match tunnel.decapsulate(None, data, &mut send_buf) {
TunnResult::WriteToNetwork(packet) => {
log::info!("udp: send {} bytes to {}", packet.len(), self.endpoint);
if let Err(err) = self.udp.send_to(packet, self.endpoint).await {
let endpoint = self.endpoint.read().await;
log::info!("udp: send {} bytes to {}", packet.len(), *endpoint);
if let Err(err) = self.udp.send_to(packet, *endpoint).await {
error!("Failed to send decapsulation-instructed packet to WireGuard endpoint: {err:?}");
};
// Flush pending queue
@@ -170,8 +169,8 @@ impl WireGuardTunnel {
let mut send_buf = [0u8; MAX_PACKET];
match tunnel.decapsulate(None, &[], &mut send_buf) {
TunnResult::WriteToNetwork(packet) => {
log::info!("udp: send {} bytes to {}", packet.len(), self.endpoint);
if let Err(err) = self.udp.send_to(packet, self.endpoint).await {
log::info!("udp: send {} bytes to {}", packet.len(), *endpoint);
if let Err(err) = self.udp.send_to(packet, *endpoint).await {
error!("Failed to send decapsulation-instructed packet to WireGuard endpoint: {err:?}");
break;
};
@@ -182,14 +181,23 @@ impl WireGuardTunnel {
}
}
}
TunnResult::WriteToTunnelV4(packet, _) | TunnResult::WriteToTunnelV6(packet, _) => {
let headers = SlicedPacket::from_ip(packet).unwrap();
let (source_addr, _destination_addr) = match headers.ip.unwrap() {
InternetSlice::Ipv4(ip, _) => (ip.source_addr(), ip.destination_addr()),
InternetSlice::Ipv6(_, _) => unimplemented!(),
};
self.set_source_addr(source_addr);
self.tun_task_tx.send(packet.to_vec()).unwrap();
TunnResult::WriteToTunnelV4(packet, addr) => {
// TODO: once the flow is redone, we should add updating the endpoint dynamically
// self.set_endpoint(addr);
if self.allowed_ips.longest_match(addr).is_some() {
self.tun_task_tx.send(packet.to_vec()).unwrap();
} else {
warn!("Packet from {addr} not in allowed_ips");
}
}
TunnResult::WriteToTunnelV6(packet, addr) => {
// TODO: once the flow is redone, we should add updating the endpoint dynamically
// self.set_endpoint(addr);
if self.allowed_ips.longest_match(addr).is_some() {
self.tun_task_tx.send(packet.to_vec()).unwrap();
} else {
warn!("Packet (v6) from {addr} not in allowed_ips");
}
}
TunnResult::Done => {
debug!("WireGuard: decapsulate done");
@@ -209,9 +217,10 @@ impl WireGuardTunnel {
encapsulated_packet.len()
);
info!("consume_eth: send to {}: {}", self.endpoint, data.len());
let endpoint = self.endpoint.read().await;
info!("consume_eth: send to {}: {}", *endpoint, data.len());
self.udp
.send_to(&encapsulated_packet, self.endpoint)
.send_to(&encapsulated_packet, *endpoint)
.await
.unwrap();
}
@@ -244,12 +253,9 @@ impl WireGuardTunnel {
async fn handle_routine_tun_result<'a: 'async_recursion>(&self, result: TunnResult<'a>) {
match result {
TunnResult::WriteToNetwork(packet) => {
log::info!(
"routine: write to network: {}: {}",
self.endpoint,
packet.len()
);
if let Err(err) = self.udp.send_to(packet, self.endpoint).await {
let endpoint = self.endpoint.read().await;
log::info!("routine: write to network: {}: {}", endpoint, packet.len());
if let Err(err) = self.udp.send_to(packet, *endpoint).await {
error!("routine: failed to send packet: {err:?}");
};
}
@@ -276,18 +282,25 @@ impl WireGuardTunnel {
}
}
pub fn start_wg_tunnel(
pub(crate) fn start_wg_tunnel(
endpoint: SocketAddr,
udp: Arc<UdpSocket>,
static_private: x25519::StaticSecret,
peer_static_public: x25519::PublicKey,
tunnel_tx: mpsc::UnboundedSender<Vec<u8>>,
peer_allowed_ips: ip_network::IpNetwork,
tunnel_tx: TunTaskTx,
) -> (
tokio::task::JoinHandle<SocketAddr>,
mpsc::UnboundedSender<Event>,
) {
let (mut tunnel, peer_tx) =
WireGuardTunnel::new(udp, endpoint, static_private, peer_static_public, tunnel_tx);
let (mut tunnel, peer_tx) = WireGuardTunnel::new(
udp,
endpoint,
static_private,
peer_static_public,
peer_allowed_ips,
tunnel_tx,
);
let join_handle = tokio::spawn(async move {
tunnel.spin_off().await;
endpoint