如何基于 Kestrel 实现 socks5 代理

前言

之前做了个轮子NZOrz, 本来打算慢慢参照KestrelYarp长久地写着玩

奈何川普上台,关税,订婚案,自身和钱包等等各种乐子层出不穷,无暇慢悠悠地写轮子玩

还有有些盆友也想知道能否直接使用 Kestrel 来实现L4的处理,

所以为了2025年轻松一些,重新基于 Kestrel 实现了 L4/L7的代理 VKProxy (有兴趣的同学点个赞呗),并简单实现 socks5 为大家展示一下

(PS:叠甲 本人认知和能力有限,永远搞不懂/也不知道什么Txxxrojan/Sxxxhadowsocks等等这些东西,所以请不要咨询本人,本人不会不懂)

如何释放 Kestrel 的能力

众所周知 Kestrel 是 Aspnetcore 为了跨平台而实现的web server,只提供 http 1/2/3 的 L7层的能力

但看过源码的同学都知道,其实其本身从L4层(socket)实现的Http协议处理,只是OnBind只有http相关实现以及没有提供相关公开扩展的api,所以限制了其能力

但是既然代码是开源的,并且我们也知道dotnet有虽然麻烦但是能跨越访问限制的能力(Reflection),所以它是不能阻挡我们的魔爪

(ps
1. 不过这样绕过限制可能会在Native AOT相关场景存在问题,目前暂时没有做具体相关测试
2. 在不同版本Kestrel 可能会存在api变动,目前为了省事,不适配各版本差异,暂时以net9.0为准,net10正式发布后迁移升级到net10,此后不再适配net9.0之前版本

示例

首先我们先来看完成效果监听并处理 tcp/udp/http1/http2/http3,以便大家能理解我们的目的

VKProxy.Core 单纯封装释放Kestrel的能力以及简单的udp处理能力,所以大家单纯想使用 Kestrel处理相关内容就可以只使用VKProxy.Core

安装
dotnet add package VKProxy.Core --version 0.0.0.1
程序入口
using CoreDemo;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using VKProxy.Core.Hosting;

var app = Host.CreateDefaultBuilder(args).UseVKProxyCore()
    .ConfigureServices(i =>
    {
        // 已通过 IListenHandler 解耦监听和处理, 大家可以实现其而做任意自己想做的事情
        i.AddSingleton();
        i.AddSingleton();
        i.AddSingleton();
    })
    .Build();

await app.RunAsync();
如何处理 tcp
internal class TcpListenHandler : ListenHandlerBase
{
    private readonly List endPointOptions = new List();
    private readonly ILogger logger;
    private readonly IConnectionFactory connectionFactory;

    public TcpListenHandler(ILogger logger, IConnectionFactory connectionFactory)
    {
        this.logger = logger;
        this.connectionFactory = connectionFactory;
    }

    /// 程序初次启动时,可以在此实现相关的初始化操作
    public override Task InitAsync(CancellationToken cancellationToken)
    {
        endPointOptions.Add(new EndPointOptions()
        {
            EndPoint = IPEndPoint.Parse("127.0.0.1:5000"),
            Key = "tcpXXX"
        });
        return Task.CompletedTask;
    }

    /// 可在此方法通过 transportManager.BindAsync 实现监听哪些端口以及如何处理,如果需要运行时监听端口变动等,可通过 GetReloadToken 和 RebindAsync 实现,这里为了简单不再举例
    public override async Task BindAsync(ITransportManager transportManager, CancellationToken cancellationToken)
    {
        foreach (var item in endPointOptions)
        {
            try
            {
                await transportManager.BindAsync(item, Proxy, cancellationToken);
                logger.LogInformation($"listen {item.EndPoint}");
            }
            catch (Exception ex)
            {
                logger.LogError(ex.Message, ex);
            }
        }
    }

    /// 处理的委托方法,这里的例子为简单的 tcp 代理
    private async Task Proxy(ConnectionContext connection)
    {
        logger.LogInformation($"begin tcp {DateTime.Now} {connection.LocalEndPoint.ToString()} ");
        var upstream = await connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Parse("14.215.177.38"), 80));
        var task1 = connection.Transport.Input.CopyToAsync(upstream.Transport.Output);
        var task2 = upstream.Transport.Input.CopyToAsync(connection.Transport.Output);
        await Task.WhenAny(task1, task2);
        upstream.Abort();
        connection.Abort();
        logger.LogInformation($"end tcp {DateTime.Now} {connection.LocalEndPoint.ToString()} ");
    }
}
如何处理 udp

