找回密码
 立即注册
首页 业界区 业界 如何基于 Kestrel 实现 socks5 代理

如何基于 Kestrel 实现 socks5 代理

颐港 5 天前
前言

之前做了个轮子NZOrz, 本来打算慢慢参照Kestrel和Yarp长久地写着玩
奈何川普上台,关税,订婚案,自身和钱包等等各种乐子层出不穷,无暇慢悠悠地写轮子玩
还有有些盆友也想知道能否直接使用 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
安装
  1. dotnet add package VKProxy.Core --version 0.0.0.1
复制代码
程序入口
  1. using CoreDemo;
  2. using Microsoft.Extensions.DependencyInjection;
  3. using Microsoft.Extensions.Hosting;
  4. using VKProxy.Core.Hosting;
  5. var app = Host.CreateDefaultBuilder(args).UseVKProxyCore()
  6.     .ConfigureServices(i =>
  7.     {
  8.         // 已通过 IListenHandler 解耦监听和处理, 大家可以实现其而做任意自己想做的事情
  9.         i.AddSingleton<IListenHandler, TcpListenHandler>();
  10.         i.AddSingleton<IListenHandler, UdpListenHandler>();
  11.         i.AddSingleton<IListenHandler, HttpListenHandler>();
  12.     })
  13.     .Build();
  14. await app.RunAsync();
复制代码
如何处理 tcp
  1. internal class TcpListenHandler : ListenHandlerBase
  2. {
  3.     private readonly List<EndPointOptions> endPointOptions = new List<EndPointOptions>();
  4.     private readonly ILogger<TcpListenHandler> logger;
  5.     private readonly IConnectionFactory connectionFactory;
  6.     public TcpListenHandler(ILogger<TcpListenHandler> logger, IConnectionFactory connectionFactory)
  7.     {
  8.         this.logger = logger;
  9.         this.connectionFactory = connectionFactory;
  10.     }
  11.     /// 程序初次启动时,可以在此实现相关的初始化操作
  12.     public override Task InitAsync(CancellationToken cancellationToken)
  13.     {
  14.         endPointOptions.Add(new EndPointOptions()
  15.         {
  16.             EndPoint = IPEndPoint.Parse("127.0.0.1:5000"),
  17.             Key = "tcpXXX"
  18.         });
  19.         return Task.CompletedTask;
  20.     }
  21.     /// 可在此方法通过 transportManager.BindAsync 实现监听哪些端口以及如何处理,如果需要运行时监听端口变动等,可通过 GetReloadToken 和 RebindAsync 实现,这里为了简单不再举例
  22.     public override async Task BindAsync(ITransportManager transportManager, CancellationToken cancellationToken)
  23.     {
  24.         foreach (var item in endPointOptions)
  25.         {
  26.             try
  27.             {
  28.                 await transportManager.BindAsync(item, Proxy, cancellationToken);
  29.                 logger.LogInformation($"listen {item.EndPoint}");
  30.             }
  31.             catch (Exception ex)
  32.             {
  33.                 logger.LogError(ex.Message, ex);
  34.             }
  35.         }
  36.     }
  37.     /// 处理的委托方法,这里的例子为简单的 tcp 代理
  38.     private async Task Proxy(ConnectionContext connection)
  39.     {
  40.         logger.LogInformation($"begin tcp {DateTime.Now} {connection.LocalEndPoint.ToString()} ");
  41.         var upstream = await connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Parse("14.215.177.38"), 80));
  42.         var task1 = connection.Transport.Input.CopyToAsync(upstream.Transport.Output);
  43.         var task2 = upstream.Transport.Input.CopyToAsync(connection.Transport.Output);
  44.         await Task.WhenAny(task1, task2);
  45.         upstream.Abort();
  46.         connection.Abort();
  47.         logger.LogInformation($"end tcp {DateTime.Now} {connection.LocalEndPoint.ToString()} ");
  48.     }
  49. }
复制代码
如何处理 udp

