use super::error::{NetworkError, Result};
use std::process::Command;
use super::bridge::Bridge;
pub struct Tap {
pub name: String,
pub bridge: Bridge,
}
pub fn create_tap(name: &str, bridge: &str) -> Result<Tap> {
if tap_exists(name)? {
return get_tap(name);
}
let bridge_dev = super::bridge::create_bridge(bridge)?;
let output = Command::new("ip")
.args(["tuntap", "add", name, "mode", "tap"])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to create TAP device: {}", e)))?;
if !output.status.success() {
return Err(NetworkError::Interface(format!(
"Failed to create TAP device {}: {}",
name,
String::from_utf8_lossy(&output.stderr)
)));
}
let output = Command::new("ip")
.args(["link", "set", "dev", name, "mtu", "1500"])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to set MTU: {}", e)))?;
if !output.status.success() {
return Err(NetworkError::Interface(format!(
"Failed to set MTU for TAP {}: {}",
name,
String::from_utf8_lossy(&output.stderr)
)));
}
super::interface::attach_to_bridge(name, bridge)?;
super::interface::set_up(name, None)?;
Ok(Tap {
name: name.to_string(),
bridge: bridge_dev,
})
}
pub fn delete_tap(name: &str) -> Result<()> {
if !tap_exists(name)? {
return Ok(());
}
let output = Command::new("ip")
.args(["link", "set", "dev", name, "down"])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to bring TAP down: {}", e)))?;
if !output.status.success() {
return Err(NetworkError::Interface(format!(
"Failed to bring TAP {} down: {}",
name,
String::from_utf8_lossy(&output.stderr)
)));
}
let output = Command::new("ip")
.args(["tuntap", "del", name, "mode", "tap"])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to delete TAP device: {}", e)))?;
if !output.status.success() {
return Err(NetworkError::Interface(format!(
"Failed to delete TAP device {}: {}",
name,
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(())
}
pub fn tap_exists(name: &str) -> Result<bool> {
let output = Command::new("ip")
.args(["link", "show", name])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to check TAP device: {}", e)))?;
if !output.status.success() {
return Ok(false);
}
let output_str = String::from_utf8_lossy(&output.stdout);
Ok(output_str.contains("tap"))
}
pub fn get_tap(name: &str) -> Result<Tap> {
if !tap_exists(name)? {
return Err(NetworkError::NotFound(format!("TAP device {} not found", name)));
}
let output = Command::new("ip")
.args(["link", "show", name])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to get TAP info: {}", e)))?;
let output_str = String::from_utf8_lossy(&output.stdout);
let bridge_name = output_str
.lines()
.find(|line| line.contains("master"))
.and_then(|line| {
line.split("master").nth(1)
.and_then(|s| s.split_whitespace().nth(0))
})
.unwrap_or("unknown")
.trim_end_matches(',')
.to_string();
if bridge_name == "unknown" {
return Err(NetworkError::Interface(format!(
"TAP device {} exists but is not attached to any bridge", name
)));
}
let bridge = super::bridge::get_bridge(&bridge_name)?;
Ok(Tap {
name: name.to_string(),
bridge,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tap_exists() {
let result = tap_exists("non-existent-tap");
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_delete_tap_nonexistent() {
let result = delete_tap("non-existent-tap");
assert!(result.is_ok());
}
#[cfg(target_os = "linux")]
#[test]
#[ignore]
fn test_create_tap() {
let tap_name = "test-tap";
let bridge_name = "test-br";
let result = create_tap(tap_name, bridge_name);
assert!(result.is_ok(), "TAP creation should succeed");
let tap = result.unwrap();
assert_eq!(tap.name, tap_name);
assert_eq!(tap.bridge.name, bridge_name);
let exists = tap_exists(tap_name).unwrap();
assert!(exists, "TAP should exist after creation");
let delete_result = delete_tap(tap_name);
assert!(delete_result.is_ok(), "TAP deletion should succeed");
let exists_after = tap_exists(tap_name).unwrap();
assert!(!exists_after, "TAP should not exist after deletion");
}
}