893 lines
31 KiB
C#
893 lines
31 KiB
C#
using QemuVmManager.Models;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.Json;
|
|
using System.Linq;
|
|
|
|
namespace QemuVmManager.Core;
|
|
|
|
public class QemuCommandBuilder
|
|
{
|
|
private readonly VmConfiguration _config;
|
|
private readonly List<string> _arguments = new();
|
|
private readonly VirtualizationType _virtualizationType;
|
|
|
|
public QemuCommandBuilder(VmConfiguration config, VirtualizationType virtualizationType = VirtualizationType.TCG)
|
|
{
|
|
_config = config;
|
|
_virtualizationType = virtualizationType;
|
|
}
|
|
|
|
public string BuildCommand()
|
|
{
|
|
_arguments.Clear();
|
|
|
|
// Basic QEMU command
|
|
_arguments.Add("qemu-system-x86_64");
|
|
|
|
// Machine and CPU configuration
|
|
AddMachineConfiguration();
|
|
AddCpuConfiguration();
|
|
AddMemoryConfiguration();
|
|
|
|
// 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();
|
|
|
|
// Boot configuration
|
|
AddBootConfiguration();
|
|
|
|
// Advanced features
|
|
AddAdvancedConfiguration();
|
|
|
|
// Extra arguments
|
|
_arguments.AddRange(_config.Advanced.ExtraArgs);
|
|
|
|
// Handle CPU model based on virtualization type
|
|
if (_virtualizationType == VirtualizationType.TCG || _virtualizationType == VirtualizationType.HyperV)
|
|
{
|
|
// Replace 'host' CPU model with 'qemu64' for TCG compatibility
|
|
for (int i = 0; i < _arguments.Count; i++)
|
|
{
|
|
if (_arguments[i] == "-cpu" && i + 1 < _arguments.Count && _arguments[i + 1] == "host")
|
|
{
|
|
_arguments[i + 1] = "qemu64";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove -enable-kvm if not using KVM
|
|
if (_virtualizationType != VirtualizationType.KVM)
|
|
{
|
|
_arguments.RemoveAll(arg => arg == "-enable-kvm");
|
|
}
|
|
|
|
// Add WHPX-specific configurations
|
|
if (_virtualizationType == VirtualizationType.HyperV)
|
|
{
|
|
AddWHPXSpecificConfiguration();
|
|
}
|
|
|
|
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<List<QemuVmManager.Models.PortForward>>(json) ?? new List<QemuVmManager.Models.PortForward>();
|
|
|
|
// 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
|
|
{
|
|
var configFile = "port-forwards.json";
|
|
if (!File.Exists(configFile))
|
|
return string.Empty;
|
|
|
|
var json = File.ReadAllText(configFile);
|
|
var portForwards = System.Text.Json.JsonSerializer.Deserialize<List<QemuVmManager.Models.PortForward>>(json) ?? new List<QemuVmManager.Models.PortForward>();
|
|
|
|
// Filter port forwards for this VM
|
|
var vmForwards = portForwards.Where(pf => pf.VmName == _config.Name).ToList();
|
|
|
|
if (!vmForwards.Any())
|
|
return string.Empty;
|
|
|
|
// Build hostfwd arguments for custom port forwards
|
|
var forwardArgs = new List<string>();
|
|
foreach (var pf in vmForwards)
|
|
{
|
|
forwardArgs.Add($"hostfwd=tcp::{pf.HostPort}-:{pf.VmPort}");
|
|
}
|
|
|
|
return string.Join(",", forwardArgs);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Warning: Could not load custom port forwards: {ex.Message}");
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
private void AddMachineConfiguration()
|
|
{
|
|
_arguments.Add("-machine");
|
|
|
|
switch (_virtualizationType)
|
|
{
|
|
case VirtualizationType.KVM:
|
|
_arguments.Add("type=q35,accel=kvm:tcg");
|
|
break;
|
|
case VirtualizationType.HyperV:
|
|
// Use WHPX hardware acceleration
|
|
_arguments.Add("type=pc-i440fx-10.1,accel=whpx:tcg,kernel-irqchip=off");
|
|
break;
|
|
case VirtualizationType.HAXM:
|
|
_arguments.Add("type=q35,accel=hax:tcg");
|
|
break;
|
|
case VirtualizationType.HVF:
|
|
_arguments.Add("type=q35,accel=hvf:tcg");
|
|
break;
|
|
case VirtualizationType.TCG:
|
|
default:
|
|
_arguments.Add("type=q35,accel=tcg");
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void AddCpuConfiguration()
|
|
{
|
|
var cpu = _config.Cpu;
|
|
|
|
_arguments.Add("-cpu");
|
|
_arguments.Add(cpu.Model);
|
|
|
|
// For WHPX, use simpler CPU configuration to avoid exit code 4
|
|
if (_virtualizationType == VirtualizationType.HyperV)
|
|
{
|
|
_arguments.Add("-smp");
|
|
_arguments.Add($"{Math.Min(cpu.Cores, 2)},cores={Math.Min(cpu.Cores, 2)},sockets=1,threads=1");
|
|
}
|
|
else
|
|
{
|
|
_arguments.Add("-smp");
|
|
_arguments.Add($"{cpu.Cores},cores={cpu.Cores},sockets={cpu.Sockets},threads={cpu.Threads}");
|
|
}
|
|
}
|
|
|
|
private void AddMemoryConfiguration()
|
|
{
|
|
var memory = _config.Memory;
|
|
|
|
// For WHPX, use smaller memory allocation to avoid exit code 4
|
|
if (_virtualizationType == VirtualizationType.HyperV)
|
|
{
|
|
var whpxMemorySize = Math.Min(memory.Size, 4096); // Limit to 4GB for WHPX
|
|
_arguments.Add("-m");
|
|
_arguments.Add($"{whpxMemorySize}{memory.Unit}");
|
|
}
|
|
else
|
|
{
|
|
_arguments.Add("-m");
|
|
_arguments.Add($"{memory.Size}{memory.Unit}");
|
|
}
|
|
}
|
|
|
|
private void AddStorageConfiguration()
|
|
{
|
|
var storage = _config.Storage;
|
|
|
|
// Add disks
|
|
for (int i = 0; i < storage.Disks.Count; i++)
|
|
{
|
|
var disk = storage.Disks[i];
|
|
var driveLetter = (char)('a' + i);
|
|
|
|
_arguments.Add("-drive");
|
|
var driveArgs = $"file={disk.Path.Replace('\\', '/')},format={disk.Format},cache={disk.Cache},id=drive{i},if=none";
|
|
_arguments.Add(driveArgs);
|
|
|
|
// For WHPX, only use the first disk on IDE to avoid conflicts
|
|
if (_virtualizationType == VirtualizationType.HyperV && i > 0)
|
|
{
|
|
Console.WriteLine($"Warning: Additional disk {i} disabled for WHPX compatibility to avoid IDE conflicts. Disk: {disk.Path}");
|
|
continue;
|
|
}
|
|
|
|
// Add device
|
|
_arguments.Add("-device");
|
|
if (disk.Interface == "virtio" && _virtualizationType != VirtualizationType.HyperV)
|
|
{
|
|
// Use virtio for better performance, but avoid with WHPX due to MSI issues
|
|
_arguments.Add($"virtio-blk-pci,drive=drive{i}");
|
|
}
|
|
else
|
|
{
|
|
// Use IDE for WHPX compatibility
|
|
_arguments.Add($"ide-hd,drive=drive{i}");
|
|
}
|
|
}
|
|
|
|
// Add CD-ROM if specified
|
|
if (!string.IsNullOrEmpty(storage.Cdrom))
|
|
{
|
|
if (_virtualizationType == VirtualizationType.HyperV)
|
|
{
|
|
// For WHPX, use the second IDE controller for CD-ROM
|
|
_arguments.Add("-drive");
|
|
_arguments.Add($"file={storage.Cdrom.Replace('\\', '/')},media=cdrom,if=ide,index=1");
|
|
}
|
|
else
|
|
{
|
|
// Use standard CD-ROM for other virtualization types
|
|
_arguments.Add("-cdrom");
|
|
_arguments.Add(storage.Cdrom.Replace('\\', '/'));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AddNetworkConfiguration()
|
|
{
|
|
var network = _config.Network;
|
|
|
|
Console.WriteLine($"Debug: Network configuration - Interfaces count: {network.Interfaces.Count}");
|
|
Console.WriteLine($"Debug: Network configuration object: {network}");
|
|
Console.WriteLine($"Debug: Network interfaces: {string.Join(", ", network.Interfaces.Select(i => $"{i.Type}:{i.Model}:{i.Bridge}"))}");
|
|
|
|
for (int i = 0; i < network.Interfaces.Count; i++)
|
|
{
|
|
var nic = network.Interfaces[i];
|
|
|
|
_arguments.Add("-netdev");
|
|
string netdevArgs;
|
|
|
|
// Configure network backend based on type
|
|
if (nic.Type == "bridge" && !string.IsNullOrEmpty(nic.Bridge))
|
|
{
|
|
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
|
|
|
|
// Build base netdev arguments
|
|
var baseArgs = $"user,id=net{i},net=192.168.100.0/24,host=192.168.100.1";
|
|
|
|
// Add built-in port forwards
|
|
var builtInForwards = $"hostfwd=tcp::{hostPort}-:22,hostfwd=tcp::{hostPort + 1}-:80,hostfwd=tcp::{hostPort + 2}-:443";
|
|
|
|
// Add custom port forwards from configuration
|
|
var customForwards = GetCustomPortForwards();
|
|
if (!string.IsNullOrEmpty(customForwards))
|
|
{
|
|
netdevArgs = $"{baseArgs},{builtInForwards},{customForwards}";
|
|
}
|
|
else
|
|
{
|
|
netdevArgs = $"{baseArgs},{builtInForwards}";
|
|
}
|
|
|
|
_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
|
|
{
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AddDisplayConfiguration()
|
|
{
|
|
var display = _config.Display;
|
|
|
|
_arguments.Add("-display");
|
|
|
|
// Use appropriate display backend based on OS
|
|
string displayType = display.Type;
|
|
if (OperatingSystem.IsMacOS())
|
|
{
|
|
// On macOS, GTK is not available, use cocoa instead
|
|
if (displayType == "gtk")
|
|
{
|
|
displayType = "cocoa";
|
|
}
|
|
}
|
|
|
|
// Handle VNC display type with proper argument
|
|
if (displayType == "vnc")
|
|
{
|
|
var vncPort = display.SpicePort > 0 ? display.SpicePort : 5900;
|
|
// QEMU VNC display numbering: vnc=0.0.0.0:0 means port 5900, vnc=0.0.0.0:1 means port 5901, etc.
|
|
// So for port 5900, we use display 0, for port 5901, we use display 1, etc.
|
|
var displayNumber = vncPort - 5900;
|
|
if (displayNumber < 0) displayNumber = 0; // Default to display 0 if port < 5900
|
|
displayType = $"vnc=0.0.0.0:{displayNumber}";
|
|
}
|
|
|
|
_arguments.Add(displayType);
|
|
|
|
// For X11 forwarding compatibility, use std VGA instead of virtio
|
|
// This avoids GLX/OpenGL issues when forwarding over X11
|
|
if (displayType.StartsWith("gtk") || displayType.StartsWith("x11"))
|
|
{
|
|
_arguments.Add("-vga");
|
|
_arguments.Add("std"); // Use standard VGA for better X11 compatibility
|
|
|
|
// Add software rendering options to avoid GLX issues
|
|
_arguments.Add("-device");
|
|
_arguments.Add("virtio-gpu-pci,edid=off"); // Use virtio-gpu without EDID
|
|
}
|
|
else if (displayType.StartsWith("vnc"))
|
|
{
|
|
// For VNC, use std VGA for better compatibility
|
|
_arguments.Add("-vga");
|
|
_arguments.Add("std");
|
|
}
|
|
else
|
|
{
|
|
_arguments.Add("-vga");
|
|
_arguments.Add(display.Vga);
|
|
}
|
|
|
|
if (display.EnableSpice)
|
|
{
|
|
_arguments.Add("-spice");
|
|
_arguments.Add($"port={display.SpicePort},addr=127.0.0.1,disable-ticketing=on");
|
|
}
|
|
}
|
|
|
|
private void AddBootConfiguration()
|
|
{
|
|
var boot = _config.Boot;
|
|
|
|
if (boot.Order.Count > 0)
|
|
{
|
|
_arguments.Add("-boot");
|
|
_arguments.Add($"order={string.Join("", boot.Order)}");
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(boot.Kernel))
|
|
{
|
|
_arguments.Add("-kernel");
|
|
_arguments.Add(boot.Kernel);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(boot.Initrd))
|
|
{
|
|
_arguments.Add("-initrd");
|
|
_arguments.Add(boot.Initrd);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(boot.Cmdline))
|
|
{
|
|
_arguments.Add("-append");
|
|
_arguments.Add(boot.Cmdline);
|
|
}
|
|
}
|
|
|
|
private void AddAdvancedConfiguration()
|
|
{
|
|
var advanced = _config.Advanced;
|
|
|
|
if (advanced.EnableAudio)
|
|
{
|
|
_arguments.Add("-device");
|
|
_arguments.Add("intel-hda");
|
|
_arguments.Add("-device");
|
|
_arguments.Add("hda-duplex");
|
|
}
|
|
|
|
if (advanced.EnableUsb)
|
|
{
|
|
_arguments.Add("-usb");
|
|
_arguments.Add("-device");
|
|
_arguments.Add("usb-tablet");
|
|
}
|
|
|
|
// Disable virtio devices for WHPX to avoid MSI issues
|
|
if (advanced.EnableBalloon && _virtualizationType != VirtualizationType.HyperV)
|
|
{
|
|
_arguments.Add("-device");
|
|
_arguments.Add("virtio-balloon-pci");
|
|
}
|
|
|
|
if (advanced.EnableVirtioRng && _virtualizationType != VirtualizationType.HyperV)
|
|
{
|
|
_arguments.Add("-device");
|
|
_arguments.Add("virtio-rng-pci");
|
|
}
|
|
|
|
if (advanced.EnableVirtioFs && _virtualizationType != VirtualizationType.HyperV)
|
|
{
|
|
_arguments.Add("-device");
|
|
_arguments.Add("virtio-fs-pci");
|
|
}
|
|
|
|
// Add shared folders (disabled for compatibility)
|
|
// Note: 9p filesystem support is not available in all QEMU builds
|
|
// For now, shared folders are disabled to ensure compatibility
|
|
if (advanced.SharedFolders.Any())
|
|
{
|
|
// Log that shared folders are disabled
|
|
Console.WriteLine($"Warning: Shared folders are disabled for compatibility. {advanced.SharedFolders.Count} folder(s) configured but not used.");
|
|
}
|
|
}
|
|
|
|
private void AddWHPXSpecificConfiguration()
|
|
{
|
|
// Add WHPX-specific configurations to avoid MSI issues
|
|
|
|
// Disable MSI for better WHPX compatibility
|
|
_arguments.Add("-global");
|
|
_arguments.Add("pcie-root-port.msi=off");
|
|
|
|
// Use legacy interrupt mode for better compatibility
|
|
_arguments.Add("-global");
|
|
_arguments.Add("pcie-root-port.msix=off");
|
|
|
|
// Add additional WHPX optimizations
|
|
_arguments.Add("-global");
|
|
_arguments.Add("pcie-root-port.ari=off");
|
|
|
|
// Add WHPX-specific optimizations
|
|
_arguments.Add("-rtc");
|
|
_arguments.Add("base=localtime");
|
|
|
|
// Memory allocation optimizations for WHPX
|
|
// Note: -mem-path is not needed for WHPX and can cause issues
|
|
|
|
// Memory preallocation is not available in this QEMU build
|
|
|
|
// Add additional WHPX optimizations to avoid exit code 4
|
|
_arguments.Add("-no-reboot");
|
|
_arguments.Add("-no-shutdown");
|
|
|
|
// Use simpler interrupt handling (removed KVM-specific option)
|
|
|
|
// Disable some features that might cause issues with WHPX
|
|
// _arguments.Add("-no-acpi"); // Commented out as it might cause issues
|
|
}
|
|
}
|