feat(forwarding): add hybrid forwarding mode with per-client bridge and VLAN settings

This commit is contained in:
2026-04-01 03:47:26 +00:00
parent c49fcaf1ce
commit 180282ba86
8 changed files with 301 additions and 17 deletions
+44
View File
@@ -225,6 +225,50 @@ pub async fn enable_proxy_arp(iface: &str) -> Result<()> {
Ok(())
}
// ============================================================================
// VLAN support (802.1Q via Linux bridge VLAN filtering)
// ============================================================================
async fn run_bridge_cmd(args: &[&str]) -> Result<String> {
let output = tokio::process::Command::new("bridge")
.args(args)
.output()
.await?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("bridge {} failed: {}", args.join(" "), stderr.trim());
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
/// Enable VLAN filtering on a bridge.
pub async fn enable_vlan_filtering(bridge: &str) -> Result<()> {
run_ip_cmd(&["link", "set", bridge, "type", "bridge", "vlan_filtering", "1"]).await?;
info!("Enabled VLAN filtering on bridge {}", bridge);
Ok(())
}
/// Add a VLAN ID to a bridge port (TAP or physical interface).
/// `pvid` = set as port VLAN ID (untagged ingress), `untagged` = strip tag on egress.
pub async fn add_vlan_to_port(port: &str, vlan_id: u16, pvid: bool, untagged: bool) -> Result<()> {
let mut args = vec!["vlan", "add", "dev", port, "vid"];
let vid_str = vlan_id.to_string();
args.push(&vid_str);
if pvid { args.push("pvid"); }
if untagged { args.push("untagged"); }
run_bridge_cmd(&args).await?;
info!("Added VLAN {} to port {} (pvid={}, untagged={})", vlan_id, port, pvid, untagged);
Ok(())
}
/// Remove a VLAN ID from a bridge port.
pub async fn remove_vlan_from_port(port: &str, vlan_id: u16) -> Result<()> {
let vid_str = vlan_id.to_string();
run_bridge_cmd(&["vlan", "del", "dev", port, "vid", &vid_str]).await?;
info!("Removed VLAN {} from port {}", vlan_id, port);
Ok(())
}
/// Create a TAP device (L2) using the tun crate.
pub fn create_tap(name: &str, mtu: u16) -> Result<tun::AsyncDevice> {
let mut config = tun::Configuration::default();