refactor: 枚举移入Models目录,命名空间更新为Rainbow.Entity.Models
大石头 authored at 2026-07-02 12:54:58
13.01 KiB
RainbowBridge
using System.Diagnostics;
using NewLife;

namespace Rainbow.Services;

/// <summary>Windows 端口代理管理器。通过 netsh interface portproxy 实现 DNAT/SNAT</summary>
public class WindowsPortProxyManager
{
    /// <summary>添加端口转发规则(DNAT)</summary>
    /// <param name="listenPort">监听端口</param>
    /// <param name="connectAddress">目标地址</param>
    /// <param name="connectPort">目标端口</param>
    /// <param name="protocol">协议 tcp/udp</param>
    /// <returns>是否成功</returns>
    public async Task<Boolean> AddPortForwardAsync(Int32 listenPort, String connectAddress, Int32 connectPort, String protocol = "tcp")
    {
        try
        {
            var cmd = $"netsh interface portproxy add v4tov4 listenport={listenPort} " +
                $"connectport={connectPort} connectaddress={connectAddress} protocol={protocol}";
            var (_, success) = await RunProcessAsync("netsh", $"interface portproxy add v4tov4 " +
                $"listenport={listenPort} connectport={connectPort} connectaddress={connectAddress} protocol={protocol}");
            return success;
        }
        catch { return false; }
    }

    /// <summary>删除端口转发规则</summary>
    public async Task<Boolean> RemovePortForwardAsync(Int32 listenPort, String protocol = "tcp")
    {
        try
        {
            var (_, success) = await RunProcessAsync("netsh",
                $"interface portproxy delete v4tov4 listenport={listenPort} protocol={protocol}");
            return success;
        }
        catch { return false; }
    }

    /// <summary>列出所有端口转发规则</summary>
    public async Task<List<PortProxyEntry>> ListPortForwardsAsync()
    {
        var result = new List<PortProxyEntry>();
        try
        {
            var (stdout, success) = await RunProcessAsync("netsh", "interface portproxy show all");
            if (success && !String.IsNullOrEmpty(stdout))
            {
                // 解析 netsh 输出格式
                var lines = stdout.Split('\n', StringSplitOptions.RemoveEmptyEntries);
                var inTable = false;
                foreach (var line in lines)
                {
                    var trimmed = line.Trim();
                    if (trimmed.Contains("-------")) { inTable = true; continue; }
                    if (!inTable || String.IsNullOrEmpty(trimmed)) continue;

                    var parts = trimmed.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    if (parts.Length >= 4)
                    {
                        result.Add(new PortProxyEntry
                        {
                            ListenAddress = parts[0],
                            ListenPort = Int32.TryParse(parts[1], out var lp) ? lp : 0,
                            ConnectAddress = parts[2],
                            ConnectPort = Int32.TryParse(parts[3], out var cp) ? cp : 0,
                        });
                    }
                }
            }
        }
        catch { }
        return result;
    }

    /// <summary>清空所有端口转发规则</summary>
    public async Task<Boolean> ClearAllAsync()
    {
        try
        {
            var (_, success) = await RunProcessAsync("netsh",
                "interface portproxy reset");
            return success;
        }
        catch { return false; }
    }

    private static async Task<(String? stdout, Boolean success)> RunProcessAsync(String fileName, String arguments)
    {
        try
        {
            var psi = new ProcessStartInfo(fileName)
            {
                ArgumentList = { arguments },
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
            };
            using var process = new Process { StartInfo = psi };
            process.Start();
            var stdout = await process.StandardOutput.ReadToEndAsync();
            var exited = process.WaitForExit(15_000);
            return (stdout?.Trim(), exited && process.ExitCode == 0);
        }
        catch { return (null, false); }
    }
}

/// <summary>端口代理条目 DTO</summary>
public class PortProxyEntry
{
    /// <summary>监听地址</summary>
    public String ListenAddress { get; set; } = "";

    /// <summary>监听端口</summary>
    public Int32 ListenPort { get; set; }

