diff --git a/QemuVmManager.Core/QemuCommandBuilder.cs b/QemuVmManager.Core/QemuCommandBuilder.cs index f632561..c68c6d7 100644 --- a/QemuVmManager.Core/QemuCommandBuilder.cs +++ b/QemuVmManager.Core/QemuCommandBuilder.cs @@ -33,11 +33,17 @@ public class QemuCommandBuilder // Storage configuration AddStorageConfiguration(); + // OS Detection Debug + Console.WriteLine($"Debug: OS Detection - IsMacOS: {OperatingSystem.IsMacOS()}, IsLinux: {OperatingSystem.IsLinux()}, IsWindows: {OperatingSystem.IsWindows()}"); + // Network configuration Console.WriteLine("Debug: About to add network configuration"); AddNetworkConfiguration(); Console.WriteLine("Debug: Network configuration added"); + // Add port forwarding for bridge networking if needed + AddPortForwardingForBridge(); + // Display configuration AddDisplayConfiguration(); @@ -78,6 +84,355 @@ public class QemuCommandBuilder return string.Join(" ", _arguments); } + private void AddPortForwardingForBridge() + { + var customForwards = GetCustomPortForwards(); + if (string.IsNullOrEmpty(customForwards)) + return; + + Console.WriteLine($"Info: Bridge networking detected - port forwarding will be configured via iptables"); + Console.WriteLine($"Info: Custom port forwards: {customForwards}"); + + // Automatically configure iptables port forwarding + ConfigureIptablesPortForwarding(); + } + + private void ConfigureIptablesPortForwarding() + { + try + { + var configFile = "port-forwards.json"; + if (!File.Exists(configFile)) + return; + + var json = File.ReadAllText(configFile); + var portForwards = System.Text.Json.JsonSerializer.Deserialize>(json) ?? new List(); + + // Filter port forwards for this VM + var vmForwards = portForwards.Where(pf => pf.VmName == _config.Name).ToList(); + + if (!vmForwards.Any()) + return; + + Console.WriteLine($"🔧 Automatically configuring iptables port forwarding for VM '{_config.Name}'..."); + + foreach (var pf in vmForwards) + { + // Get the VM's IP address from the bridge network + var vmIp = GetVmIpFromBridge(); + + if (string.IsNullOrEmpty(vmIp)) + { + Console.WriteLine($"⚠️ Warning: Could not determine VM IP for port forward {pf.HostPort}:{pf.VmPort}"); + Console.WriteLine($" Port forwarding will be configured once the VM gets an IP address"); + continue; + } + + // Configure iptables rules + ConfigureIptablesRule(pf.HostPort, pf.VmPort, vmIp, pf.Protocol); + } + + Console.WriteLine($"✅ Port forwarding configuration completed for VM '{_config.Name}'"); + } + catch (Exception ex) + { + Console.WriteLine($"❌ Error configuring iptables port forwarding: {ex.Message}"); + } + } + + private string? GetVmIpFromBridge() + { + try + { + var bridgeName = _config.Network.Bridge ?? "virbr0"; + + // Method 1: Check libvirt DHCP leases + var vmIp = GetVmIpFromLibvirtDhcp(); + if (!string.IsNullOrEmpty(vmIp)) + { + Console.WriteLine($" 📡 Found VM IP {vmIp} from libvirt DHCP leases"); + return vmIp; + } + + // Method 2: Check system DHCP leases file + vmIp = GetVmIpFromDhcpLeases(); + if (!string.IsNullOrEmpty(vmIp)) + { + Console.WriteLine($" 📡 Found VM IP {vmIp} from system DHCP leases"); + return vmIp; + } + + // Method 3: Use ARP to discover VM IP on the bridge + vmIp = GetVmIpFromArp(bridgeName); + if (!string.IsNullOrEmpty(vmIp)) + { + Console.WriteLine($" 📡 Found VM IP {vmIp} from ARP table"); + return vmIp; + } + + Console.WriteLine($" ⚠️ Could not determine VM IP automatically"); + Console.WriteLine($" Port forwarding will be configured once the VM gets an IP address"); + return null; + } + catch (Exception ex) + { + Console.WriteLine($" ❌ Error getting VM IP: {ex.Message}"); + return null; + } + } + + private string? GetVmIpFromLibvirtDhcp() + { + try + { + // Use virsh to get network information and DHCP leases + var process = new System.Diagnostics.Process + { + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = "virsh", + Arguments = "net-dhcp-leases default", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + + process.Start(); + process.WaitForExit(); + + if (process.ExitCode != 0) + return null; + + var output = process.StandardOutput.ReadToEnd(); + + // Parse virsh output to find our VM's IP + // Example output format: + // Expiry Time MAC address Protocol IP address Hostname Client ID or DUID + // 2025-09-01 11:00:00 aa:a0:cc:aa:23:b5 ipv4 192.168.122.100 ubuntu-desktop - + + var lines = output.Split('\n'); + foreach (var line in lines) + { + if (line.Contains("ubuntu-desktop") || line.Contains(_config.Name)) + { + var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length >= 4) + { + var ipAddress = parts[3]; + if (IsValidIpAddress(ipAddress)) + { + return ipAddress; + } + } + } + } + + return null; + } + catch + { + return null; + } + } + + private string? GetVmIpFromDhcpLeases() + { + try + { + // Check system DHCP leases file (common locations) + var leaseFiles = new[] + { + "/var/lib/libvirt/dnsmasq/virbr0.status", + "/var/lib/dhcp/dhcpd.leases", + "/var/lib/dhcpcd/dhcpcd.leases" + }; + + foreach (var leaseFile in leaseFiles) + { + if (File.Exists(leaseFile)) + { + var content = File.ReadAllText(leaseFile); + var vmIp = ParseDhcpLeaseFile(content); + if (!string.IsNullOrEmpty(vmIp)) + return vmIp; + } + } + + return null; + } + catch + { + return null; + } + } + + private string? GetVmIpFromArp(string bridgeName) + { + try + { + // Use arp command to find devices on the bridge + var process = new System.Diagnostics.Process + { + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = "arp", + Arguments = $"-n -i {bridgeName}", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + + process.Start(); + process.WaitForExit(); + + if (process.ExitCode != 0) + return null; + + var output = process.StandardOutput.ReadToEnd(); + + // Parse arp output to find VM IPs + // Example: Address HWtype HWaddress Flags Mask Iface + // 192.168.122.100 ether aa:a0:cc:aa:23:b5 C virbr0 + + var lines = output.Split('\n'); + foreach (var line in lines) + { + if (line.Contains(bridgeName) && !line.Contains("192.168.122.1")) + { + var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length >= 1) + { + var ipAddress = parts[0]; + if (IsValidIpAddress(ipAddress) && ipAddress != "192.168.122.1") + { + return ipAddress; + } + } + } + } + + return null; + } + catch + { + return null; + } + } + + private string? ParseDhcpLeaseFile(string content) + { + try + { + // Simple parsing of DHCP lease files + // Look for lease entries with our VM's name or MAC + var lines = content.Split('\n'); + foreach (var line in lines) + { + if (line.Contains("lease") && line.Contains("192.168.122.")) + { + var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var part in parts) + { + if (IsValidIpAddress(part) && part != "192.168.122.1") + { + return part; + } + } + } + } + + return null; + } + catch + { + return null; + } + } + + private bool IsValidIpAddress(string ip) + { + try + { + if (string.IsNullOrWhiteSpace(ip)) + return false; + + var parts = ip.Split('.'); + if (parts.Length != 4) + return false; + + foreach (var part in parts) + { + if (!int.TryParse(part, out var num) || num < 0 || num > 255) + return false; + } + + return true; + } + catch + { + return false; + } + } + + private void ConfigureIptablesRule(int hostPort, int vmPort, string vmIp, string protocol) + { + try + { + var protocolLower = protocol.ToLower(); + + // Create iptables rules for port forwarding + var natRule = $"-t nat -A PREROUTING -p {protocolLower} --dport {hostPort} -j DNAT --to-destination {vmIp}:{vmPort}"; + var forwardRule = $"-A FORWARD -p {protocolLower} --dport {vmPort} -d {vmIp} -j ACCEPT"; + + // Execute iptables commands + ExecuteIptablesCommand(natRule); + ExecuteIptablesCommand(forwardRule); + + Console.WriteLine($" ✅ Port {hostPort} → {vmIp}:{vmPort} ({protocol})"); + } + catch (Exception ex) + { + Console.WriteLine($" ❌ Failed to configure port {hostPort}:{vmPort}: {ex.Message}"); + } + } + + private void ExecuteIptablesCommand(string rule) + { + try + { + // Use Process.Start to execute iptables command + var process = new System.Diagnostics.Process + { + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = "sudo", + Arguments = $"iptables {rule}", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + + process.Start(); + process.WaitForExit(); + + if (process.ExitCode != 0) + { + var error = process.StandardError.ReadToEnd(); + throw new Exception($"iptables command failed: {error}"); + } + } + catch (Exception ex) + { + throw new Exception($"Failed to execute iptables command: {ex.Message}"); + } + } + private string GetCustomPortForwards() { try