默认已提供简单的udp 处理,所以无需大家自己实现监听循环, 当然由于实现过于简单,复杂场景可能需要大家自己实现 IConnectionListenerFactory 或者 IMultiplexedConnectionListenerFactory

internal class UdpListenHandler : ListenHandlerBase
{
    private readonly ILogger logger;
    private readonly IUdpConnectionFactory udp;
    private readonly IPEndPoint proxyServer = new(IPAddress.Parse("127.0.0.1"), 11000);

    public UdpListenHandler(ILogger logger, IUdpConnectionFactory udp)
    {
        this.logger = logger;
        this.udp = udp;
    }

    public override async Task BindAsync(ITransportManager transportManager, CancellationToken cancellationToken)
    {
        var ip = new EndPointOptions()
        {
            EndPoint = UdpEndPoint.Parse("127.0.0.1:5000"), // 为了区别 Kestrel 默认的tcp实现,所以必须通过 UdpEndPoint 屏蔽默认的tcp监听
            Key = "udpXXX"
        };
        await transportManager.BindAsync(ip, Proxy, cancellationToken);
        logger.LogInformation($"listen {ip.EndPoint}");
    }

    /// 处理的委托方法,这里的例子为简单的 UDP 代理
    private async Task Proxy(ConnectionContext connection)
    {
        if (connection is UdpConnectionContext context)
        {
            Console.WriteLine($"{context.LocalEndPoint} received {context.ReceivedBytesCount} from {context.RemoteEndPoint}");
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            await udp.SendToAsync(socket, proxyServer, context.ReceivedBytes, CancellationToken.None);
            var r = await udp.ReceiveAsync(socket, CancellationToken.None);
            await udp.SendToAsync(context.Socket, context.RemoteEndPoint, r.GetReceivedBytes(), CancellationToken.None);
        }
    }
}
如何处理 http
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Logging;
using System.Net;
using VKProxy.Core.Adapters;
using VKProxy.Core.Config;
using VKProxy.Core.Hosting;

namespace CoreDemo;

public class HttpListenHandler : ListenHandlerBase
{
   private readonly ILogger logger;
   private readonly ICertificateLoader certificateLoader;

   public HttpListenHandler(ILogger logger, ICertificateLoader certificateLoader)
   {
       this.logger = logger;
       this.certificateLoader = certificateLoader;
   }

   private async Task Proxy(HttpContext context)
   {
       var resp = context.Response;
       resp.StatusCode = 404;
       await resp.WriteAsJsonAsync(new { context.Request.Protocol });
       await resp.CompleteAsync().ConfigureAwait(false);
   }

   public override async Task BindAsync(ITransportManager transportManager, CancellationToken cancellationToken)
   {
       try
       {
           // http  (http2和http3都需要证书,所以这里监听会忽略,只监听http1)
           var ip = new EndPointOptions()
           {
               EndPoint = IPEndPoint.Parse("127.0.0.1:4000"),
               Key = "http"
           };
           await transportManager.BindHttpAsync(ip, Proxy, cancellationToken);
           logger.LogInformation($"listen {ip.EndPoint}");

           // https
           ip = new EndPointOptions()
           {
               EndPoint = IPEndPoint.Parse("127.0.0.1:4001"),
               Key = "https"
           };

           var (c, f) = certificateLoader.LoadCertificate(new CertificateConfig() { Path = "testCert.pfx", Password = "testPassword" });  //读取证书
           await transportManager.BindHttpAsync(ip, Proxy, cancellationToken, HttpProtocols.Http1AndHttp2AndHttp3, callbackOptions: new HttpsConnectionAdapterOptions()
           {
               //ServerCertificateSelector = (context, host) => c   http3 由于底层 quic 实现,无法支持动态ServerCertificate
               ServerCertificate = c,
               CheckCertificateRevocation = false,
               ClientCertificateMode = ClientCertificateMode.AllowCertificate
           });
           logger.LogInformation($"listen {ip.EndPoint}");
       }
       catch (Exception ex)
       {
           logger.LogError(ex.Message, ex);
       }
   }
}