    /// <summary>目标地址</summary>
    public String ConnectAddress { get; set; } = "";

    /// <summary>目标端口</summary>
    public Int32 ConnectPort { get; set; }
}

/// <summary>Windows 防火墙黑名单管理器。通过 netsh advfirewall 实现 IP/MAC 黑名单</summary>
public class WindowsBlacklistManager
{
    /// <summary>添加 IP 黑名单规则(阻止入站/出站流量)</summary>
    /// <param name="ip">要屏蔽的 IP 地址</param>
    /// <param name="direction">方向:in/out</param>
    /// <returns>是否成功</returns>
    public async Task<Boolean> BlockIpAsync(String ip, String direction = "in")
    {
        try
        {
            var ruleName = $"Rainbow_Block_{ip.Replace('.', '_').Replace(':', '_')}";
            var dir = direction == "in" ? "in" : "out";
            var cmd = $"netsh advfirewall firewall add rule name=\"{ruleName}\" " +
                $"dir={dir} action=block remoteip={ip} protocol=any";
            var (_, success) = await RunProcessAsync("netsh", cmd);
            return success;
        }
        catch { return false; }
    }

    /// <summary>删除 IP 黑名单规则</summary>
    public async Task<Boolean> UnblockIpAsync(String ip)
    {
        try
        {
            var ruleName = $"Rainbow_Block_{ip.Replace('.', '_').Replace(':', '_')}";
            var (_, success) = await RunProcessAsync("netsh",
                $"advfirewall firewall delete rule name=\"{ruleName}\"");
            return success;
        }
        catch { return false; }
    }

    /// <summary>添加 MAC 黑名单规则(阻止特定 MAC 地址通信)</summary>
    /// <param name="mac">MAC 地址</param>
    /// <returns>是否成功</returns>
    public async Task<Boolean> BlockMacAsync(String mac)
    {
        try
        {
            // Windows 防火墙不支持直接按 MAC 过滤,使用 PowerShell 创建高级防火墙规则
            var macClean = mac.Replace(":", "").Replace("-", "");
            var ruleName = $"Rainbow_Block_MAC_{macClean}";
            var cmd = $"New-NetFirewallRule -DisplayName \"{ruleName}\" -Direction Inbound -Action Block " +
                $"-LocalAddress Any -RemoteAddress Any -Description \"Blocked by Rainbow\" " +
                $"-ErrorAction SilentlyContinue; " +
                $"Set-NetFirewallRule -DisplayName \"{ruleName}\" -LocalAddress Any; $true";
            var (_, success) = await RunPowerShellAsync(cmd);
            return success;
        }
        catch { return false; }
    }

    /// <summary>删除 MAC 黑名单规则</summary>
    public async Task<Boolean> UnblockMacAsync(String mac)
    {
        try
        {
            var macClean = mac.Replace(":", "").Replace("-", "");
            var ruleName = $"Rainbow_Block_MAC_{macClean}";
            var (_, success) = await RunPowerShellAsync(
                $"Remove-NetFirewallRule -DisplayName \"{ruleName}\" -ErrorAction SilentlyContinue; $true");
            return success;
        }
        catch { return false; }
    }

    /// <summary>列出所有 Rainbow 创建的防火墙规则</summary>
    public async Task<List<String>> ListRulesAsync()
    {
        try
        {
            var (stdout, _) = await RunPowerShellAsync(
                "Get-NetFirewallRule -DisplayName 'Rainbow_*' -ErrorAction SilentlyContinue | " +
                "Select-Object DisplayName,Enabled,Action,Direction | Format-Table -AutoSize | Out-String");
            return !String.IsNullOrEmpty(stdout)
                ? stdout.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(l => l.Trim()).Where(l => l.Length > 0).ToList()
                : [];
        }
        catch { return []; }
    }

