feat(server): add optional PROXY protocol v2 headers for socket-based userspace NAT forwarding

This commit is contained in:
2026-03-30 07:13:49 +00:00
parent af46dc9b39
commit 5bf21ab4ac
5 changed files with 39 additions and 3 deletions

View File

@@ -191,10 +191,13 @@ pub struct NatEngine {
bridge_rx: mpsc::Receiver<BridgeMessage>,
bridge_tx: mpsc::Sender<BridgeMessage>,
start_time: std::time::Instant,
/// When true, outbound TCP connections prepend PROXY protocol v2 headers
/// with the VPN client's tunnel IP as source address.
proxy_protocol: bool,
}
impl NatEngine {
pub fn new(gateway_ip: Ipv4Addr, mtu: usize, state: Arc<ServerState>) -> Self {
pub fn new(gateway_ip: Ipv4Addr, mtu: usize, state: Arc<ServerState>, proxy_protocol: bool) -> Self {
let mut device = VirtualIpDevice::new(mtu);
let config = Config::new(HardwareAddress::Ip);
let now = smoltcp::time::Instant::from_millis(0);
@@ -226,6 +229,7 @@ impl NatEngine {
bridge_rx,
bridge_tx,
start_time: std::time::Instant::now(),
proxy_protocol,
}
}
@@ -322,8 +326,9 @@ impl NatEngine {
// Spawn bridge task that connects to the real destination
let bridge_tx = self.bridge_tx.clone();
let key_clone = key.clone();
let proxy_protocol = self.proxy_protocol;
tokio::spawn(async move {
tcp_bridge_task(key_clone, data_rx, bridge_tx).await;
tcp_bridge_task(key_clone, data_rx, bridge_tx, proxy_protocol).await;
});
debug!(
@@ -531,6 +536,7 @@ async fn tcp_bridge_task(
key: SessionKey,
mut data_rx: mpsc::Receiver<Vec<u8>>,
bridge_tx: mpsc::Sender<BridgeMessage>,
proxy_protocol: bool,
) {
let addr = SocketAddr::new(key.dst_ip.into(), key.dst_port);
@@ -552,6 +558,18 @@ async fn tcp_bridge_task(
let (mut reader, mut writer) = stream.into_split();
// Send PROXY protocol v2 header with VPN client's tunnel IP as source
if proxy_protocol {
let src = SocketAddr::new(key.src_ip.into(), key.src_port);
let dst = SocketAddr::new(key.dst_ip.into(), key.dst_port);
let pp_header = crate::proxy_protocol::build_pp_v2_header(src, dst);
if let Err(e) = writer.write_all(&pp_header).await {
debug!("NAT: failed to send PP v2 header to {}: {}", addr, e);
let _ = bridge_tx.send(BridgeMessage::TcpClosed { key }).await;
return;
}
}
// Read from real socket → send to NAT engine
let bridge_tx2 = bridge_tx.clone();
let key2 = key.clone();