默认已提供简单的udp 处理,所以无需大家自己实现监听循环, 当然由于实现过于简单,复杂场景可能需要大家自己实现 IConnectionListenerFactory 或者 IMultiplexedConnectionListenerFactory
  1. internal class UdpListenHandler : ListenHandlerBase
  2. {
  3.     private readonly ILogger<UdpListenHandler> logger;
  4.     private readonly IUdpConnectionFactory udp;
  5.     private readonly IPEndPoint proxyServer = new(IPAddress.Parse("127.0.0.1"), 11000);
  6.     public UdpListenHandler(ILogger<UdpListenHandler> logger, IUdpConnectionFactory udp)
  7.     {
  8.         this.logger = logger;
  9.         this.udp = udp;
  10.     }
  11.     public override async Task BindAsync(ITransportManager transportManager, CancellationToken cancellationToken)
  12.     {
  13.         var ip = new EndPointOptions()
  14.         {
  15.             EndPoint = UdpEndPoint.Parse("127.0.0.1:5000"), // 为了区别 Kestrel 默认的tcp实现,所以必须通过 UdpEndPoint 屏蔽默认的tcp监听
  16.             Key = "udpXXX"
  17.         };
  18.         await transportManager.BindAsync(ip, Proxy, cancellationToken);
  19.         logger.LogInformation($"listen {ip.EndPoint}");
  20.     }
  21.     /// 处理的委托方法,这里的例子为简单的 UDP 代理
  22.     private async Task Proxy(ConnectionContext connection)
  23.     {
  24.         if (connection is UdpConnectionContext context)
  25.         {
  26.             Console.WriteLine($"{context.LocalEndPoint} received {context.ReceivedBytesCount} from {context.RemoteEndPoint}");
  27.             var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  28.             await udp.SendToAsync(socket, proxyServer, context.ReceivedBytes, CancellationToken.None);
  29.             var r = await udp.ReceiveAsync(socket, CancellationToken.None);
  30.             await udp.SendToAsync(context.Socket, context.RemoteEndPoint, r.GetReceivedBytes(), CancellationToken.None);
  31.         }
  32.     }
  33. }
复制代码
如何处理 http
  1. using Microsoft.AspNetCore.Http;
  2. using Microsoft.AspNetCore.Server.Kestrel.Core;
  3. using Microsoft.AspNetCore.Server.Kestrel.Https;
  4. using Microsoft.Extensions.Logging;
  5. using System.Net;
  6. using VKProxy.Core.Adapters;
  7. using VKProxy.Core.Config;
  8. using VKProxy.Core.Hosting;
  9. namespace CoreDemo;
  10. public class HttpListenHandler : ListenHandlerBase
  11. {
  12.    private readonly ILogger<HttpListenHandler> logger;
  13.    private readonly ICertificateLoader certificateLoader;
  14.    public HttpListenHandler(ILogger<HttpListenHandler> logger, ICertificateLoader certificateLoader)
  15.    {
  16.        this.logger = logger;
  17.        this.certificateLoader = certificateLoader;
  18.    }
  19.    private async Task Proxy(HttpContext context)
  20.    {
  21.        var resp = context.Response;
  22.        resp.StatusCode = 404;
  23.        await resp.WriteAsJsonAsync(new { context.Request.Protocol });
  24.        await resp.CompleteAsync().ConfigureAwait(false);
  25.    }
  26.    public override async Task BindAsync(ITransportManager transportManager, CancellationToken cancellationToken)
  27.    {
  28.        try
  29.        {
  30.            // http  (http2和http3都需要证书,所以这里监听会忽略,只监听http1)
  31.            var ip = new EndPointOptions()
  32.            {
  33.                EndPoint = IPEndPoint.Parse("127.0.0.1:4000"),
  34.                Key = "http"
  35.            };
  36.            await transportManager.BindHttpAsync(ip, Proxy, cancellationToken);
  37.            logger.LogInformation($"listen {ip.EndPoint}");
  38.            // https
  39.            ip = new EndPointOptions()
  40.            {
  41.                EndPoint = IPEndPoint.Parse("127.0.0.1:4001"),
  42.                Key = "https"
  43.            };
  44.            var (c, f) = certificateLoader.LoadCertificate(new CertificateConfig() { Path = "testCert.pfx", Password = "testPassword" });  //读取证书
  45.            await transportManager.BindHttpAsync(ip, Proxy, cancellationToken, HttpProtocols.Http1AndHttp2AndHttp3, callbackOptions: new HttpsConnectionAdapterOptions()
  46.            {
  47.                //ServerCertificateSelector = (context, host) => c   http3 由于底层 quic 实现,无法支持动态ServerCertificate
  48.                ServerCertificate = c,
  49.                CheckCertificateRevocation = false,
  50.                ClientCertificateMode = ClientCertificateMode.AllowCertificate
  51.            });
  52.            logger.LogInformation($"listen {ip.EndPoint}");
  53.        }
  54.        catch (Exception ex)
  55.        {
  56.            logger.LogError(ex.Message, ex);
  57.        }
  58.    }
  59. }
