Compare commits

..

2 Commits

Author SHA1 Message Date
19abfb919e Port forwarding and automation 2025-09-01 11:04:00 -04:00
c8a48e61e7 Port forward debugging 2025-08-31 23:19:53 -04:00
3 changed files with 374 additions and 0 deletions

View File

@@ -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<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

View File

@@ -380,17 +380,27 @@ public class VmManagementService
try
{
var json = File.ReadAllText(configFile);
Console.WriteLine($"Loading config from {configFile}");
Console.WriteLine($"JSON content: {json}");
var config = JsonSerializer.Deserialize<VmConfiguration>(json);
if (config != null && !string.IsNullOrWhiteSpace(config.Name))
{
Console.WriteLine($"Successfully loaded VM: {config.Name}");
Console.WriteLine($"Network interfaces count: {config.Network?.Interfaces?.Count ?? 0}");
_vmConfigurations[config.Name] = config;
}
else
{
Console.WriteLine($"Failed to deserialize config from {configFile}: config is null or has no name");
}
}
catch (Exception ex)
{
// Log error but continue loading other configurations
Console.WriteLine($"Failed to load configuration from {configFile}: {ex.Message}");
Console.WriteLine($"Exception details: {ex}");
}
}
}

9
port-forwards.json Normal file
View File

@@ -0,0 +1,9 @@
[
{
"VmName": "ubuntu-desktop",
"HostPort": 8080,
"VmPort": 80,
"Protocol": "TCP",
"Created": "2025-08-31T23:55:53.202737Z"
}
]