feat(server): add bridge forwarding mode and per-client destination policy overrides
This commit is contained in:
@@ -25,7 +25,7 @@ use crate::tunnel::{self, TunConfig};
|
||||
const DEAD_PEER_TIMEOUT: Duration = Duration::from_secs(180);
|
||||
|
||||
/// Destination routing policy for VPN client traffic.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DestinationPolicyConfig {
|
||||
/// Default action: "forceTarget", "block", or "allow".
|
||||
@@ -92,6 +92,17 @@ pub struct ServerConfig {
|
||||
/// Defaults to ["0.0.0.0/0"] (full tunnel).
|
||||
#[serde(alias = "clientAllowedIPs")]
|
||||
pub client_allowed_ips: Option<Vec<String>>,
|
||||
|
||||
// Bridge mode configuration (forwarding_mode: "bridge")
|
||||
|
||||
/// LAN subnet CIDR for bridge mode (e.g. "192.168.1.0/24").
|
||||
pub bridge_lan_subnet: Option<String>,
|
||||
/// Physical network interface to bridge (e.g. "eth0"). Auto-detected if omitted.
|
||||
pub bridge_physical_interface: Option<String>,
|
||||
/// Start of VPN client IP range within the LAN subnet (host offset, e.g. 200).
|
||||
pub bridge_ip_range_start: Option<u32>,
|
||||
/// End of VPN client IP range within the LAN subnet (host offset, e.g. 250).
|
||||
pub bridge_ip_range_end: Option<u32>,
|
||||
}
|
||||
|
||||
/// Information about a connected client.
|
||||
@@ -148,6 +159,8 @@ pub enum ForwardingEngine {
|
||||
Tun(tokio::io::WriteHalf<tun::AsyncDevice>),
|
||||
/// Userspace NAT — packets sent to smoltcp-based NAT engine via channel.
|
||||
Socket(mpsc::Sender<Vec<u8>>),
|
||||
/// L2 Bridge — packets sent to BridgeEngine via channel, bridged to host LAN.
|
||||
Bridge(mpsc::Sender<Vec<u8>>),
|
||||
/// Testing/monitoring — packets are counted but not forwarded.
|
||||
Testing,
|
||||
}
|
||||
@@ -191,7 +204,15 @@ impl VpnServer {
|
||||
anyhow::bail!("Server is already running");
|
||||
}
|
||||
|
||||
let ip_pool = IpPool::new(&config.subnet)?;
|
||||
let mode = config.forwarding_mode.as_deref().unwrap_or("testing");
|
||||
let ip_pool = if mode == "bridge" {
|
||||
let lan_subnet = config.bridge_lan_subnet.as_deref().unwrap_or(&config.subnet);
|
||||
let range_start = config.bridge_ip_range_start.unwrap_or(200);
|
||||
let range_end = config.bridge_ip_range_end.unwrap_or(250);
|
||||
IpPool::new_with_range(lan_subnet, range_start, range_end)?
|
||||
} else {
|
||||
IpPool::new(&config.subnet)?
|
||||
};
|
||||
|
||||
if config.enable_nat.unwrap_or(false) {
|
||||
if let Err(e) = crate::network::enable_ip_forwarding() {
|
||||
@@ -205,7 +226,6 @@ impl VpnServer {
|
||||
}
|
||||
|
||||
let link_mtu = config.mtu.unwrap_or(1420);
|
||||
let mode = config.forwarding_mode.as_deref().unwrap_or("testing");
|
||||
let gateway_ip = ip_pool.gateway_addr();
|
||||
|
||||
// Create forwarding engine based on mode
|
||||
@@ -220,6 +240,12 @@ impl VpnServer {
|
||||
packet_rx: mpsc::Receiver<Vec<u8>>,
|
||||
shutdown_rx: mpsc::Receiver<()>,
|
||||
},
|
||||
Bridge {
|
||||
packet_tx: mpsc::Sender<Vec<u8>>,
|
||||
packet_rx: mpsc::Receiver<Vec<u8>>,
|
||||
tap_device: tun::AsyncDevice,
|
||||
shutdown_rx: mpsc::Receiver<()>,
|
||||
},
|
||||
Testing,
|
||||
}
|
||||
|
||||
@@ -243,6 +269,33 @@ impl VpnServer {
|
||||
let (tx, rx) = mpsc::channel::<()>(1);
|
||||
(ForwardingSetup::Socket { packet_tx, packet_rx, shutdown_rx: rx }, tx)
|
||||
}
|
||||
"bridge" => {
|
||||
info!("Starting L2 bridge forwarding (requires CAP_NET_ADMIN)");
|
||||
let phys_iface = match &config.bridge_physical_interface {
|
||||
Some(i) => i.clone(),
|
||||
None => crate::bridge::detect_default_interface().await?,
|
||||
};
|
||||
let (host_ip, host_prefix) = crate::bridge::get_interface_ip(&phys_iface).await?;
|
||||
|
||||
let bridge_name = "svpn_br0";
|
||||
let tap_name = "svpn_tap0";
|
||||
|
||||
// Create TAP + bridge infrastructure
|
||||
let tap_device = crate::bridge::create_tap(tap_name, link_mtu)?;
|
||||
crate::bridge::create_bridge(bridge_name).await?;
|
||||
crate::bridge::set_interface_up(bridge_name).await?;
|
||||
crate::bridge::bridge_add_interface(bridge_name, tap_name).await?;
|
||||
crate::bridge::set_interface_up(tap_name).await?;
|
||||
crate::bridge::bridge_add_interface(bridge_name, &phys_iface).await?;
|
||||
crate::bridge::migrate_host_ip_to_bridge(&phys_iface, bridge_name, host_ip, host_prefix).await?;
|
||||
crate::bridge::enable_proxy_arp(bridge_name).await?;
|
||||
|
||||
info!("Bridge {} created: TAP={}, physical={}, IP={}/{}", bridge_name, tap_name, phys_iface, host_ip, host_prefix);
|
||||
|
||||
let (packet_tx, packet_rx) = mpsc::channel::<Vec<u8>>(4096);
|
||||
let (tx, rx) = mpsc::channel::<()>(1);
|
||||
(ForwardingSetup::Bridge { packet_tx, packet_rx, tap_device, shutdown_rx: rx }, tx)
|
||||
}
|
||||
_ => {
|
||||
info!("Forwarding disabled (testing/monitoring mode)");
|
||||
let (tx, _rx) = mpsc::channel::<()>(1);
|
||||
@@ -301,6 +354,15 @@ impl VpnServer {
|
||||
}
|
||||
});
|
||||
}
|
||||
ForwardingSetup::Bridge { packet_tx, packet_rx, tap_device, shutdown_rx } => {
|
||||
*state.forwarding_engine.lock().await = ForwardingEngine::Bridge(packet_tx);
|
||||
let bridge_engine = crate::bridge::BridgeEngine::new(state.clone());
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = bridge_engine.run(tap_device, packet_rx, shutdown_rx).await {
|
||||
error!("Bridge engine error: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
ForwardingSetup::Testing => {}
|
||||
}
|
||||
|
||||
@@ -1430,6 +1492,9 @@ async fn handle_client_connection(
|
||||
ForwardingEngine::Socket(sender) => {
|
||||
let _ = sender.try_send(buf[..len].to_vec());
|
||||
}
|
||||
ForwardingEngine::Bridge(sender) => {
|
||||
let _ = sender.try_send(buf[..len].to_vec());
|
||||
}
|
||||
ForwardingEngine::Testing => {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user