using System.Net; using System.Net.Sockets; using Microsoft.Extensions.Logging; using QemuVmManager.Models; namespace QemuVmManager.Core; public interface IMacOSNetworkManager { Task IsPortAvailableAsync(int port); Task ForwardPortAsync(int externalPort, int internalPort, string description); Task RemovePortForwardAsync(int externalPort); Task> GetActivePortForwardsAsync(); Task GetLocalIpAddressAsync(); Task GetExternalIpAddressAsync(); } public class MacOSNetworkManager : IMacOSNetworkManager { private readonly ILogger? _logger; private readonly Dictionary _activeForwards = new(); private readonly object _lock = new(); public MacOSNetworkManager(ILogger? logger = null) { _logger = logger; } public async Task IsPortAvailableAsync(int port) { try { using var client = new TcpClient(); await client.ConnectAsync("127.0.0.1", port); client.Close(); return false; // Port is in use } catch (SocketException) { return true; // Port is available } catch (Exception ex) { _logger?.LogWarning(ex, "Error checking port availability for port {Port}", port); return false; // Assume port is not available on error } } public async Task ForwardPortAsync(int externalPort, int internalPort, string description) { try { // Check if port is available if (!await IsPortAvailableAsync(externalPort)) { _logger?.LogWarning("Port {Port} is already in use", externalPort); return false; } var localIp = await GetLocalIpAddressAsync(); if (localIp == null) { _logger?.LogError("Could not determine local IP address"); return false; } // For macOS, we'll use the built-in port forwarding that QEMU provides // This is more reliable than trying to configure the router lock (_lock) { _activeForwards[externalPort] = new PortForward { ExternalPort = externalPort, InternalPort = internalPort, InternalIp = localIp, Protocol = "TCP", Description = description, CreatedAt = DateTime.UtcNow, Status = "Active (QEMU Forwarded)" }; } _logger?.LogInformation("Port forward configured: {ExternalPort} -> {InternalIp}:{InternalPort} ({Description})", externalPort, localIp, internalPort, description); _logger?.LogInformation("Note: On macOS, port forwarding is handled by QEMU's built-in forwarding."); _logger?.LogInformation("External access: http://:{ExternalPort}", externalPort); _logger?.LogInformation("Local access: http://localhost:{ExternalPort}", externalPort); return true; } catch (Exception ex) { _logger?.LogError(ex, "Failed to configure port forward {ExternalPort} -> {InternalPort}", externalPort, internalPort); return false; } } public async Task RemovePortForwardAsync(int externalPort) { try { lock (_lock) { if (_activeForwards.Remove(externalPort)) { _logger?.LogInformation("Removed port forward: {ExternalPort}", externalPort); return true; } else { _logger?.LogWarning("Port forward {ExternalPort} not found", externalPort); return false; } } } catch (Exception ex) { _logger?.LogError(ex, "Failed to remove port forward {ExternalPort}", externalPort); return false; } } public async Task> GetActivePortForwardsAsync() { try { lock (_lock) { return _activeForwards.Values.ToList(); } } catch (Exception ex) { _logger?.LogError(ex, "Failed to get active port forwards"); return new List(); } } public async Task GetLocalIpAddressAsync() { try { // Get the local IP address (not localhost) var host = Dns.GetHostEntry(Dns.GetHostName()); foreach (var ip in host.AddressList) { if (ip.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(ip)) { return ip; } } // Fallback: try to get IP from network interfaces var networkInterfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces(); foreach (var networkInterface in networkInterfaces) { if (networkInterface.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up && networkInterface.NetworkInterfaceType != System.Net.NetworkInformation.NetworkInterfaceType.Loopback) { var properties = networkInterface.GetIPProperties(); foreach (var address in properties.UnicastAddresses) { if (address.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(address.Address)) { return address.Address; } } } } return null; } catch (Exception ex) { _logger?.LogWarning(ex, "Failed to get local IP address"); return null; } } public async Task GetExternalIpAddressAsync() { try { // Use external service to get public IP (more reliable than UPnP on macOS) using var client = new HttpClient(); client.Timeout = TimeSpan.FromSeconds(10); var response = await client.GetStringAsync("https://api.ipify.org"); if (IPAddress.TryParse(response.Trim(), out var externalIp)) { _logger?.LogInformation("External IP from service: {ExternalIP}", externalIp); return externalIp; } return null; } catch (Exception ex) { _logger?.LogWarning(ex, "Failed to get external IP from service"); return null; } } } public class PortForward { public int ExternalPort { get; set; } public int InternalPort { get; set; } public IPAddress? InternalIp { get; set; } public string Protocol { get; set; } = "TCP"; public string Description { get; set; } = ""; public DateTime CreatedAt { get; set; } public string Status { get; set; } = "Active"; }