XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

LumaTunnel

【WinUI】LumaTunnel 是一个面向个人多设备的 Windows 代理系统。客户端在本机提供 HTTP/HTTPS CONNECT 与 SOCKS5 TCP 代理,并通过一个受信任 TLS 证书保护的 WSS 会话,将多个 TCP 流复用到自建 Windows Server 节点。

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Net;
using System.Net.Sockets;
using LumaTunnel.Server.Core.Configuration;
using LumaTunnel.Shared.Protocol;

namespace LumaTunnel.Server.Core.Security;

public sealed class TargetPolicy(ServerSettings settings)
{
    private readonly ServerSettings _settings = settings;
    private readonly HashSet<int> _blockedPorts = [.. settings.BlockedPorts];

    public async Task<IPAddress> ResolveAndValidateAsync(string host, int port, CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(host) || host.Length > 253 || port is < 1 or > 65535)
            throw new TunnelProtocolException(TunnelErrorCode.InvalidTarget, "Target host or port is invalid.");
        if (_blockedPorts.Contains(port))
            throw new TunnelProtocolException(TunnelErrorCode.TargetBlocked, $"Target port {port} is blocked by server policy.");

        IPAddress[] addresses;
        if (IPAddress.TryParse(host.Trim('[', ']'), out var literal))
        {
            addresses = [literal];
        }
        else
        {
            try
            {
                addresses = await Dns.GetHostAddressesAsync(host, cancellationToken).ConfigureAwait(false);
            }
            catch (SocketException exception)
            {
                throw new TunnelProtocolException(TunnelErrorCode.DnsFailed, $"Unable to resolve target host: {exception.SocketErrorCode}.");
            }
        }

        if (addresses.Length == 0)
            throw new TunnelProtocolException(TunnelErrorCode.DnsFailed, "Target host resolved to no addresses.");

        var allowed = addresses.Where(IsAllowed).ToArray();
        if (allowed.Length != addresses.Length)
            throw new TunnelProtocolException(TunnelErrorCode.TargetBlocked, "Target host resolves to a private or reserved address.");

        return allowed.OrderBy(static ip => ip.AddressFamily == AddressFamily.InterNetwork ? 0 : 1).First();
    }

    public bool IsAllowed(IPAddress address)
    {
        if (_settings.AllowPrivateTargets)
            return !IPAddress.IsLoopback(address);

        if (IPAddress.IsLoopback(address) || address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any))
            return false;

        if (address.IsIPv4MappedToIPv6)
            address = address.MapToIPv4();

        var bytes = address.GetAddressBytes();
        if (address.AddressFamily == AddressFamily.InterNetwork)
        {
            return !(bytes[0] is 0 or 10 or 127
                     || bytes[0] == 169 && bytes[1] == 254
                     || bytes[0] == 172 && bytes[1] is >= 16 and <= 31
                     || bytes[0] == 192 && bytes[1] == 168
                     || bytes[0] >= 224);
        }

        if (address.AddressFamily == AddressFamily.InterNetworkV6)
        {
            var isUniqueLocal = (bytes[0] & 0xFE) == 0xFC;
            var isLinkLocal = bytes[0] == 0xFE && (bytes[1] & 0xC0) == 0x80;
            var isMulticast = bytes[0] == 0xFF;
            return !isUniqueLocal && !isLinkLocal && !isMulticast && !address.Equals(IPAddress.IPv6Loopback);
        }

        return false;
    }
}