herolib-mos 0.3.13

Mycelium Operating System (MOS) - Network and VM abstraction layer
Documentation
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);
    }

    // create bridge if it doesn't exist
    let bridge_dev = super::bridge::create_bridge(bridge)?;

    // create tap
    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)
        )));
    }

    // attach bridge to tap
    super::interface::attach_to_bridge(name, bridge)?;

    // bring tap up
    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(());
    }

    // bring tap down
    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)
        )));
    }

    // delete tap
    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)));
    }

    // get attached bridge
    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() {
        // Test that tap_exists can check for non-existent TAP
        let result = tap_exists("non-existent-tap");
        assert!(result.is_ok());
        assert!(!result.unwrap());
    }
    
    #[test]
    fn test_delete_tap_nonexistent() {
        // Test deleting a TAP that doesn't exist (should not error)
        let result = delete_tap("non-existent-tap");
        assert!(result.is_ok());
    }
    
    // Integration tests - require root privileges
    #[cfg(target_os = "linux")]
    #[test]
    #[ignore]
    fn test_create_tap() {
        // This test requires root privileges
        // To run: sudo cargo test test_create_tap -- --ignored
        
        let tap_name = "test-tap";
        let bridge_name = "test-br";
        
        // Create TAP
        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);
        
        // Check TAP exists
        let exists = tap_exists(tap_name).unwrap();
        assert!(exists, "TAP should exist after creation");
        
        // Clean up
        let delete_result = delete_tap(tap_name);
        assert!(delete_result.is_ok(), "TAP deletion should succeed");
        
        // Verify TAP is gone
        let exists_after = tap_exists(tap_name).unwrap();
        assert!(!exists_after, "TAP should not exist after deletion");
    }
}