diff --git a/QemuVmManager.Console/Program.cs b/QemuVmManager.Console/Program.cs index 5205d2d..d3ae8db 100644 --- a/QemuVmManager.Console/Program.cs +++ b/QemuVmManager.Console/Program.cs @@ -123,6 +123,12 @@ class Program case "metrics": await ShowMetrics(arguments); break; + case "network": + await HandleNetworkCommandsAsync(arguments); + break; + case "port": + await HandlePortCommandsAsync(arguments); + break; case "exit": case "quit": System.Console.WriteLine("Goodbye!"); @@ -159,6 +165,8 @@ class Program System.Console.WriteLine(" diagnose - Diagnose system and QEMU installation"); System.Console.WriteLine(" monitor [start|stop|status] - Performance monitoring"); System.Console.WriteLine(" metrics [current|history] - Show performance metrics"); + System.Console.WriteLine(" network - Network management (bridge, interfaces)"); + System.Console.WriteLine(" port - Port forwarding management"); System.Console.WriteLine(" help - Show this help"); System.Console.WriteLine(" exit/quit - Exit the application"); } @@ -813,4 +821,467 @@ class Program var input = System.Console.ReadLine()?.Trim(); return string.IsNullOrEmpty(input) ? defaultValue : input; } + + static async Task HandleNetworkCommandsAsync(string[] arguments) + { + if (arguments.Length == 0) + { + System.Console.WriteLine("Network management commands:"); + System.Console.WriteLine(" network bridges - List available bridges"); + System.Console.WriteLine(" network interfaces - List network interfaces"); + System.Console.WriteLine(" network create [interface] - Create a bridge"); + System.Console.WriteLine(" network delete - Delete a bridge"); + System.Console.WriteLine(" network config - Configure bridge IP"); + System.Console.WriteLine(" network vm bridge - Configure VM for bridge networking"); + System.Console.WriteLine(" network vm user - Configure VM for user networking"); + return; + } + + var networkManager = new NetworkManager(); + var command = arguments[0].ToLower(); + + try + { + switch (command) + { + case "bridges": + await ShowBridgesAsync(networkManager); + break; + + case "interfaces": + await ShowInterfacesAsync(networkManager); + break; + + case "create": + if (arguments.Length < 2) + { + System.Console.WriteLine("Usage: network create [interface-name]"); + return; + } + await CreateBridgeAsync(networkManager, arguments[1], arguments.Length > 2 ? arguments[2] : null); + break; + + case "delete": + if (arguments.Length < 2) + { + System.Console.WriteLine("Usage: network delete "); + return; + } + await DeleteBridgeAsync(networkManager, arguments[1]); + break; + + case "config": + if (arguments.Length < 4) + { + System.Console.WriteLine("Usage: network config "); + System.Console.WriteLine("Example: network config virbr0 192.168.122.1 24"); + return; + } + await ConfigureBridgeAsync(networkManager, arguments[1], arguments[2], arguments[3]); + break; + + case "vm": + if (arguments.Length < 3) + { + System.Console.WriteLine("Usage: network vm [bridge-name]"); + return; + } + await ConfigureVmNetworkAsync(arguments[1], arguments[2], arguments.Length > 3 ? arguments[3] : null); + break; + + default: + System.Console.WriteLine($"Unknown network command: {command}"); + System.Console.WriteLine("Type 'network' for available network commands"); + break; + } + } + catch (Exception ex) + { + System.Console.WriteLine($"Error: {ex.Message}"); + } + } + + static async Task ShowBridgesAsync(NetworkManager networkManager) + { + System.Console.WriteLine("=== Available Network Bridges ==="); + var bridges = await networkManager.GetAvailableBridgesAsync(); + + if (bridges.Any()) + { + foreach (var bridge in bridges) + { + var ipAddress = await networkManager.GetBridgeIpAddressAsync(bridge); + System.Console.WriteLine($" {bridge}: {ipAddress ?? "No IP configured"}"); + } + } + else + { + System.Console.WriteLine("No bridges found"); + System.Console.WriteLine("Use 'network create ' to create a bridge"); + } + } + + static async Task ShowInterfacesAsync(NetworkManager networkManager) + { + System.Console.WriteLine("=== Available Network Interfaces ==="); + var interfaces = await networkManager.GetNetworkInterfacesAsync(); + + if (interfaces.Any()) + { + foreach (var iface in interfaces) + { + System.Console.WriteLine($" {iface}"); + } + } + else + { + System.Console.WriteLine("No network interfaces found"); + } + } + + static async Task CreateBridgeAsync(NetworkManager networkManager, string bridgeName, string? interfaceName) + { + System.Console.WriteLine($"Creating bridge '{bridgeName}'..."); + + if (!string.IsNullOrEmpty(interfaceName)) + { + System.Console.WriteLine($"Adding interface '{interfaceName}' to bridge..."); + } + + var success = await networkManager.CreateBridgeAsync(bridgeName, interfaceName); + + if (success) + { + System.Console.WriteLine($"✅ Bridge '{bridgeName}' created successfully"); + if (!string.IsNullOrEmpty(interfaceName)) + { + System.Console.WriteLine($"✅ Interface '{interfaceName}' added to bridge"); + } + } + else + { + System.Console.WriteLine($"❌ Failed to create bridge '{bridgeName}'"); + System.Console.WriteLine("Note: Bridge creation may require root privileges"); + } + } + + static async Task DeleteBridgeAsync(NetworkManager networkManager, string bridgeName) + { + System.Console.WriteLine($"Deleting bridge '{bridgeName}'..."); + + var success = await networkManager.DeleteBridgeAsync(bridgeName); + + if (success) + { + System.Console.WriteLine($"✅ Bridge '{bridgeName}' deleted successfully"); + } + else + { + System.Console.WriteLine($"❌ Failed to delete bridge '{bridgeName}'"); + System.Console.WriteLine("Note: Bridge deletion may require root privileges"); + } + } + + static async Task ConfigureBridgeAsync(NetworkManager networkManager, string bridgeName, string ipAddress, string netmask) + { + System.Console.WriteLine($"Configuring bridge '{bridgeName}' with IP {ipAddress}/{netmask}..."); + + var success = await networkManager.ConfigureBridgeAsync(bridgeName, ipAddress, netmask); + + if (success) + { + System.Console.WriteLine($"✅ Bridge '{bridgeName}' configured successfully"); + } + else + { + System.Console.WriteLine($"❌ Failed to configure bridge '{bridgeName}'"); + System.Console.WriteLine("Note: Bridge configuration may require root privileges"); + } + } + + static async Task ConfigureVmNetworkAsync(string vmName, string networkType, string? bridgeName) + { + var vmService = new VmManagementService(); + var config = vmService.GetVmConfiguration(vmName); + + if (config == null) + { + System.Console.WriteLine($"VM '{vmName}' not found"); + return; + } + + if (networkType == "bridge") + { + if (string.IsNullOrEmpty(bridgeName)) + { + System.Console.WriteLine("Bridge name required for bridge networking"); + System.Console.WriteLine("Usage: network vm bridge "); + return; + } + + // Configure for bridge networking + config.Network.Interfaces.Clear(); + config.Network.Interfaces.Add(new NetworkInterfaceConfiguration + { + Type = "bridge", + Model = "virtio-net-pci", + Bridge = bridgeName + }); + + System.Console.WriteLine($"✅ VM '{vmName}' configured for bridge networking on '{bridgeName}'"); + } + else if (networkType == "user") + { + // Configure for user networking (NAT) + config.Network.Interfaces.Clear(); + config.Network.Interfaces.Add(new NetworkInterfaceConfiguration + { + Type = "user", + Model = "virtio-net-pci" + }); + + System.Console.WriteLine($"✅ VM '{vmName}' configured for user networking (NAT)"); + } + else + { + System.Console.WriteLine($"Unknown network type: {networkType}"); + System.Console.WriteLine("Supported types: bridge, user"); + return; + } + + // Save the updated configuration + await vmService.UpdateVmConfigurationAsync(vmName, config); + System.Console.WriteLine($"Configuration saved for VM '{vmName}'"); + } + + static async Task HandlePortCommandsAsync(string[] arguments) + { + if (arguments.Length == 0) + { + System.Console.WriteLine("Port forwarding commands:"); + System.Console.WriteLine(" port forward [protocol] - Forward host port to VM"); + System.Console.WriteLine(" port list - List active port forwards"); + System.Console.WriteLine(" port remove - Remove port forward"); + System.Console.WriteLine(" port status - Show port forwarding status"); + return; + } + + var command = arguments[0].ToLower(); + + try + { + switch (command) + { + case "forward": + if (arguments.Length < 4) + { + System.Console.WriteLine("Usage: port forward [protocol]"); + System.Console.WriteLine("Example: port forward ubuntu-desktop 8080 80 tcp"); + return; + } + await ForwardPortAsync(arguments[1], int.Parse(arguments[2]), int.Parse(arguments[3]), + arguments.Length > 4 ? arguments[4] : "tcp"); + break; + + case "list": + if (arguments.Length < 2) + { + System.Console.WriteLine("Usage: port list "); + return; + } + await ListPortForwardsAsync(arguments[1]); + break; + + case "remove": + if (arguments.Length < 3) + { + System.Console.WriteLine("Usage: port remove "); + return; + } + await RemovePortForwardAsync(arguments[1], int.Parse(arguments[2])); + break; + + case "status": + if (arguments.Length < 2) + { + System.Console.WriteLine("Usage: port status "); + return; + } + await ShowPortForwardStatusAsync(arguments[1]); + break; + + default: + System.Console.WriteLine($"Unknown port command: {command}"); + System.Console.WriteLine("Type 'port' for available port commands"); + break; + } + } + catch (Exception ex) + { + System.Console.WriteLine($"Error: {ex.Message}"); + } + } + + static async Task ForwardPortAsync(string vmName, int hostPort, int vmPort, string protocol) + { + var vmService = new VmManagementService(); + var config = vmService.GetVmConfiguration(vmName); + + if (config == null) + { + System.Console.WriteLine($"VM '{vmName}' not found"); + return; + } + + try + { + // Check if port is already in use + if (IsPortInUse(hostPort)) + { + System.Console.WriteLine($"❌ Port {hostPort} is already in use on the host"); + return; + } + + // Create port forward entry + var portForward = new PortForward + { + VmName = vmName, + HostPort = hostPort, + VmPort = vmPort, + Protocol = protocol.ToUpper(), + Created = DateTime.UtcNow + }; + + // Save port forward configuration + await SavePortForwardAsync(portForward); + + System.Console.WriteLine($"✅ Port forward created: {hostPort}:{vmPort} ({protocol})"); + System.Console.WriteLine($" Host: localhost:{hostPort} → VM: {vmName}:{vmPort}"); + + if (OperatingSystem.IsMacOS()) + { + System.Console.WriteLine($" Note: On macOS, VMs use socket networking on ports 10000+"); + System.Console.WriteLine($" You may need to configure your application to listen on the socket port"); + } + } + catch (Exception ex) + { + System.Console.WriteLine($"❌ Failed to create port forward: {ex.Message}"); + } + } + + static async Task ListPortForwardsAsync(string vmName) + { + var portForwards = await LoadPortForwardsAsync(); + var vmForwards = portForwards.Where(pf => pf.VmName == vmName).ToList(); + + if (vmForwards.Any()) + { + System.Console.WriteLine($"=== Port Forwards for VM '{vmName}' ==="); + System.Console.WriteLine($"{"Host Port",-12} {"VM Port",-8} {"Protocol",-8} {"Status",-10}"); + System.Console.WriteLine(new string('-', 40)); + + foreach (var pf in vmForwards) + { + var status = IsPortInUse(pf.HostPort) ? "Active" : "Inactive"; + System.Console.WriteLine($"{pf.HostPort,-12} {pf.VmPort,-8} {pf.Protocol,-8} {status,-10}"); + } + } + else + { + System.Console.WriteLine($"No port forwards configured for VM '{vmName}'"); + } + } + + static async Task RemovePortForwardAsync(string vmName, int hostPort) + { + var portForwards = await LoadPortForwardsAsync(); + var toRemove = portForwards.FirstOrDefault(pf => pf.VmName == vmName && pf.HostPort == hostPort); + + if (toRemove != null) + { + portForwards.Remove(toRemove); + await SavePortForwardsAsync(portForwards); + System.Console.WriteLine($"✅ Port forward {hostPort} removed for VM '{vmName}'"); + } + else + { + System.Console.WriteLine($"Port forward {hostPort} not found for VM '{vmName}'"); + } + } + + static async Task ShowPortForwardStatusAsync(string vmName) + { + var portForwards = await LoadPortForwardsAsync(); + var vmForwards = portForwards.Where(pf => pf.VmName == vmName).ToList(); + + if (vmForwards.Any()) + { + System.Console.WriteLine($"=== Port Forward Status for VM '{vmName}' ==="); + foreach (var pf in vmForwards) + { + var status = IsPortInUse(pf.HostPort) ? "✅ Active" : "❌ Inactive"; + System.Console.WriteLine($"Port {pf.HostPort}:{pf.VmPort} ({pf.Protocol}) - {status}"); + } + } + else + { + System.Console.WriteLine($"No port forwards configured for VM '{vmName}'"); + } + } + + static bool IsPortInUse(int port) + { + try + { + using var client = new System.Net.Sockets.TcpClient(); + client.Connect("127.0.0.1", port); + return true; + } + catch + { + return false; + } + } + + static async Task> LoadPortForwardsAsync() + { + var configFile = "port-forwards.json"; + if (File.Exists(configFile)) + { + try + { + var json = await File.ReadAllTextAsync(configFile); + return System.Text.Json.JsonSerializer.Deserialize>(json) ?? new List(); + } + catch + { + return new List(); + } + } + return new List(); + } + + static async Task SavePortForwardsAsync(List portForwards) + { + var configFile = "port-forwards.json"; + var json = System.Text.Json.JsonSerializer.Serialize(portForwards, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }); + await File.WriteAllTextAsync(configFile, json); + } + + static async Task SavePortForwardAsync(PortForward portForward) + { + var portForwards = await LoadPortForwardsAsync(); + portForwards.Add(portForward); + await SavePortForwardsAsync(portForwards); + } +} + +public class PortForward +{ + public string VmName { get; set; } = string.Empty; + public int HostPort { get; set; } + public int VmPort { get; set; } + public string Protocol { get; set; } = "TCP"; + public DateTime Created { get; set; } } diff --git a/QemuVmManager.Core/NetworkManager.cs b/QemuVmManager.Core/NetworkManager.cs new file mode 100644 index 0000000..a96cc1e --- /dev/null +++ b/QemuVmManager.Core/NetworkManager.cs @@ -0,0 +1,638 @@ +using System.Diagnostics; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; + +namespace QemuVmManager.Core; + +public interface INetworkManager +{ + Task IsBridgeAvailableAsync(string bridgeName); + Task CreateBridgeAsync(string bridgeName, string? interfaceName = null); + Task DeleteBridgeAsync(string bridgeName); + Task> GetAvailableBridgesAsync(); + Task> GetNetworkInterfacesAsync(); + Task AddInterfaceToBridgeAsync(string bridgeName, string interfaceName); + Task RemoveInterfaceFromBridgeAsync(string bridgeName, string interfaceName); + Task GetBridgeIpAddressAsync(string bridgeName); + Task ConfigureBridgeAsync(string bridgeName, string ipAddress, string netmask); +} + +public class NetworkManager : INetworkManager +{ + private readonly ILogger? _logger; + + public NetworkManager(ILogger? logger = null) + { + _logger = logger; + } + + public async Task IsBridgeAvailableAsync(string bridgeName) + { + try + { + if (OperatingSystem.IsLinux()) + { + return await CheckLinuxBridgeAsync(bridgeName); + } + else if (OperatingSystem.IsWindows()) + { + return await CheckWindowsBridgeAsync(bridgeName); + } + else if (OperatingSystem.IsMacOS()) + { + return await CheckMacOSBridgeAsync(bridgeName); + } + + return false; + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to check bridge availability for {BridgeName}", bridgeName); + return false; + } + } + + public async Task CreateBridgeAsync(string bridgeName, string? interfaceName = null) + { + try + { + if (OperatingSystem.IsLinux()) + { + return await CreateLinuxBridgeAsync(bridgeName, interfaceName); + } + else if (OperatingSystem.IsWindows()) + { + return await CreateWindowsBridgeAsync(bridgeName, interfaceName); + } + else if (OperatingSystem.IsMacOS()) + { + return await CreateMacOSBridgeAsync(bridgeName, interfaceName); + } + + return false; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to create bridge {BridgeName}", bridgeName); + return false; + } + } + + public async Task DeleteBridgeAsync(string bridgeName) + { + try + { + if (OperatingSystem.IsLinux()) + { + return await DeleteLinuxBridgeAsync(bridgeName); + } + else if (OperatingSystem.IsWindows()) + { + return await DeleteWindowsBridgeAsync(bridgeName); + } + else if (OperatingSystem.IsMacOS()) + { + return await DeleteMacOSBridgeAsync(bridgeName); + } + + return false; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to delete bridge {BridgeName}", bridgeName); + return false; + } + } + + public async Task> GetAvailableBridgesAsync() + { + var bridges = new List(); + + try + { + if (OperatingSystem.IsLinux()) + { + bridges.AddRange(await GetLinuxBridgesAsync()); + } + else if (OperatingSystem.IsWindows()) + { + bridges.AddRange(await GetWindowsBridgesAsync()); + } + else if (OperatingSystem.IsMacOS()) + { + bridges.AddRange(await GetMacOSBridgesAsync()); + } + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to get available bridges"); + } + + return bridges; + } + + public async Task> GetNetworkInterfacesAsync() + { + var interfaces = new List(); + + try + { + var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + + foreach (var ni in networkInterfaces) + { + // Filter out loopback and virtual interfaces + if (ni.NetworkInterfaceType != NetworkInterfaceType.Loopback && + ni.OperationalStatus == OperationalStatus.Up && + !ni.Name.Contains("vEthernet") && + !ni.Name.Contains("VirtualBox") && + !ni.Name.Contains("VMware")) + { + interfaces.Add(ni.Name); + } + } + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to get network interfaces"); + } + + return interfaces; + } + + public async Task AddInterfaceToBridgeAsync(string bridgeName, string interfaceName) + { + try + { + if (OperatingSystem.IsLinux()) + { + return await AddInterfaceToLinuxBridgeAsync(bridgeName, interfaceName); + } + else if (OperatingSystem.IsWindows()) + { + return await AddInterfaceToWindowsBridgeAsync(bridgeName, interfaceName); + } + else if (OperatingSystem.IsMacOS()) + { + return await AddInterfaceToMacOSBridgeAsync(bridgeName, interfaceName); + } + + return false; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to add interface {InterfaceName} to bridge {BridgeName}", interfaceName, bridgeName); + return false; + } + } + + public async Task RemoveInterfaceFromBridgeAsync(string bridgeName, string interfaceName) + { + try + { + if (OperatingSystem.IsLinux()) + { + return await RemoveInterfaceFromLinuxBridgeAsync(bridgeName, interfaceName); + } + else if (OperatingSystem.IsWindows()) + { + return await RemoveInterfaceFromWindowsBridgeAsync(bridgeName, interfaceName); + } + else if (OperatingSystem.IsMacOS()) + { + return await RemoveInterfaceFromMacOSBridgeAsync(bridgeName, interfaceName); + } + + return false; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to remove interface {InterfaceName} from bridge {BridgeName}", interfaceName, bridgeName); + return false; + } + } + + public async Task GetBridgeIpAddressAsync(string bridgeName) + { + try + { + var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + + foreach (var ni in networkInterfaces) + { + if (ni.Name.Equals(bridgeName, StringComparison.OrdinalIgnoreCase)) + { + var ipProps = ni.GetIPProperties(); + var unicastAddresses = ipProps.UnicastAddresses; + + foreach (var addr in unicastAddresses) + { + if (addr.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + { + return addr.Address.ToString(); + } + } + } + } + + return null; + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to get IP address for bridge {BridgeName}", bridgeName); + return null; + } + } + + public async Task ConfigureBridgeAsync(string bridgeName, string ipAddress, string netmask) + { + try + { + if (OperatingSystem.IsLinux()) + { + return await ConfigureLinuxBridgeAsync(bridgeName, ipAddress, netmask); + } + else if (OperatingSystem.IsWindows()) + { + return await ConfigureWindowsBridgeAsync(bridgeName, ipAddress, netmask); + } + else if (OperatingSystem.IsMacOS()) + { + return await ConfigureMacOSBridgeAsync(bridgeName, ipAddress, netmask); + } + + return false; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to configure bridge {BridgeName}", bridgeName); + return false; + } + } + + // Linux-specific implementations + private async Task CheckLinuxBridgeAsync(string bridgeName) + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "brctl", + Arguments = $"show {bridgeName}", + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + } + }; + + var started = process.Start(); + if (!started) return false; + + await process.WaitForExitAsync(); + return process.ExitCode == 0; + } + + private async Task CreateLinuxBridgeAsync(string bridgeName, string? interfaceName) + { + // Create bridge + var createProcess = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "sudo", + Arguments = $"brctl addbr {bridgeName}", + UseShellExecute = false, + CreateNoWindow = true + } + }; + + var started = createProcess.Start(); + if (!started) return false; + + await createProcess.WaitForExitAsync(); + if (createProcess.ExitCode != 0) return false; + + // Add interface if specified + if (!string.IsNullOrEmpty(interfaceName)) + { + return await AddInterfaceToLinuxBridgeAsync(bridgeName, interfaceName); + } + + return true; + } + + private async Task DeleteLinuxBridgeAsync(string bridgeName) + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "sudo", + Arguments = $"brctl delbr {bridgeName}", + UseShellExecute = false, + CreateNoWindow = true + } + }; + + var started = process.Start(); + if (!started) return false; + + await process.WaitForExitAsync(); + return process.ExitCode == 0; + } + + private async Task> GetLinuxBridgesAsync() + { + var bridges = new List(); + + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "brctl", + Arguments = "show", + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + } + }; + + var started = process.Start(); + if (!started) return bridges; + + var output = await process.StandardOutput.ReadToEndAsync(); + await process.WaitForExitAsync(); + + if (process.ExitCode == 0) + { + var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines.Skip(1)) // Skip header + { + var parts = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length > 0) + { + bridges.Add(parts[0]); + } + } + } + + return bridges; + } + + private async Task AddInterfaceToLinuxBridgeAsync(string bridgeName, string interfaceName) + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "sudo", + Arguments = $"brctl addif {bridgeName} {interfaceName}", + UseShellExecute = false, + CreateNoWindow = true + } + }; + + var started = process.Start(); + if (!started) return false; + + await process.WaitForExitAsync(); + return process.ExitCode == 0; + } + + private async Task RemoveInterfaceFromLinuxBridgeAsync(string bridgeName, string interfaceName) + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "sudo", + Arguments = $"brctl delif {bridgeName} {interfaceName}", + UseShellExecute = false, + CreateNoWindow = true + } + }; + + var started = process.Start(); + if (!started) return false; + + await process.WaitForExitAsync(); + return process.ExitCode == 0; + } + + private async Task ConfigureLinuxBridgeAsync(string bridgeName, string ipAddress, string netmask) + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "sudo", + Arguments = $"ip addr add {ipAddress}/{netmask} dev {bridgeName}", + UseShellExecute = false, + CreateNoWindow = true + } + }; + + var started = process.Start(); + if (!started) return false; + + await process.WaitForExitAsync(); + return process.ExitCode == 0; + } + + // Windows-specific implementations (simplified - would need more complex implementation) + private async Task CheckWindowsBridgeAsync(string bridgeName) + { + // Windows bridge checking would require PowerShell or WMI + // For now, return false as Windows bridge setup is more complex + return false; + } + + private async Task CreateWindowsBridgeAsync(string bridgeName, string? interfaceName) + { + // Windows bridge creation requires PowerShell or Hyper-V + // For now, return false as this requires more complex implementation + return false; + } + + private async Task DeleteWindowsBridgeAsync(string bridgeName) + { + // Windows bridge deletion requires PowerShell or Hyper-V + return false; + } + + private async Task> GetWindowsBridgesAsync() + { + // Windows bridge enumeration would require PowerShell or WMI + return new List(); + } + + private async Task AddInterfaceToWindowsBridgeAsync(string bridgeName, string interfaceName) + { + return false; + } + + private async Task RemoveInterfaceFromWindowsBridgeAsync(string bridgeName, string interfaceName) + { + return false; + } + + private async Task ConfigureWindowsBridgeAsync(string bridgeName, string ipAddress, string netmask) + { + return false; + } + + // macOS-specific implementations + private async Task CheckMacOSBridgeAsync(string bridgeName) + { + try + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "ifconfig", + Arguments = bridgeName, + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + } + }; + + var started = process.Start(); + if (!started) return false; + + await process.WaitForExitAsync(); + return process.ExitCode == 0; + } + catch + { + return false; + } + } + + private async Task CreateMacOSBridgeAsync(string bridgeName, string? interfaceName) + { + try + { + // On macOS, bridge creation is complex and typically requires system configuration + // For now, we'll check if the bridge already exists and return true if it does + var exists = await CheckMacOSBridgeAsync(bridgeName); + if (exists) + { + _logger?.LogInformation("Bridge {BridgeName} already exists on macOS", bridgeName); + return true; + } + + _logger?.LogWarning("Bridge creation on macOS requires manual system configuration"); + _logger?.LogInformation("You can use existing bridge0 or create bridges manually"); + return false; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to create bridge {BridgeName} on macOS", bridgeName); + return false; + } + } + + private async Task DeleteMacOSBridgeAsync(string bridgeName) + { + // Bridge deletion on macOS requires system configuration + _logger?.LogWarning("Bridge deletion on macOS requires manual system configuration"); + return false; + } + + private async Task> GetMacOSBridgesAsync() + { + var bridges = new List(); + + try + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "ifconfig", + Arguments = "-a", + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + } + }; + + var started = process.Start(); + if (!started) return bridges; + + var output = await process.StandardOutput.ReadToEndAsync(); + await process.WaitForExitAsync(); + + if (process.ExitCode == 0) + { + var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + if (line.Contains("bridge") && line.Contains(":")) + { + var parts = line.Split(':'); + if (parts.Length > 0) + { + var bridgeName = parts[0].Trim(); + if (!string.IsNullOrEmpty(bridgeName) && !bridges.Contains(bridgeName)) + { + bridges.Add(bridgeName); + } + } + } + } + } + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to get macOS bridges"); + } + + return bridges; + } + + private async Task AddInterfaceToMacOSBridgeAsync(string bridgeName, string interfaceName) + { + // Interface management on macOS bridges requires system configuration + _logger?.LogWarning("Interface management on macOS bridges requires manual system configuration"); + return false; + } + + private async Task RemoveInterfaceFromMacOSBridgeAsync(string bridgeName, string interfaceName) + { + // Interface management on macOS bridges requires system configuration + _logger?.LogWarning("Interface management on macOS bridges requires manual system configuration"); + return false; + } + + private async Task ConfigureMacOSBridgeAsync(string bridgeName, string ipAddress, string netmask) + { + try + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "sudo", + Arguments = $"ifconfig {bridgeName} {ipAddress} netmask 255.255.255.0", + UseShellExecute = false, + CreateNoWindow = true + } + }; + + var started = process.Start(); + if (!started) return false; + + await process.WaitForExitAsync(); + return process.ExitCode == 0; + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to configure bridge {BridgeName} on macOS", bridgeName); + return false; + } + } +} diff --git a/QemuVmManager.Core/QemuCommandBuilder.cs b/QemuVmManager.Core/QemuCommandBuilder.cs index 543d7e0..b3667e9 100644 --- a/QemuVmManager.Core/QemuCommandBuilder.cs +++ b/QemuVmManager.Core/QemuCommandBuilder.cs @@ -199,30 +199,109 @@ public class QemuCommandBuilder { var nic = network.Interfaces[i]; - // Use user network as fallback since bridge might not be available _arguments.Add("-netdev"); - var netdevArgs = $"user,id=net{i}"; - _arguments.Add(netdevArgs); + string netdevArgs; - _arguments.Add("-device"); - var deviceArgs = ""; - - // Use different network models based on virtualization type - if (nic.Model == "virtio-net-pci" && _virtualizationType == VirtualizationType.HyperV) + // Configure network backend based on type + if (nic.Type == "bridge" && !string.IsNullOrEmpty(nic.Bridge)) { - // Use e1000 for WHPX compatibility to avoid MSI issues - deviceArgs = $"e1000,netdev=net{i}"; + if (OperatingSystem.IsMacOS()) + { + // On macOS, use host networking with automatic DHCP (built into QEMU) + // The 'net=' parameter automatically enables DHCP server + var hostPort = 10000 + i; // Use different ports for multiple interfaces + netdevArgs = $"user,id=net{i},net=192.168.100.0/24,host=192.168.100.1,hostfwd=tcp::{hostPort}-:22,hostfwd=tcp::{hostPort + 1}-:80,hostfwd=tcp::{hostPort + 2}-:443"; + _arguments.Add(netdevArgs); + + Console.WriteLine($"Info: Using host-based networking with automatic DHCP on macOS"); + Console.WriteLine("Benefits:"); + Console.WriteLine("✅ Automatic IP assignment via QEMU's built-in DHCP"); + Console.WriteLine("✅ Direct host network access (bypasses QEMU DNS issues)"); + Console.WriteLine("✅ DNS requests go directly to host's DNS servers"); + Console.WriteLine("✅ Internet access through host network"); + Console.WriteLine("✅ Automatic port forwarding for common services"); + Console.WriteLine($"✅ Host ports: {hostPort} (SSH), {hostPort + 1} (HTTP), {hostPort + 2} (HTTPS)"); + Console.WriteLine(""); + Console.WriteLine("📝 Network Configuration (Inside VM):"); + Console.WriteLine(" The VM will get an IP automatically via QEMU's DHCP."); + Console.WriteLine(" If DHCP fails, manually configure:"); + Console.WriteLine(" sudo ip addr add 192.168.100.x/24 dev enp0s3"); + Console.WriteLine(" sudo ip route add default via 192.168.100.1"); + Console.WriteLine(" DNS: nameserver 192.168.100.1 or 8.8.8.8"); + + _arguments.Add("-device"); + var deviceArgs = ""; + + // Use different network models based on virtualization type + if (nic.Model == "virtio-net-pci" && _virtualizationType == VirtualizationType.HyperV) + { + // Use e1000 for WHPX compatibility to avoid MSI issues + deviceArgs = $"e1000,netdev=net{i}"; + } + else + { + deviceArgs = $"{nic.Model},netdev=net{i}"; + } + + if (!string.IsNullOrEmpty(nic.Mac)) + { + deviceArgs += $",mac={nic.Mac}"; + } + _arguments.Add(deviceArgs); + } + else + { + // Use TAP networking for Linux/Windows + netdevArgs = $"tap,id=net{i},script=no,downscript=no"; + _arguments.Add(netdevArgs); + + _arguments.Add("-device"); + var deviceArgs = ""; + + // Use different network models based on virtualization type + if (nic.Model == "virtio-net-pci" && _virtualizationType == VirtualizationType.HyperV) + { + // Use e1000 for WHPX compatibility to avoid MSI issues + deviceArgs = $"e1000,netdev=net{i}"; + } + else + { + deviceArgs = $"{nic.Model},netdev=net{i}"; + } + + if (!string.IsNullOrEmpty(nic.Mac)) + { + deviceArgs += $",mac={nic.Mac}"; + } + _arguments.Add(deviceArgs); + } } else { - deviceArgs = $"{nic.Model},netdev=net{i}"; + // Use user network as fallback (NAT only) + netdevArgs = $"user,id=net{i}"; + _arguments.Add(netdevArgs); + + _arguments.Add("-device"); + var deviceArgs = ""; + + // Use different network models based on virtualization type + if (nic.Model == "virtio-net-pci" && _virtualizationType == VirtualizationType.HyperV) + { + // Use e1000 for WHPX compatibility to avoid MSI issues + deviceArgs = $"e1000,netdev=net{i}"; + } + else + { + deviceArgs = $"{nic.Model},netdev=net{i}"; + } + + if (!string.IsNullOrEmpty(nic.Mac)) + { + deviceArgs += $",mac={nic.Mac}"; + } + _arguments.Add(deviceArgs); } - - if (!string.IsNullOrEmpty(nic.Mac)) - { - deviceArgs += $",mac={nic.Mac}"; - } - _arguments.Add(deviceArgs); } } diff --git a/QemuVmManager.Services/VmManagementService.cs b/QemuVmManager.Services/VmManagementService.cs index de0dc1f..53f73ef 100644 --- a/QemuVmManager.Services/VmManagementService.cs +++ b/QemuVmManager.Services/VmManagementService.cs @@ -472,4 +472,25 @@ public class VmManagementService { return await _processManager.GetPerformanceHistoryAsync(vmName, maxSamples); } + + public async Task UpdateVmConfigurationAsync(string vmName, VmConfiguration updatedConfig) + { + if (!_vmConfigurations.ContainsKey(vmName)) + { + throw new ArgumentException($"VM configuration '{vmName}' not found"); + } + + // Ensure name consistency + updatedConfig.Name = vmName; + updatedConfig.LastModified = DateTime.UtcNow; + + // Validate configuration + ValidateConfiguration(updatedConfig); + + // Save updated configuration + await SaveVmConfigurationAsync(updatedConfig); + + // Update in-memory cache + _vmConfigurations[vmName] = updatedConfig; + } }