适配Kestrel 的核心点

核心重点在暴露TransportManager api, 这样大家就有了L4层的处理能力

TransportManagerAdapter 实现

public class TransportManagerAdapter : ITransportManager, IHeartbeat
{
    private static MethodInfo StopAsyncMethod;
    private static MethodInfo StopEndpointsAsyncMethod;
    private static MethodInfo MultiplexedBindAsyncMethod;
    private static MethodInfo BindAsyncMethod;
    private static MethodInfo StartHeartbeatMethod;
    private object transportManager;
    private object heartbeat;
    private object serviceContext;
    private object metrics;
    private int multiplexedTransportCount;
    private int transportCount;
    internal readonly IServiceProvider serviceProvider;

    IServiceProvider ITransportManager.ServiceProvider => serviceProvider;

    public TransportManagerAdapter(IServiceProvider serviceProvider, IEnumerable transportFactories, IEnumerable multiplexedConnectionListenerFactories)
    {
        (transportManager, heartbeat, serviceContext, metrics) = CreateTransportManager(serviceProvider);
        multiplexedTransportCount = multiplexedConnectionListenerFactories.Count();
        transportCount = transportFactories.Count();
        this.serviceProvider = serviceProvider;
    }

    private static (object, object, object, object) CreateTransportManager(IServiceProvider serviceProvider)
    {
        foreach (var item in KestrelExtensions.TransportManagerType.GetTypeInfo().DeclaredMethods)
        {
            if (item.Name == "StopAsync")
            {
                StopAsyncMethod = item;
            }
            else if (item.Name == "StopEndpointsAsync")
            {
                StopEndpointsAsyncMethod = item;
            }
            else if (item.Name == "BindAsync")
            {
                if (item.GetParameters().Any(i => i.ParameterType == typeof(ConnectionDelegate)))
                {
                    BindAsyncMethod = item;
                }
                else
                {
                    MultiplexedBindAsyncMethod = item;
                }
            }
        }

        var s = CreateServiceContext(serviceProvider);
        var r = Activator.CreateInstance(KestrelExtensions.TransportManagerType,
                    Enumerable.Reverse(serviceProvider.GetServices()).ToList(),
                    Enumerable.Reverse(serviceProvider.GetServices()).ToList(),
                    CreateHttpsConfigurationService(serviceProvider),
                    s.context
                    );
        return (r, s.heartbeat, s.context, s.metrics);

        static object CreateHttpsConfigurationService(IServiceProvider serviceProvider)
        {
            var CreateLogger = typeof(LoggerFactoryExtensions).GetTypeInfo().DeclaredMethods.First(i => i.Name == "CreateLogger" && i.ContainsGenericParameters);
            var r = Activator.CreateInstance(KestrelExtensions.HttpsConfigurationServiceType);
            var m = KestrelExtensions.HttpsConfigurationServiceType.GetMethod("Initialize");
            var log = serviceProvider.GetRequiredService();
            var l = CreateLogger.MakeGenericMethod(KestrelExtensions.HttpsConnectionMiddlewareType).Invoke(null, new object[] { log });
            m.Invoke(r, new object[] { serviceProvider.GetRequiredService(), log.CreateLogger(), l });
            return r;
        }

        static (object context, object heartbeat, object metrics) CreateServiceContext(IServiceProvider serviceProvider)
        {
            var m = CreateKestrelMetrics();
            var KestrelCreateServiceContext = KestrelExtensions.KestrelServerImplType.GetMethod("CreateServiceContext", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
            var r = KestrelCreateServiceContext.Invoke(null, new object[]
            {
                serviceProvider.GetRequiredService>(),
                serviceProvider.GetRequiredService(),
                null,
                m
            });
            var h = KestrelExtensions.ServiceContextType.GetTypeInfo().DeclaredProperties.First(i => i.Name == "Heartbeat");
            StartHeartbeatMethod = KestrelExtensions.HeartbeatType.GetTypeInfo().DeclaredMethods.First(i => i.Name == "Start");
            return (r, h.GetGetMethod().Invoke(r, null), m);
        }

        static object CreateKestrelMetrics()
        {
            return Activator.CreateInstance(KestrelExtensions.KestrelMetricsType, Activator.CreateInstance(KestrelExtensions.DummyMeterFactoryType));
        }
    }

    public Task BindAsync(EndPointOptions endpointConfig, ConnectionDelegate connectionDelegate, CancellationToken cancellationToken)
    {
        return BindAsyncMethod.Invoke(transportManager, new object[] { endpointConfig.EndPoint, connectionDelegate, endpointConfig.Init(), cancellationToken }) as Task;
    }

    public Task BindAsync(EndPointOptions endpointConfig, MultiplexedConnectionDelegate multiplexedConnectionDelegate, CancellationToken cancellationToken)
    {
        return MultiplexedBindAsyncMethod.Invoke(transportManager, new object[] { endpointConfig.EndPoint, multiplexedConnectionDelegate, endpointConfig.GetListenOptions(), cancellationToken }) as Task;
    }

    public Task StopEndpointsAsync(List endpointsToStop, CancellationToken cancellationToken)
    {
        return StopEndpointsAsyncMethod.Invoke(transportManager, new object[] { EndPointOptions.Init(endpointsToStop), cancellationToken }) as Task;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return StopAsyncMethod.Invoke(transportManager, new object[] { cancellationToken }) as Task;
    }

    public void StartHeartbeat()
    {
        if (heartbeat != null)
        {
            StartHeartbeatMethod.Invoke(heartbeat, null);
        }
    }

    public void StopHeartbeat()
    {
        if (heartbeat is IDisposable disposable)
        {
            disposable.Dispose();
        }
    }

    public IConnectionBuilder UseHttpServer(IConnectionBuilder builder, IHttpApplication application, HttpProtocols protocols, bool addAltSvcHeader)
    {
        KestrelExtensions.UseHttpServerMethod.Invoke(null, new object[] { builder, serviceContext, application, protocols, addAltSvcHeader });
        return builder;
    }

    public IMultiplexedConnectionBuilder UseHttp3Server(IMultiplexedConnectionBuilder builder, IHttpApplication application, HttpProtocols protocols, bool addAltSvcHeader)
    {
        KestrelExtensions.UseHttp3ServerMethod.Invoke(null, new object[] { builder, serviceContext, application, protocols, addAltSvcHeader });
        return builder;
    }

    public ConnectionDelegate UseHttps(ConnectionDelegate next, HttpsConnectionAdapterOptions tlsCallbackOptions, HttpProtocols protocols)
    {
        if (tlsCallbackOptions == null)
            return next;
        var o = KestrelExtensions.HttpsConnectionMiddlewareInitMethod.Invoke(new object[] { next, tlsCallbackOptions, protocols, serviceProvider.GetRequiredService(), metrics });
        return KestrelExtensions.HttpsConnectionMiddlewareOnConnectionAsyncMethod.CreateDelegate(o);
    }

    public async Task BindHttpApplicationAsync(EndPointOptions options, IHttpApplication application, CancellationToken cancellationToken, HttpProtocols protocols = HttpProtocols.Http1AndHttp2AndHttp3, bool addAltSvcHeader = true, Action config = null
        , Action configMultiplexed = null, HttpsConnectionAdapterOptions callbackOptions = null)
    {
        var hasHttp1 = protocols.HasFlag(HttpProtocols.Http1);
        var hasHttp2 = protocols.HasFlag(HttpProtocols.Http2);
        var hasHttp3 = protocols.HasFlag(HttpProtocols.Http3);
        var hasTls = callbackOptions is not null;

        if (hasTls)
        {
            if (hasHttp3)
            {
                options.GetListenOptions().Protocols = protocols;
                options.SetHttpsOptions(callbackOptions);
            }
            //callbackOptions.SetHttpProtocols(protocols);
            //if (hasHttp3)
            //{
            //    HttpsConnectionAdapterOptions
            //    options.SetHttpsCallbackOptions(callbackOptions);
            //}
        }
        else
        {
            // Http/1 without TLS, no-op HTTP/2 and 3.
            if (hasHttp1)
            {
                hasHttp2 = false;
                hasHttp3 = false;
            }
            // Http/3 requires TLS. Note we only let it fall back to HTTP/1, not HTTP/2
            else if (hasHttp3)
            {
                throw new InvalidOperationException("HTTP/3 requires HTTPS.");
            }
        }

        // Quic isn't registered if it's not supported, throw if we can't fall back to 1 or 2
        if (hasHttp3 && multiplexedTransportCount == 0 && !(hasHttp1 || hasHttp2))
        {
            throw new InvalidOperationException("Unable to bind an HTTP/3 endpoint. This could be because QUIC has not been configured using UseQuic, or the platform doesn't support QUIC or HTTP/3.");
        }

        addAltSvcHeader = addAltSvcHeader && multiplexedTransportCount > 0;

        // Add the HTTP middleware as the terminal connection middleware
        if (hasHttp1 || hasHttp2
            || protocols == HttpProtocols.None)
        {
            if (transportCount == 0)
            {
                throw new InvalidOperationException($"Cannot start HTTP/1.x or HTTP/2 server if no {nameof(IConnectionListenerFactory)} is registered.");
            }

            var builder = new ConnectionBuilder(serviceProvider);
            config?.Invoke(builder);
            UseHttpServer(builder, application, protocols, addAltSvcHeader);
            var connectionDelegate = UseHttps(builder.Build(), callbackOptions, protocols);

            options.EndPoint = await BindAsync(options, connectionDelegate, cancellationToken).ConfigureAwait(false);
        }

        if (hasHttp3 && multiplexedTransportCount > 0)
        {
            var builder = new MultiplexedConnectionBuilder(serviceProvider);
            configMultiplexed?.Invoke(builder);
            UseHttp3Server(builder, application, protocols, addAltSvcHeader);
            var multiplexedConnectionDelegate = builder.Build();

            options.EndPoint = await BindAsync(options, multiplexedConnectionDelegate, cancellationToken).ConfigureAwait(false);
        }
    }
}

其次通过重写 VKServer 从而去除 OnBind 方法的影响,达到大家可以使用 ITransportManager 做任意 L4/L7的处理

public class VKServer : IServer
{
    private readonly ITransportManager transportManager;
    private readonly IHeartbeat heartbeat;
    private readonly IListenHandler listenHandler;
    private readonly GeneralLogger logger;
    private bool _hasStarted;
    private int _stopping;
    private readonly SemaphoreSlim _bindSemaphore = new SemaphoreSlim(initialCount: 1);
    private readonly CancellationTokenSource _stopCts = new CancellationTokenSource();
    private readonly TaskCompletionSource _stoppedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
    private IDisposable? _configChangedRegistration;