复制代码
适配Kestrel 的核心点

核心重点在暴露TransportManager api, 这样大家就有了L4层的处理能力
TransportManagerAdapter 实现
  1. public class TransportManagerAdapter : ITransportManager, IHeartbeat
  2. {
  3.     private static MethodInfo StopAsyncMethod;
  4.     private static MethodInfo StopEndpointsAsyncMethod;
  5.     private static MethodInfo MultiplexedBindAsyncMethod;
  6.     private static MethodInfo BindAsyncMethod;
  7.     private static MethodInfo StartHeartbeatMethod;
  8.     private object transportManager;
  9.     private object heartbeat;
  10.     private object serviceContext;
  11.     private object metrics;
  12.     private int multiplexedTransportCount;
  13.     private int transportCount;
  14.     internal readonly IServiceProvider serviceProvider;
  15.     IServiceProvider ITransportManager.ServiceProvider => serviceProvider;
  16.     public TransportManagerAdapter(IServiceProvider serviceProvider, IEnumerable<IConnectionListenerFactory> transportFactories, IEnumerable<IMultiplexedConnectionListenerFactory> multiplexedConnectionListenerFactories)
  17.     {
  18.         (transportManager, heartbeat, serviceContext, metrics) = CreateTransportManager(serviceProvider);
  19.         multiplexedTransportCount = multiplexedConnectionListenerFactories.Count();
  20.         transportCount = transportFactories.Count();
  21.         this.serviceProvider = serviceProvider;
  22.     }
  23.     private static (object, object, object, object) CreateTransportManager(IServiceProvider serviceProvider)
  24.     {
  25.         foreach (var item in KestrelExtensions.TransportManagerType.GetTypeInfo().DeclaredMethods)
  26.         {
  27.             if (item.Name == "StopAsync")
  28.             {
  29.                 StopAsyncMethod = item;
  30.             }
  31.             else if (item.Name == "StopEndpointsAsync")
  32.             {
  33.                 StopEndpointsAsyncMethod = item;
  34.             }
  35.             else if (item.Name == "BindAsync")
  36.             {
  37.                 if (item.GetParameters().Any(i => i.ParameterType == typeof(ConnectionDelegate)))
  38.                 {
  39.                     BindAsyncMethod = item;
  40.                 }
  41.                 else
  42.                 {
  43.                     MultiplexedBindAsyncMethod = item;
  44.                 }
  45.             }
  46.         }
  47.         var s = CreateServiceContext(serviceProvider);
  48.         var r = Activator.CreateInstance(KestrelExtensions.TransportManagerType,
  49.                     Enumerable.Reverse(serviceProvider.GetServices<IConnectionListenerFactory>()).ToList(),
  50.                     Enumerable.Reverse(serviceProvider.GetServices<IMultiplexedConnectionListenerFactory>()).ToList(),
  51.                     CreateHttpsConfigurationService(serviceProvider),
  52.                     s.context
  53.                     );
  54.         return (r, s.heartbeat, s.context, s.metrics);
  55.         static object CreateHttpsConfigurationService(IServiceProvider serviceProvider)
  56.         {
  57.             var CreateLogger = typeof(LoggerFactoryExtensions).GetTypeInfo().DeclaredMethods.First(i => i.Name == "CreateLogger" && i.ContainsGenericParameters);
  58.             var r = Activator.CreateInstance(KestrelExtensions.HttpsConfigurationServiceType);
  59.             var m = KestrelExtensions.HttpsConfigurationServiceType.GetMethod("Initialize");
  60.             var log = serviceProvider.GetRequiredService<ILoggerFactory>();
  61.             var l = CreateLogger.MakeGenericMethod(KestrelExtensions.HttpsConnectionMiddlewareType).Invoke(null, new object[] { log });
  62.             m.Invoke(r, new object[] { serviceProvider.GetRequiredService<IHostEnvironment>(), log.CreateLogger<KestrelServer>(), l });
  63.             return r;
  64.         }
  65.         static (object context, object heartbeat, object metrics) CreateServiceContext(IServiceProvider serviceProvider)
  66.         {
  67.             var m = CreateKestrelMetrics();
  68.             var KestrelCreateServiceContext = KestrelExtensions.KestrelServerImplType.GetMethod("CreateServiceContext", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
  69.             var r = KestrelCreateServiceContext.Invoke(null, new object[]
  70.             {
  71.                 serviceProvider.GetRequiredService<IOptions<KestrelServerOptions>>(),
  72.                 serviceProvider.GetRequiredService<ILoggerFactory>(),
  73.                 null,
  74.                 m
  75.             });
  76.             var h = KestrelExtensions.ServiceContextType.GetTypeInfo().DeclaredProperties.First(i => i.Name == "Heartbeat");
  77.             StartHeartbeatMethod = KestrelExtensions.HeartbeatType.GetTypeInfo().DeclaredMethods.First(i => i.Name == "Start");
  78.             return (r, h.GetGetMethod().Invoke(r, null), m);
  79.         }
  80.         static object CreateKestrelMetrics()
  81.         {
  82.             return Activator.CreateInstance(KestrelExtensions.KestrelMetricsType, Activator.CreateInstance(KestrelExtensions.DummyMeterFactoryType));
  83.         }
  84.     }
  85.     public Task<EndPoint> BindAsync(EndPointOptions endpointConfig, ConnectionDelegate connectionDelegate, CancellationToken cancellationToken)
  86.     {
  87.         return BindAsyncMethod.Invoke(transportManager, new object[] { endpointConfig.EndPoint, connectionDelegate, endpointConfig.Init(), cancellationToken }) as Task<EndPoint>;
  88.     }
  89.     public Task<EndPoint> BindAsync(EndPointOptions endpointConfig, MultiplexedConnectionDelegate multiplexedConnectionDelegate, CancellationToken cancellationToken)
  90.     {
  91.         return MultiplexedBindAsyncMethod.Invoke(transportManager, new object[] { endpointConfig.EndPoint, multiplexedConnectionDelegate, endpointConfig.GetListenOptions(), cancellationToken }) as Task<EndPoint>;
  92.     }
  93.     public Task StopEndpointsAsync(List<EndPointOptions> endpointsToStop, CancellationToken cancellationToken)
  94.     {
  95.         return StopEndpointsAsyncMethod.Invoke(transportManager, new object[] { EndPointOptions.Init(endpointsToStop), cancellationToken }) as Task;
  96.     }
  97.     public Task StopAsync(CancellationToken cancellationToken)
  98.     {
  99.         return StopAsyncMethod.Invoke(transportManager, new object[] { cancellationToken }) as Task;
  100.     }
  101.     public void StartHeartbeat()
  102.     {
  103.         if (heartbeat != null)
  104.         {
  105.             StartHeartbeatMethod.Invoke(heartbeat, null);
  106.         }
  107.     }
  108.     public void StopHeartbeat()
  109.     {
  110.         if (heartbeat is IDisposable disposable)
  111.         {
  112.             disposable.Dispose();
  113.         }
  114.     }
  115.     public IConnectionBuilder UseHttpServer(IConnectionBuilder builder, IHttpApplication<HttpApplication.Context> application, HttpProtocols protocols, bool addAltSvcHeader)
  116.     {
  117.         KestrelExtensions.UseHttpServerMethod.Invoke(null, new object[] { builder, serviceContext, application, protocols, addAltSvcHeader });
  118.         return builder;
  119.     }
  120.     public IMultiplexedConnectionBuilder UseHttp3Server(IMultiplexedConnectionBuilder builder, IHttpApplication<HttpApplication.Context> application, HttpProtocols protocols, bool addAltSvcHeader)
  121.     {
  122.         KestrelExtensions.UseHttp3ServerMethod.Invoke(null, new object[] { builder, serviceContext, application, protocols, addAltSvcHeader });
  123.         return builder;
  124.     }
  125.     public ConnectionDelegate UseHttps(ConnectionDelegate next, HttpsConnectionAdapterOptions tlsCallbackOptions, HttpProtocols protocols)
  126.     {
  127.         if (tlsCallbackOptions == null)
  128.             return next;
  129.         var o = KestrelExtensions.HttpsConnectionMiddlewareInitMethod.Invoke(new object[] { next, tlsCallbackOptions, protocols, serviceProvider.GetRequiredService<ILoggerFactory>(), metrics });
  130.         return KestrelExtensions.HttpsConnectionMiddlewareOnConnectionAsyncMethod.CreateDelegate<ConnectionDelegate>(o);
  131.     }
  132.     public async Task BindHttpApplicationAsync(EndPointOptions options, IHttpApplication<HttpApplication.Context> application, CancellationToken cancellationToken, HttpProtocols protocols = HttpProtocols.Http1AndHttp2AndHttp3, bool addAltSvcHeader = true, Action<IConnectionBuilder> config = null
  133.         , Action<IMultiplexedConnectionBuilder> configMultiplexed = null, HttpsConnectionAdapterOptions callbackOptions = null)
  134.     {
  135.         var hasHttp1 = protocols.HasFlag(HttpProtocols.Http1);
  136.         var hasHttp2 = protocols.HasFlag(HttpProtocols.Http2);
  137.         var hasHttp3 = protocols.HasFlag(HttpProtocols.Http3);
  138.         var hasTls = callbackOptions is not null;
  139.         if (hasTls)
  140.         {
  141.             if (hasHttp3)
  142.             {
  143.                 options.GetListenOptions().Protocols = protocols;
  144.                 options.SetHttpsOptions(callbackOptions);
  145.             }
  146.             //callbackOptions.SetHttpProtocols(protocols);
  147.             //if (hasHttp3)
  148.             //{
  149.             //    HttpsConnectionAdapterOptions
  150.             //    options.SetHttpsCallbackOptions(callbackOptions);
  151.             //}
  152.         }
  153.         else
  154.         {
  155.             // Http/1 without TLS, no-op HTTP/2 and 3.
  156.             if (hasHttp1)
  157.             {
  158.                 hasHttp2 = false;
  159.                 hasHttp3 = false;
  160.             }
  161.             // Http/3 requires TLS. Note we only let it fall back to HTTP/1, not HTTP/2
  162.             else if (hasHttp3)
  163.             {
  164.                 throw new InvalidOperationException("HTTP/3 requires HTTPS.");
  165.             }
  166.         }
  167.         // Quic isn't registered if it's not supported, throw if we can't fall back to 1 or 2
  168.         if (hasHttp3 && multiplexedTransportCount == 0 && !(hasHttp1 || hasHttp2))
  169.         {
  170.             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.");
  171.         }
  172.         addAltSvcHeader = addAltSvcHeader && multiplexedTransportCount > 0;
  173.         // Add the HTTP middleware as the terminal connection middleware
  174.         if (hasHttp1 || hasHttp2
  175.             || protocols == HttpProtocols.None)
  176.         {
  177.             if (transportCount == 0)
  178.             {
  179.                 throw new InvalidOperationException($"Cannot start HTTP/1.x or HTTP/2 server if no {nameof(IConnectionListenerFactory)} is registered.");
  180.             }
  181.             var builder = new ConnectionBuilder(serviceProvider);
  182.             config?.Invoke(builder);
  183.             UseHttpServer(builder, application, protocols, addAltSvcHeader);
  184.             var connectionDelegate = UseHttps(builder.Build(), callbackOptions, protocols);
  185.             options.EndPoint = await BindAsync(options, connectionDelegate, cancellationToken).ConfigureAwait(false);
  186.         }
  187.         if (hasHttp3 && multiplexedTransportCount > 0)
  188.         {
  189.             var builder = new MultiplexedConnectionBuilder(serviceProvider);
  190.             configMultiplexed?.Invoke(builder);
  191.             UseHttp3Server(builder, application, protocols, addAltSvcHeader);
  192.             var multiplexedConnectionDelegate = builder.Build();
  193.             options.EndPoint = await BindAsync(options, multiplexedConnectionDelegate, cancellationToken).ConfigureAwait(false);
  194.         }
  195.     }
  196. }
复制代码
其次通过重写 VKServer 从而去除 OnBind 方法的影响,达到大家可以使用 ITransportManager 做任意 L4/L7的处理
  1. public class VKServer : IServer
  2. {
  3.     private readonly ITransportManager transportManager;
  4.     private readonly IHeartbeat heartbeat;
  5.     private readonly IListenHandler listenHandler;
  6.     private readonly GeneralLogger logger;
  7.     private bool _hasStarted;
  8.     private int _stopping;
  9.     private readonly SemaphoreSlim _bindSemaphore = new SemaphoreSlim(initialCount: 1);
  10.     private readonly CancellationTokenSource _stopCts = new CancellationTokenSource();
  11.     private readonly TaskCompletionSource _stoppedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
  12.     private IDisposable? _configChangedRegistration;
  13.     public VKServer(ITransportManager transportManager, IHeartbeat heartbeat, IListenHandler listenHandler, GeneralLogger logger)
  14.     {
  15.         this.transportManager = transportManager;
  16.         this.heartbeat = heartbeat;
  17.         this.listenHandler = listenHandler;
  18.         this.logger = logger;
  19.     }
  20.     public async Task StartAsync(CancellationToken cancellationToken)
  21.     {
  22.         try
  23.         {
  24.             if (_hasStarted)
  25.             {
  26.                 throw new InvalidOperationException("Server already started");
  27.             }
  28.             _hasStarted = true;
  29.             await listenHandler.InitAsync(cancellationToken);
  30.             heartbeat.StartHeartbeat();
  31.             await BindAsync(cancellationToken).ConfigureAwait(false);
  32.         }
  33.         catch
  34.         {
  35.             Dispose();
  36.             throw;
  37.         }
  38.     }
  39.     private async Task BindAsync(CancellationToken cancellationToken)
  40.     {
  41.         await _bindSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  42.         try
  43.         {
  44.             if (_stopping == 1)
  45.             {
  46.                 throw new InvalidOperationException("Server has already been stopped.");
  47.             }
  48.             IChangeToken? reloadToken = listenHandler.GetReloadToken();
  49.             await listenHandler.BindAsync(transportManager, _stopCts.Token).ConfigureAwait(false);
  50.             _configChangedRegistration = reloadToken?.RegisterChangeCallback(TriggerRebind, this);
  51.         }
  52.         finally
  53.         {
  54.             _bindSemaphore.Release();
  55.         }
  56.     }
  57.     private void TriggerRebind(object? state)
  58.     {
  59.         if (state is VKServer server)
  60.         {
  61.             _ = server.RebindAsync();
  62.         }
  63.     }
  64.     private async Task RebindAsync()
  65.     {
  66.         await _bindSemaphore.WaitAsync();
  67.         IChangeToken? reloadToken = null;
  68.         try
  69.         {
  70.             if (_stopping == 1)
  71.             {
  72.                 return;
  73.             }
  74.             reloadToken = listenHandler.GetReloadToken();
  75.             await listenHandler.RebindAsync(transportManager, _stopCts.Token).ConfigureAwait(false);
  76.         }
  77.         catch (Exception ex)
  78.         {
  79.             logger.UnexpectedException("Unable to reload configuration", ex);
  80.         }
  81.         finally
  82.         {
  83.             _configChangedRegistration = reloadToken?.RegisterChangeCallback(TriggerRebind, this);
  84.             _bindSemaphore.Release();
  85.         }
  86.     }
  87.     public async Task StopAsync(CancellationToken cancellationToken)
  88.     {
  89.         if (Interlocked.Exchange(ref _stopping, 1) == 1)
  90.         {
  91.             await _stoppedTcs.Task.ConfigureAwait(false);
  92.             return;
  93.         }
  94.         heartbeat.StopHeartbeat();
  95.         _stopCts.Cancel();
  96.         await _bindSemaphore.WaitAsync().ConfigureAwait(false);
  97.         try
  98.         {
  99.             await listenHandler.StopAsync(transportManager, cancellationToken).ConfigureAwait(false);
  100.             await transportManager.StopAsync(cancellationToken).ConfigureAwait(false);
  101.         }
  102.         catch (Exception ex)
  103.         {
  104.             _stoppedTcs.TrySetException(ex);
  105.             throw;
  106.         }
  107.         finally
  108.         {
  109.             _configChangedRegistration?.Dispose();
  110.             _stopCts.Dispose();
  111.             _bindSemaphore.Release();
  112.         }
  113.         _stoppedTcs.TrySetResult();
  114.     }
  115.     public void Dispose()
  116.     {
  117.         StopAsync(new CancellationToken(canceled: true)).GetAwaiter().GetResult();
  118.     }
  119. }
复制代码
如何实现 socks5

socks5 代理协议已经有很多文章说明,这里不再赘述,想了解的可以参见https://zh.wikipedia.org/wiki/SOCKS
这里列举一下核心实现
  1. internal class Socks5Middleware : ITcpProxyMiddleware
  2. {
  3.     private readonly IDictionary<byte, ISocks5Auth> auths;
  4.     private readonly IConnectionFactory tcp;
  5.     private readonly IHostResolver hostResolver;
  6.     private readonly ITransportManager transport;
  7.     private readonly IUdpConnectionFactory udp;
  8.     public Socks5Middleware(IEnumerable<ISocks5Auth> socks5Auths, IConnectionFactory tcp, IHostResolver hostResolver, ITransportManager transport, IUdpConnectionFactory udp)
  9.     {
  10.         this.auths = socks5Auths.ToFrozenDictionary(i => i.AuthType);
  11.         this.tcp = tcp;
  12.         this.hostResolver = hostResolver;
  13.         this.transport = transport;
  14.         this.udp = udp;
  15.     }
  16.     public Task InitAsync(ConnectionContext context, CancellationToken token, TcpDelegate next)
  17.     {
  18.        // 识别是否为 socks5 路由
  19.         var feature = context.Features.Get<IL4ReverseProxyFeature>();
  20.         if (feature is not null)
  21.         {
  22.             var route = feature.Route;
  23.             if (route is not null && route.Metadata is not null
  24.                 && route.Metadata.TryGetValue("socks5", out var b) && bool.TryParse(b, out var isSocks5) && isSocks5)
  25.             {
  26.                 feature.IsDone = true;
  27.                 return Proxy(context, feature, token);
  28.             }
  29.         }
  30.         return next(context, token);
  31.     }
  32.     public Task<ReadOnlyMemory<byte>> OnRequestAsync(ConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token, TcpProxyDelegate next)
  33.     {
  34.         return next(context, source, token);
  35.     }
  36.     public Task<ReadOnlyMemory<byte>> OnResponseAsync(ConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token, TcpProxyDelegate next)
  37.     {
  38.         return next(context, source, token);
  39.     }
  40.     private async Task Proxy(ConnectionContext context, IL4ReverseProxyFeature feature, CancellationToken token)
  41.     {
  42.         var input = context.Transport.Input;
  43.         var output = context.Transport.Output;
  44.         // 1. socks5 认证
  45.         if (!await Socks5Parser.AuthAsync(input, auths, context, token))
  46.         {
  47.             context.Abort();
  48.         }
  49.         // 2. 获取 socks5 命令请求
  50.         var cmd = await Socks5Parser.GetCmdRequestAsync(input, token);
  51.         IPEndPoint ip = await ResolveIpAsync(context, cmd, token);
  52.         switch (cmd.Cmd)
  53.         {
  54.             case Socks5Cmd.Connect:
  55.             case Socks5Cmd.Bind:
  56.                 // 3. 如果为tcp代理,则会在此分支处理,以命令请求中的地址建立tcp链接
  57.                 ConnectionContext upstream;
  58.                 try
  59.                 {
  60.                     upstream = await tcp.ConnectAsync(ip, token);
  61.                 }
  62.                 catch
  63.                 {  // 为了简单,这里异常没有详细分区各种情况
  64.                     await Socks5Parser.ResponeAsync(output, Socks5CmdResponseType.ConnectFail, token);
  65.                     throw;
  66.                 }
  67.                 // 4. 服务tcp建立成功,通知 client
  68.                 await Socks5Parser.ResponeAsync(output, Socks5CmdResponseType.Success, token);
  69.                 var task = await Task.WhenAny(
  70.                                context.Transport.Input.CopyToAsync(upstream.Transport.Output, token)
  71.                                , upstream.Transport.Input.CopyToAsync(context.Transport.Output, token));
  72.                 if (task.IsCanceled)
  73.                 {
  74.                     context.Abort();
  75.                 }
  76.                 break;
  77.             case Socks5Cmd.UdpAssociate:
  78.                 // 3. 如果为udp代理,则会在此分支处理,建立临时 udp 代理服务地址
  79.                 var local = context.LocalEndPoint as IPEndPoint;
  80.                 var op = new EndPointOptions()
  81.                 {
  82.                     EndPoint = new UdpEndPoint(local.Address, 0),
  83.                     Key = Guid.NewGuid().ToString(),
  84.                 };
  85.                 try
  86.                 {
  87.                     var remote = context.RemoteEndPoint;
  88.                     var timeout = feature.Route.Timeout;
  89.                     op.EndPoint = await transport.BindAsync(op, c => ProxyUdp(c as UdpConnectionContext, remote, timeout), token);
  90.                     // 5. tcp 关闭时 需要关闭临时 udp 服务
  91.                     context.ConnectionClosed.Register(state => transport.StopEndpointsAsync(new List<EndPointOptions>() { state as EndPointOptions }, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(), op);
  92.                 }
  93.                 catch
  94.                 {
  95.                     await Socks5Parser.ResponeAsync(output, Socks5CmdResponseType.ConnectFail, token);
  96.                     throw;
  97.                 }
  98.                  // 4. 服务udp建立成功,通知 client 临时udp地址
  99.                 await Socks5Parser.ResponeAsync(output, op.EndPoint as IPEndPoint, Socks5CmdResponseType.Success, token);
  100.                 break;
  101.         }
  102.     }
  103.     private async Task ProxyUdp(UdpConnectionContext context, EndPoint remote, TimeSpan timeout)
  104.     {
  105.         using var cts = CancellationTokenSourcePool.Default.Rent(timeout);
  106.         var token = cts.Token;
  107.         // 这里用为了简单 同一个临时地址即监听client 也处理 服务端 response,通过端口比较区分, 当然这样存在一定安全问题
  108.         if (context.RemoteEndPoint.GetHashCode() == remote.GetHashCode())
  109.         {
  110.             var req = Socks5Parser.GetUdpRequest(context.ReceivedBytes);
  111.             IPEndPoint ip = await ResolveIpAsync(req, token);
  112.             // 请求服务,解包原始请求
  113.             await udp.SendToAsync(context.Socket, ip, req.Data, token);
  114.         }
  115.         else
  116.         {
  117.            
  118.             // 服务response,封包
  119.             await Socks5Parser.UdpResponeAsync(udp, context, remote as IPEndPoint, token);
  120.         }
  121.     }
  122.     private async Task<IPEndPoint> ResolveIpAsync(ConnectionContext context, Socks5Common cmd, CancellationToken token)
  123.     {
  124.         IPEndPoint ip = await ResolveIpAsync(cmd, token);
  125.         if (ip is null)
  126.         {
  127.             await Socks5Parser.ResponeAsync(context.Transport.Output, Socks5CmdResponseType.AddressNotAllow, token);
  128.             context.Abort();
  129.         }
  130.         return ip;
  131.     }
  132.     private async Task<IPEndPoint> ResolveIpAsync(Socks5Common cmd, CancellationToken token)
  133.     {
  134.         IPEndPoint ip;
  135.         if (cmd.Domain is not null)
  136.         {
  137.             var ips = await hostResolver.HostResolveAsync(cmd.Domain, token);
  138.             if (ips.Length > 0)
  139.             {
  140.                 ip = new IPEndPoint(ips.First(), cmd.Port);
  141.             }
  142.             else
  143.                 ip = null;
  144.         }
  145.         else if (cmd.Ip is not null)
  146.         {
  147.             ip = new IPEndPoint(cmd.Ip, cmd.Port);
  148.         }
  149.         else
  150.         {
  151.             ip = null;
  152.         }
  153.         return ip;
  154.     }
  155. }
复制代码
如此大家可以看到大家无需疯狂 while(true) { await socket.Receive...  }, 减轻了很多大家负担

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册