写点什么

如何基于 Kestrel 实现 socks5 代理

作者:八苦-瞿昙
  • 2025-04-23
    四川
  • 本文字数:15073 字

    阅读完需:约 49 分钟

前言

之前做了个轮子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),所以它是不能阻挡我们的魔爪


(ps1. 不过这样绕过限制可能会在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<IListenHandler, TcpListenHandler>(); i.AddSingleton<IListenHandler, UdpListenHandler>(); i.AddSingleton<IListenHandler, HttpListenHandler>(); }) .Build();
await app.RunAsync();
复制代码
如何处理 tcp
internal class TcpListenHandler : ListenHandlerBase{    private readonly List<EndPointOptions> endPointOptions = new List<EndPointOptions>();    private readonly ILogger<TcpListenHandler> logger;    private readonly IConnectionFactory connectionFactory;
public TcpListenHandler(ILogger<TcpListenHandler> 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<UdpListenHandler> logger;    private readonly IUdpConnectionFactory udp;    private readonly IPEndPoint proxyServer = new(IPAddress.Parse("127.0.0.1"), 11000);
public UdpListenHandler(ILogger<UdpListenHandler> 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<HttpListenHandler> logger; private readonly ICertificateLoader certificateLoader;
public HttpListenHandler(ILogger<HttpListenHandler> 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<IConnectionListenerFactory> transportFactories, IEnumerable<IMultiplexedConnectionListenerFactory> 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<IConnectionListenerFactory>()).ToList(), Enumerable.Reverse(serviceProvider.GetServices<IMultiplexedConnectionListenerFactory>()).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<ILoggerFactory>(); var l = CreateLogger.MakeGenericMethod(KestrelExtensions.HttpsConnectionMiddlewareType).Invoke(null, new object[] { log }); m.Invoke(r, new object[] { serviceProvider.GetRequiredService<IHostEnvironment>(), log.CreateLogger<KestrelServer>(), 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<IOptions<KestrelServerOptions>>(), serviceProvider.GetRequiredService<ILoggerFactory>(), 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<EndPoint> BindAsync(EndPointOptions endpointConfig, ConnectionDelegate connectionDelegate, CancellationToken cancellationToken) { return BindAsyncMethod.Invoke(transportManager, new object[] { endpointConfig.EndPoint, connectionDelegate, endpointConfig.Init(), cancellationToken }) as Task<EndPoint>; }
public Task<EndPoint> BindAsync(EndPointOptions endpointConfig, MultiplexedConnectionDelegate multiplexedConnectionDelegate, CancellationToken cancellationToken) { return MultiplexedBindAsyncMethod.Invoke(transportManager, new object[] { endpointConfig.EndPoint, multiplexedConnectionDelegate, endpointConfig.GetListenOptions(), cancellationToken }) as Task<EndPoint>; }
public Task StopEndpointsAsync(List<EndPointOptions> 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<HttpApplication.Context> application, HttpProtocols protocols, bool addAltSvcHeader) { KestrelExtensions.UseHttpServerMethod.Invoke(null, new object[] { builder, serviceContext, application, protocols, addAltSvcHeader }); return builder; }
public IMultiplexedConnectionBuilder UseHttp3Server(IMultiplexedConnectionBuilder builder, IHttpApplication<HttpApplication.Context> 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<ILoggerFactory>(), metrics }); return KestrelExtensions.HttpsConnectionMiddlewareOnConnectionAsyncMethod.CreateDelegate<ConnectionDelegate>(o); }
public async Task BindHttpApplicationAsync(EndPointOptions options, IHttpApplication<HttpApplication.Context> application, CancellationToken cancellationToken, HttpProtocols protocols = HttpProtocols.Http1AndHttp2AndHttp3, bool addAltSvcHeader = true, Action<IConnectionBuilder> config = null , Action<IMultiplexedConnectionBuilder> 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<byte, ISocks5Auth> auths;    private readonly IConnectionFactory tcp;    private readonly IHostResolver hostResolver;    private readonly ITransportManager transport;    private readonly IUdpConnectionFactory udp;
public Socks5Middleware(IEnumerable<ISocks5Auth> 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<IL4ReverseProxyFeature>(); 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<ReadOnlyMemory<byte>> OnRequestAsync(ConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token, TcpProxyDelegate next) { return next(context, source, token); }
public Task<ReadOnlyMemory<byte>> OnResponseAsync(ConnectionContext context, ReadOnlyMemory<byte> 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<EndPointOptions>() { 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<IPEndPoint> 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<IPEndPoint> 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... }, 减轻了很多大家负担

发布于: 刚刚阅读数: 6
用户头像

八苦-瞿昙

关注

一个假和尚,不懂人情世故。 2018-11-23 加入

会点点技术,能写些代码,只爱静静。 g hub: https://github.com/fs7744 黑历史:https://www.cnblogs.com/fs7744

评论

发布
暂无评论
如何基于 Kestrel 实现 socks5 代理_C#_八苦-瞿昙_InfoQ写作社区