    public VKServer(ITransportManager transportManager, IHeartbeat heartbeat, IListenHandler listenHandler, GeneralLogger logger)
    {
        this.transportManager = transportManager;
        this.heartbeat = heartbeat;
        this.listenHandler = listenHandler;
        this.logger = logger;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        try
        {
            if (_hasStarted)
            {
                throw new InvalidOperationException("Server already started");
            }
            _hasStarted = true;
            await listenHandler.InitAsync(cancellationToken);
            heartbeat.StartHeartbeat();
            await BindAsync(cancellationToken).ConfigureAwait(false);
        }
        catch
        {
            Dispose();
            throw;
        }
    }

    private async Task BindAsync(CancellationToken cancellationToken)
    {
        await _bindSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

        try
        {
            if (_stopping == 1)
            {
                throw new InvalidOperationException("Server has already been stopped.");
            }

            IChangeToken? reloadToken = listenHandler.GetReloadToken();
            await listenHandler.BindAsync(transportManager, _stopCts.Token).ConfigureAwait(false);
            _configChangedRegistration = reloadToken?.RegisterChangeCallback(TriggerRebind, this);
        }
        finally
        {
            _bindSemaphore.Release();
        }
    }

    private void TriggerRebind(object? state)
    {
        if (state is VKServer server)
        {
            _ = server.RebindAsync();
        }
    }