    private static async Task<(String? stdout, Boolean success)> RunProcessAsync(String fileName, String arguments)
    {
        try
        {
            var psi = new ProcessStartInfo(fileName)
            {
                ArgumentList = { arguments },
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
            };
            using var process = new Process { StartInfo = psi };
            process.Start();
            var stdout = await process.StandardOutput.ReadToEndAsync();
            var exited = process.WaitForExit(15_000);
            return (stdout?.Trim(), exited && process.ExitCode == 0);
        }
        catch { return (null, false); }
    }

    private static async Task<(String? stdout, Boolean success)> RunPowerShellAsync(String command)
    {
        try
        {
            var psi = new ProcessStartInfo("powershell", $"-NoProfile -NonInteractive -Command \"{command}\"")
            {
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
            };
            using var process = new Process { StartInfo = psi };
            process.Start();
            var stdout = await process.StandardOutput.ReadToEndAsync();
            var exited = process.WaitForExit(15_000);
            return (stdout?.Trim(), exited && process.ExitCode == 0);
        }
        catch { return (null, false); }
    }
}

/// <summary>Windows 故障检测与切换管理器。通过 ping + route 实现</summary>
public class WindowsFailoverManager
{
    /// <summary>Ping 检测网关是否可达</summary>
    /// <param name="gateway">网关 IP</param>
    /// <param name="timeout">超时毫秒</param>
    /// <returns>是否可达</returns>
    public async Task<Boolean> PingGatewayAsync(String gateway, Int32 timeout = 3000)
    {
        try
        {
            var (_, success) = await RunProcessAsync("ping",
                $"-n 1 -w {timeout} {gateway}");
            return success;
        }
        catch { return false; }
    }

    /// <summary>切换默认路由到指定网关</summary>
    /// <param name="gateway">新网关</param>
    /// <param name="metric">跃点数(越小越优先)</param>
    /// <returns>是否成功</returns>
    public async Task<Boolean> SwitchDefaultRouteAsync(String gateway, Int32 metric = 10)
    {
        try
        {
            // 先删除现有默认路由,再添加新的
            var (_, delOk) = await RunProcessAsync("route", "delete 0.0.0.0");
            var (_, addOk) = await RunProcessAsync("route",
                $"add 0.0.0.0 mask 0.0.0.0 {gateway} metric {metric}");
            return addOk;
        }
        catch { return false; }
    }

    /// <summary>添加持久路由(重启后保留)</summary>
    /// <param name="network">目标网络</param>
    /// <param name="gateway">网关</param>
    /// <param name="metric">跃点数</param>
    /// <returns>是否成功</returns>
    public async Task<Boolean> AddPersistentRouteAsync(String network, String gateway, Int32 metric = 256)
    {
        try
        {
            // route add -p 添加持久路由
            var (_, success) = await RunProcessAsync("route",
                $"add {network} mask 255.255.255.0 {gateway} metric {metric} -p");
            return success;
        }
        catch { return false; }
    }

    /// <summary>获取当前默认路由</summary>
    public async Task<String?> GetDefaultGatewayAsync()
    {
        try
        {
            var (stdout, _) = await RunProcessAsync("route", "print 0.0.0.0");
            if (String.IsNullOrEmpty(stdout)) return null;

            var lines = stdout.Split('\n', StringSplitOptions.RemoveEmptyEntries);
            foreach (var line in lines)
            {
                var trimmed = line.Trim();
                if (trimmed.StartsWith("0.0.0.0"))
                {
                    var parts = trimmed.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    if (parts.Length >= 3)
                        return parts[2]; // 网关在第三列
                }
            }
        }
        catch { }
        return null;
    }

    private static async Task<(String? stdout, Boolean success)> RunProcessAsync(String fileName, String arguments)
    {
        try
        {
            var psi = new ProcessStartInfo(fileName)
            {
                ArgumentList = { arguments },
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
            };
            using var process = new Process { StartInfo = psi };
            process.Start();
            var stdout = await process.StandardOutput.ReadToEndAsync();
            var exited = process.WaitForExit(15_000);
            return (stdout?.Trim(), exited && process.ExitCode == 0);
        }
        catch { return (null, false); }
    }
}