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.Buffers.Binary;
using System.Net;
using System.Text;
using LumaTunnel.Shared.Models;

namespace LumaTunnel.Client.Core.Proxy;

internal static class Socks5Protocol
{
    public static async Task<ProxyTarget?> NegotiateAsync(Stream stream, CancellationToken cancellationToken)
    {
        var greeting = new byte[2];
        await ReadExactlyAsync(stream, greeting, cancellationToken).ConfigureAwait(false);
        if (greeting[0] != 5 || greeting[1] == 0)
            return null;
        var methods = new byte[greeting[1]];
        await ReadExactlyAsync(stream, methods, cancellationToken).ConfigureAwait(false);
        if (!methods.Contains((byte)0))
        {
            await stream.WriteAsync(new byte[] { 5, 0xFF }, cancellationToken).ConfigureAwait(false);
            return null;
        }
        await stream.WriteAsync(new byte[] { 5, 0 }, cancellationToken).ConfigureAwait(false);

        var header = new byte[4];
        await ReadExactlyAsync(stream, header, cancellationToken).ConfigureAwait(false);
        if (header[0] != 5 || header[1] != 1 || header[2] != 0)
        {
            await WriteReplyAsync(stream, header[1] == 1 ? (byte)0x01 : (byte)0x07, null, cancellationToken).ConfigureAwait(false);
            return null;
        }

        string host;
        IPAddress? address = null;
        switch (header[3])
        {
            case 1:
                var ipv4 = new byte[4];
                await ReadExactlyAsync(stream, ipv4, cancellationToken).ConfigureAwait(false);
                address = new IPAddress(ipv4);
                host = address.ToString();
                break;
            case 3:
                var length = stream.ReadByte();
                if (length <= 0)
                    return null;
                var domain = new byte[length];
                await ReadExactlyAsync(stream, domain, cancellationToken).ConfigureAwait(false);
                host = Encoding.ASCII.GetString(domain);
                break;
            case 4:
                var ipv6 = new byte[16];
                await ReadExactlyAsync(stream, ipv6, cancellationToken).ConfigureAwait(false);
                address = new IPAddress(ipv6);
                host = address.ToString();
                break;
            default:
                await WriteReplyAsync(stream, 0x08, null, cancellationToken).ConfigureAwait(false);
                return null;
        }

        var portBytes = new byte[2];
        await ReadExactlyAsync(stream, portBytes, cancellationToken).ConfigureAwait(false);
        var port = BinaryPrimitives.ReadUInt16BigEndian(portBytes);
        return port == 0 ? null : new ProxyTarget(host, port, address);
    }

    public static async Task WriteReplyAsync(Stream stream, byte reply, IPEndPoint? endpoint, CancellationToken cancellationToken)
    {
        endpoint ??= new IPEndPoint(IPAddress.Any, 0);
        var address = endpoint.Address;
        var addressBytes = address.GetAddressBytes();
        var response = new byte[4 + addressBytes.Length + 2];
        response[0] = 5;
        response[1] = reply;
        response[2] = 0;
        response[3] = address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? (byte)1 : (byte)4;
        addressBytes.CopyTo(response, 4);
        BinaryPrimitives.WriteUInt16BigEndian(response.AsSpan(4 + addressBytes.Length), checked((ushort)endpoint.Port));
        await stream.WriteAsync(response, cancellationToken).ConfigureAwait(false);
    }

    private static async Task ReadExactlyAsync(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken)
    {
        var offset = 0;
        while (offset < buffer.Length)
        {
            var count = await stream.ReadAsync(buffer[offset..], cancellationToken).ConfigureAwait(false);
            if (count == 0)
                throw new EndOfStreamException();
            offset += count;
        }
    }
}