    private async Task RebindAsync()
    {
        await _bindSemaphore.WaitAsync();

        IChangeToken? reloadToken = null;
        try
        {
            if (_stopping == 1)
            {
                return;
            }

            reloadToken = listenHandler.GetReloadToken();
            await listenHandler.RebindAsync(transportManager, _stopCts.Token).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            logger.UnexpectedException("Unable to reload configuration", ex);
        }
        finally
        {
            _configChangedRegistration = reloadToken?.RegisterChangeCallback(TriggerRebind, this);
            _bindSemaphore.Release();
        }
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        if (Interlocked.Exchange(ref _stopping, 1) == 1)
        {
            await _stoppedTcs.Task.ConfigureAwait(false);
            return;
        }

        heartbeat.StopHeartbeat();

        _stopCts.Cancel();

        await _bindSemaphore.WaitAsync().ConfigureAwait(false);

        try
        {
            await listenHandler.StopAsync(transportManager, cancellationToken).ConfigureAwait(false);
            await transportManager.StopAsync(cancellationToken).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            _stoppedTcs.TrySetException(ex);
            throw;
        }
        finally
        {
            _configChangedRegistration?.Dispose();
            _stopCts.Dispose();
            _bindSemaphore.Release();
        }

        _stoppedTcs.TrySetResult();
    }

