//! L2 Bridge forwarding engine. //! //! Provides server-side bridging: receives L3 IP packets from VPN clients, //! wraps them in Ethernet frames, and injects them into a Linux bridge //! connected to the host's physical network interface. //! //! Return traffic from the bridge is stripped of its Ethernet header and //! routed back to VPN clients via `tun_routes`. use anyhow::Result; use std::collections::HashMap; use std::net::Ipv4Addr; use std::sync::Arc; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::sync::mpsc; use tracing::{debug, error, info, warn}; use crate::server::ServerState; /// Configuration for the bridge forwarding engine. pub struct BridgeConfig { /// TAP device name (e.g., "svpn_tap0") pub tap_name: String, /// Linux bridge name (e.g., "svpn_br0") pub bridge_name: String, /// Physical interface to bridge (e.g., "eth0") pub physical_interface: String, /// Gateway IP on the bridge (host's LAN IP) pub gateway_ip: Ipv4Addr, /// Subnet prefix length (e.g., 24) pub prefix_len: u8, /// MTU for the TAP device pub mtu: u16, } /// Ethernet frame constants const ETH_HEADER_LEN: usize = 14; const ETH_TYPE_IPV4: [u8; 2] = [0x08, 0x00]; const ETH_TYPE_ARP: [u8; 2] = [0x08, 0x06]; const BROADCAST_MAC: [u8; 6] = [0xff; 6]; /// Generate a deterministic locally-administered MAC from an IPv4 address. /// Uses prefix 02:53:56 (locally administered, "SVP" in hex-ish). fn mac_from_ip(ip: Ipv4Addr) -> [u8; 6] { let octets = ip.octets(); [0x02, 0x53, 0x56, octets[1], octets[2], octets[3]] } /// Wrap an IP packet in an Ethernet frame. fn wrap_in_ethernet(ip_packet: &[u8], src_mac: [u8; 6], dst_mac: [u8; 6]) -> Vec { let mut frame = Vec::with_capacity(ETH_HEADER_LEN + ip_packet.len()); frame.extend_from_slice(&dst_mac); frame.extend_from_slice(&src_mac); frame.extend_from_slice(Ð_TYPE_IPV4); frame.extend_from_slice(ip_packet); frame } /// Extract the EtherType and payload from an Ethernet frame. fn unwrap_ethernet(frame: &[u8]) -> Option<([u8; 2], &[u8])> { if frame.len() < ETH_HEADER_LEN { return None; } let ether_type = [frame[12], frame[13]]; Some((ether_type, &frame[ETH_HEADER_LEN..])) } /// Extract destination IPv4 from a raw IP packet header. fn dst_ip_from_packet(packet: &[u8]) -> Option { if packet.len() < 20 { return None; } // Version must be 4 if (packet[0] >> 4) != 4 { return None; } Some(Ipv4Addr::new(packet[16], packet[17], packet[18], packet[19])) } /// Extract source IPv4 from a raw IP packet header. fn src_ip_from_packet(packet: &[u8]) -> Option { if packet.len() < 20 { return None; } if (packet[0] >> 4) != 4 { return None; } Some(Ipv4Addr::new(packet[12], packet[13], packet[14], packet[15])) } /// Build a gratuitous ARP announcement frame. fn build_garp(ip: Ipv4Addr, mac: [u8; 6]) -> Vec { let ip_bytes = ip.octets(); let mut frame = Vec::with_capacity(42); // 14 eth + 28 ARP // Ethernet header frame.extend_from_slice(&BROADCAST_MAC); // dst: broadcast frame.extend_from_slice(&mac); // src: our MAC frame.extend_from_slice(Ð_TYPE_ARP); // EtherType: ARP // ARP payload frame.extend_from_slice(&[0x00, 0x01]); // Hardware type: Ethernet frame.extend_from_slice(&[0x08, 0x00]); // Protocol type: IPv4 frame.push(6); // Hardware addr len frame.push(4); // Protocol addr len frame.extend_from_slice(&[0x00, 0x01]); // Operation: ARP Request (GARP uses request) frame.extend_from_slice(&mac); // Sender hardware addr frame.extend_from_slice(&ip_bytes); // Sender protocol addr frame.extend_from_slice(&[0x00; 6]); // Target hardware addr (ignored in GARP) frame.extend_from_slice(&ip_bytes); // Target protocol addr (same as sender for GARP) frame } // ============================================================================ // Linux bridge management (ip commands) // ============================================================================ async fn run_ip_cmd(args: &[&str]) -> Result { let output = tokio::process::Command::new("ip") .args(args) .output() .await?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); anyhow::bail!("ip {} failed: {}", args.join(" "), stderr.trim()); } Ok(String::from_utf8_lossy(&output.stdout).to_string()) } /// Create a Linux bridge interface. pub async fn create_bridge(name: &str) -> Result<()> { run_ip_cmd(&["link", "add", name, "type", "bridge"]).await?; info!("Created bridge {}", name); Ok(()) } /// Add an interface to a bridge. pub async fn bridge_add_interface(bridge: &str, iface: &str) -> Result<()> { run_ip_cmd(&["link", "set", iface, "master", bridge]).await?; info!("Added {} to bridge {}", iface, bridge); Ok(()) } /// Bring an interface up. pub async fn set_interface_up(iface: &str) -> Result<()> { run_ip_cmd(&["link", "set", iface, "up"]).await?; Ok(()) } /// Remove a bridge interface. pub async fn remove_bridge(name: &str) -> Result<()> { // First bring it down, ignore errors let _ = run_ip_cmd(&["link", "set", name, "down"]).await; run_ip_cmd(&["link", "del", name]).await?; info!("Removed bridge {}", name); Ok(()) } /// Detect the default network interface from the routing table. pub async fn detect_default_interface() -> Result { let output = run_ip_cmd(&["route", "show", "default"]).await?; // Format: "default via X.X.X.X dev IFACE ..." let parts: Vec<&str> = output.split_whitespace().collect(); if let Some(idx) = parts.iter().position(|&s| s == "dev") { if let Some(iface) = parts.get(idx + 1) { return Ok(iface.to_string()); } } anyhow::bail!("Could not detect default network interface from route table"); } /// Get the IP address and prefix length of a network interface. pub async fn get_interface_ip(iface: &str) -> Result<(Ipv4Addr, u8)> { let output = run_ip_cmd(&["-4", "addr", "show", "dev", iface]).await?; // Parse "inet X.X.X.X/NN" from output for line in output.lines() { let trimmed = line.trim(); if let Some(rest) = trimmed.strip_prefix("inet ") { let addr_cidr = rest.split_whitespace().next().unwrap_or(""); let parts: Vec<&str> = addr_cidr.split('/').collect(); if parts.len() == 2 { let ip: Ipv4Addr = parts[0].parse()?; let prefix: u8 = parts[1].parse()?; return Ok((ip, prefix)); } } } anyhow::bail!("Could not find IPv4 address on interface {}", iface); } /// Migrate the host's IP from a physical interface to a bridge. /// This is the most delicate operation — briefly interrupts connectivity. pub async fn migrate_host_ip_to_bridge( physical_iface: &str, bridge: &str, ip: Ipv4Addr, prefix: u8, ) -> Result<()> { let cidr = format!("{}/{}", ip, prefix); // Remove IP from physical interface let _ = run_ip_cmd(&["addr", "del", &cidr, "dev", physical_iface]).await; // Add IP to bridge run_ip_cmd(&["addr", "add", &cidr, "dev", bridge]).await?; info!("Migrated IP {} from {} to {}", cidr, physical_iface, bridge); Ok(()) } /// Restore the host's IP from bridge back to the physical interface. pub async fn restore_host_ip( physical_iface: &str, bridge: &str, ip: Ipv4Addr, prefix: u8, ) -> Result<()> { let cidr = format!("{}/{}", ip, prefix); let _ = run_ip_cmd(&["addr", "del", &cidr, "dev", bridge]).await; run_ip_cmd(&["addr", "add", &cidr, "dev", physical_iface]).await?; info!("Restored IP {} to {}", cidr, physical_iface); Ok(()) } /// Enable proxy ARP on an interface via sysctl. pub async fn enable_proxy_arp(iface: &str) -> Result<()> { let path = format!("/proc/sys/net/ipv4/conf/{}/proxy_arp", iface); tokio::fs::write(&path, "1").await?; info!("Enabled proxy_arp on {}", iface); Ok(()) } /// Create a TAP device (L2) using the tun crate. pub fn create_tap(name: &str, mtu: u16) -> Result { let mut config = tun::Configuration::default(); config .tun_name(name) .layer(tun::Layer::L2) .mtu(mtu) .up(); #[cfg(target_os = "linux")] config.platform_config(|p| { p.ensure_root_privileges(true); }); let device = tun::create_as_async(&config)?; info!("TAP device {} created (L2, mtu={})", name, mtu); Ok(device) } // ============================================================================ // BridgeEngine — main event loop // ============================================================================ /// The BridgeEngine wraps/unwraps Ethernet frames and bridges VPN traffic /// to the host's physical LAN via a Linux bridge + TAP device. pub struct BridgeEngine { state: Arc, /// Learned MAC addresses for LAN peers (dst IP → MAC). /// Populated from ARP replies and Ethernet frame src MACs. arp_cache: HashMap, } impl BridgeEngine { pub fn new(state: Arc) -> Self { Self { state, arp_cache: HashMap::new(), } } /// Run the bridge engine event loop. /// Receives L3 IP packets from VPN clients, wraps in Ethernet, writes to TAP. /// Reads Ethernet frames from TAP, strips header, routes back to VPN clients. pub async fn run( mut self, mut tap_device: tun::AsyncDevice, mut packet_rx: mpsc::Receiver>, mut shutdown_rx: mpsc::Receiver<()>, ) -> Result<()> { let mut buf = vec![0u8; 2048]; info!("BridgeEngine started"); loop { tokio::select! { // Packet from VPN client → wrap in Ethernet → write to TAP Some(ip_packet) = packet_rx.recv() => { if let Some(dst_ip) = dst_ip_from_packet(&ip_packet) { let src_ip = src_ip_from_packet(&ip_packet).unwrap_or(Ipv4Addr::UNSPECIFIED); let src_mac = mac_from_ip(src_ip); let dst_mac = self.arp_cache.get(&dst_ip) .copied() .unwrap_or(BROADCAST_MAC); let frame = wrap_in_ethernet(&ip_packet, src_mac, dst_mac); if let Err(e) = tap_device.write_all(&frame).await { warn!("TAP write error: {}", e); } } } // Frame from TAP (LAN) → strip Ethernet → route to VPN client result = tap_device.read(&mut buf) => { match result { Ok(len) if len >= ETH_HEADER_LEN => { let frame = &buf[..len]; // Learn src MAC from incoming frames if let Some((ether_type, payload)) = unwrap_ethernet(frame) { // Learn ARP cache from src MAC + src IP let src_mac: [u8; 6] = frame[6..12].try_into().unwrap_or([0; 6]); if ether_type == ETH_TYPE_IPV4 { if let Some(src_ip) = src_ip_from_packet(payload) { self.arp_cache.insert(src_ip, src_mac); } } // Only forward IPv4 packets to VPN clients if ether_type == ETH_TYPE_IPV4 { if let Some(dst_ip) = dst_ip_from_packet(payload) { // Look up VPN client by dst IP in tun_routes let routes = self.state.tun_routes.read().await; if let Some(sender) = routes.get(&dst_ip) { let _ = sender.try_send(payload.to_vec()); } } } } } Ok(_) => {} // Frame too short, ignore Err(e) => { warn!("TAP read error: {}", e); } } } _ = shutdown_rx.recv() => { info!("BridgeEngine shutting down"); break; } } } Ok(()) } /// Send a gratuitous ARP for a VPN client IP. pub async fn announce_client(tap: &mut tun::AsyncDevice, ip: Ipv4Addr) -> Result<()> { let mac = mac_from_ip(ip); let garp = build_garp(ip, mac); tap.write_all(&garp).await?; debug!("Sent GARP for {} (MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x})", ip, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); Ok(()) } }