    public void Dispose()
    {
        StopAsync(new CancellationToken(canceled: true)).GetAwaiter().GetResult();
    }
}

如何实现 socks5

socks5 代理协议已经有很多文章说明,这里不再赘述,想了解的可以参见https://zh.wikipedia.org/wiki/SOCKS

这里列举一下核心实现

internal class Socks5Middleware : ITcpProxyMiddleware
{
    private readonly IDictionary auths;
    private readonly IConnectionFactory tcp;
    private readonly IHostResolver hostResolver;
    private readonly ITransportManager transport;
    private readonly IUdpConnectionFactory udp;

    public Socks5Middleware(IEnumerable socks5Auths, IConnectionFactory tcp, IHostResolver hostResolver, ITransportManager transport, IUdpConnectionFactory udp)
    {
        this.auths = socks5Auths.ToFrozenDictionary(i => i.AuthType);
        this.tcp = tcp;
        this.hostResolver = hostResolver;
        this.transport = transport;
        this.udp = udp;
    }

    public Task InitAsync(ConnectionContext context, CancellationToken token, TcpDelegate next)
    {
       // 识别是否为 socks5 路由
        var feature = context.Features.Get();
        if (feature is not null)
        {
            var route = feature.Route;
            if (route is not null && route.Metadata is not null
                && route.Metadata.TryGetValue("socks5", out var b) && bool.TryParse(b, out var isSocks5) && isSocks5)
            {
                feature.IsDone = true;
                return Proxy(context, feature, token);
            }
        }
        return next(context, token);
    }

    public Task> OnRequestAsync(ConnectionContext context, ReadOnlyMemory source, CancellationToken token, TcpProxyDelegate next)
    {
        return next(context, source, token);
    }

    public Task> OnResponseAsync(ConnectionContext context, ReadOnlyMemory source, CancellationToken token, TcpProxyDelegate next)
    {
        return next(context, source, token);
    }

    private async Task Proxy(ConnectionContext context, IL4ReverseProxyFeature feature, CancellationToken token)
    {
        var input = context.Transport.Input;
        var output = context.Transport.Output;
        // 1. socks5 认证
        if (!await Socks5Parser.AuthAsync(input, auths, context, token))
        {
            context.Abort();
        }
        // 2. 获取 socks5 命令请求
        var cmd = await Socks5Parser.GetCmdRequestAsync(input, token);
        IPEndPoint ip = await ResolveIpAsync(context, cmd, token);
        switch (cmd.Cmd)
        {
            case Socks5Cmd.Connect:
            case Socks5Cmd.Bind:
                // 3. 如果为tcp代理,则会在此分支处理,以命令请求中的地址建立tcp链接
                ConnectionContext upstream;
                try
                {
                    upstream = await tcp.ConnectAsync(ip, token);
                }
                catch
                {  // 为了简单,这里异常没有详细分区各种情况
                    await Socks5Parser.ResponeAsync(output, Socks5CmdResponseType.ConnectFail, token);
                    throw;
                }
                // 4. 服务tcp建立成功,通知 client
                await Socks5Parser.ResponeAsync(output, Socks5CmdResponseType.Success, token);
                var task = await Task.WhenAny(
                               context.Transport.Input.CopyToAsync(upstream.Transport.Output, token)
                               , upstream.Transport.Input.CopyToAsync(context.Transport.Output, token));
                if (task.IsCanceled)
                {
                    context.Abort();
                }
                break;

            case Socks5Cmd.UdpAssociate:
                // 3. 如果为udp代理,则会在此分支处理,建立临时 udp 代理服务地址
                var local = context.LocalEndPoint as IPEndPoint;
                var op = new EndPointOptions()
                {
                    EndPoint = new UdpEndPoint(local.Address, 0),
                    Key = Guid.NewGuid().ToString(),
                };
                try
                {
                    var remote = context.RemoteEndPoint;
                    var timeout = feature.Route.Timeout;
                    op.EndPoint = await transport.BindAsync(op, c => ProxyUdp(c as UdpConnectionContext, remote, timeout), token);
                    // 5. tcp 关闭时 需要关闭临时 udp 服务
                    context.ConnectionClosed.Register(state => transport.StopEndpointsAsync(new List() { state as EndPointOptions }, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(), op);
                }
                catch
                {
                    await Socks5Parser.ResponeAsync(output, Socks5CmdResponseType.ConnectFail, token);
                    throw;
                }
                 // 4. 服务udp建立成功,通知 client 临时udp地址
                await Socks5Parser.ResponeAsync(output, op.EndPoint as IPEndPoint, Socks5CmdResponseType.Success, token);
                break;
        }
    }

    private async Task ProxyUdp(UdpConnectionContext context, EndPoint remote, TimeSpan timeout)
    {
        using var cts = CancellationTokenSourcePool.Default.Rent(timeout);
        var token = cts.Token;
        // 这里用为了简单 同一个临时地址即监听client 也处理 服务端 response,通过端口比较区分, 当然这样存在一定安全问题 
        if (context.RemoteEndPoint.GetHashCode() == remote.GetHashCode())
        {
            var req = Socks5Parser.GetUdpRequest(context.ReceivedBytes);
            IPEndPoint ip = await ResolveIpAsync(req, token);
            // 请求服务,解包原始请求
            await udp.SendToAsync(context.Socket, ip, req.Data, token);
        }
        else
        {
           
            // 服务response,封包
            await Socks5Parser.UdpResponeAsync(udp, context, remote as IPEndPoint, token);
        }
    }

    private async Task ResolveIpAsync(ConnectionContext context, Socks5Common cmd, CancellationToken token)
    {
        IPEndPoint ip = await ResolveIpAsync(cmd, token);
        if (ip is null)
        {
            await Socks5Parser.ResponeAsync(context.Transport.Output, Socks5CmdResponseType.AddressNotAllow, token);
            context.Abort();
        }

        return ip;
    }

    private async Task ResolveIpAsync(Socks5Common cmd, CancellationToken token)
    {
        IPEndPoint ip;
        if (cmd.Domain is not null)
        {
            var ips = await hostResolver.HostResolveAsync(cmd.Domain, token);
            if (ips.Length > 0)
            {
                ip = new IPEndPoint(ips.First(), cmd.Port);
            }
            else
                ip = null;
        }
        else if (cmd.Ip is not null)
        {
            ip = new IPEndPoint(cmd.Ip, cmd.Port);
        }
        else
        {
            ip = null;
        }

        return ip;
    }
}

如此大家可以看到大家无需疯狂 while(true) { await socket.Receive... }, 减轻了很多大家负担

From:https://www.cnblogs.com/fs7744/p/18842979
victor.x.qu
100+评论
captcha