有盆友好奇所谓的网络穿透是怎么做的
然后talk is cheap,please show code
所以只好写个简单且常见的websocket例子,
这里的例子大致是这个原理
浏览器插件(或者其他)首先将正常访问请求 --> 转换为socks5访问 --> 假代理服务器建立websocket链接,然后传输socks5协议数据 --> 允许websocket的网关由于不解析websocket数据而不知道是socks5所以未做拦截 --> 真代理服务器从websocket中解析socks5进行转发处理
代码如下
Socks5 --> websocket 端
- internal class Socks5ToWSMiddleware : ITcpProxyMiddleware
- {
- private readonly IForwarderHttpClientFactory httpClientFactory;
- private readonly ILoadBalancingPolicyFactory loadBalancing;
- private readonly ProxyLogger logger;
- private readonly TimeProvider timeProvider;
- public Socks5ToWSMiddleware(IForwarderHttpClientFactory httpClientFactory, ILoadBalancingPolicyFactory loadBalancing, ProxyLogger logger, TimeProvider timeProvider)
- {
- this.httpClientFactory = httpClientFactory;
- this.loadBalancing = loadBalancing;
- this.logger = logger;
- this.timeProvider = timeProvider;
- }
- public Task InitAsync(ConnectionContext context, CancellationToken token, TcpDelegate next)
- {
- // 过滤符合的路由配置
- 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("socks5ToWS", out var b) && bool.TryParse(b, out var isSocks5) && isSocks5)
- {
- feature.IsDone = true;
- route.ClusterConfig?.InitHttp(httpClientFactory);
- return Proxy(context, feature, token);
- }
- }
- return next(context, token);
- }
- private async Task Proxy(ConnectionContext context, IL4ReverseProxyFeature feature, CancellationToken token)
- { // loadBalancing 选取有效 ip
- var route = feature.Route;
- var cluster = route.ClusterConfig;
- DestinationState selectedDestination;
- if (cluster is null)
- {
- selectedDestination = null;
- }
- else
- {
- selectedDestination = feature.SelectedDestination;
- selectedDestination ??= loadBalancing.PickDestination(feature);
- }
- if (selectedDestination is null)
- {
- logger.NotFoundAvailableUpstream(route.ClusterId);
- Abort(context);
- return;
- }
- selectedDestination.ConcurrencyCounter.Increment();
- try
- {
- await SendAsync(context, feature, selectedDestination, cluster, route.Transformer, token);
- selectedDestination.ReportSuccessed();
- }
- catch
- {
- selectedDestination.ReportFailed();
- throw;
- }
- finally
- {
- selectedDestination.ConcurrencyCounter.Decrement();
- }
- }
- private async Task<ForwarderError> SendAsync(ConnectionContext context, IL4ReverseProxyFeature feature, DestinationState selectedDestination, ClusterConfig? cluster, IHttpTransformer transformer, CancellationToken token)
- {
- // 创建 websocket 请求, 这里为了简单,只创建简单 http1.1 websocket
- var destinationPrefix = selectedDestination.Address;
- if (destinationPrefix is null || destinationPrefix.Length < 8)
- {
- throw new ArgumentException("Invalid destination prefix.", nameof(destinationPrefix));
- }
- var route = feature.Route;
- var requestConfig = cluster.HttpRequest ?? ForwarderRequestConfig.Empty;
- var httpClient = cluster.HttpMessageHandler ?? throw new ArgumentNullException("httpClient");
- var destinationRequest = new HttpRequestMessage();
- destinationRequest.Version = HttpVersion.Version11;
- destinationRequest.VersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
- destinationRequest.Method = HttpMethod.Get;
- destinationRequest.RequestUri ??= new Uri(destinationPrefix, UriKind.Absolute);
- destinationRequest.Headers.TryAddWithoutValidation(HeaderNames.Connection, HeaderNames.Upgrade);
- destinationRequest.Headers.TryAddWithoutValidation(HeaderNames.Upgrade, HttpForwarder.WebSocketName);
- destinationRequest.Headers.TryAddWithoutValidation(HeaderNames.SecWebSocketVersion, "13");
- destinationRequest.Headers.TryAddWithoutValidation(HeaderNames.SecWebSocketKey, ProtocolHelper.CreateSecWebSocketKey());
- destinationRequest.Content = new EmptyHttpContent();
- if (!string.IsNullOrWhiteSpace(selectedDestination.Host))
- {
- destinationRequest.Headers.TryAddWithoutValidation(HeaderNames.Host, selectedDestination.Host);
- }
-
- // 建立websocket 链接,成功则直接 复制原始 req/resp 数据,不做任何而外处理
- var destinationResponse = await httpClient.SendAsync(destinationRequest, token);
- if (destinationResponse.StatusCode == HttpStatusCode.SwitchingProtocols)
- {
- using var destinationStream = await destinationResponse.Content.ReadAsStreamAsync(token);
- var clientStream = new DuplexPipeStreamAdapter<Stream>(null, context.Transport, static i => i);
- var activityCancellationSource = ActivityCancellationTokenSource.Rent(route.Timeout);
- var requestTask = StreamCopier.CopyAsync(isRequest: true, clientStream, destinationStream, StreamCopier.UnknownLength, timeProvider, activityCancellationSource,
- autoFlush: destinationResponse.Version == HttpVersion.Version20, token).AsTask();
- var responseTask = StreamCopier.CopyAsync(isRequest: false, destinationStream, clientStream, StreamCopier.UnknownLength, timeProvider, activityCancellationSource, token).AsTask();
- var task = await Task.WhenAny(requestTask, responseTask);
- await clientStream.DisposeAsync();
- if (task.IsCanceled)
- {
- Abort(context);
- activityCancellationSource.Cancel();
- if (task.Exception is not null)
- {
- throw task.Exception;
- }
- }
- }
- else
- {
- Abort(context);
- return ForwarderError.UpgradeRequestDestination;
- }
- return ForwarderError.None;
- }
- 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 static void Abort(ConnectionContext upstream)
- {
- upstream.Transport.Input.CancelPendingRead();
- upstream.Transport.Output.CancelPendingFlush();
- upstream.Abort();
- }
- }
复制代码 websocket --> Socks5 端
- internal class WSToSocks5HttpMiddleware : IMiddleware
- {
- private static ReadOnlySpan<byte> EncodedWebSocketKey => "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"u8;
- private WebSocketMiddleware middleware;
- private readonly Socks5Middleware socks5Middleware;
- public WSToSocks5HttpMiddleware(IOptions<WebSocketOptions> options, ILoggerFactory loggerFactory, Socks5Middleware socks5Middleware)
- {
- middleware = new WebSocketMiddleware(Scoks5, options, loggerFactory);
- this.socks5Middleware = socks5Middleware;
- }
- private async Task Scoks5(HttpContext context)
- {
- var upgradeFeature = context.Features.Get<IHttpUpgradeFeature>();
- // 检查是否未正确 websocket 请求
- var f = context.Features.Get<IHttpWebSocketFeature>();
- if (f.IsWebSocketRequest)
- {
- // 返回 websocket 接受信息
- var responseHeaders = context.Response.Headers;
- responseHeaders.Connection = HeaderNames.Upgrade;
- responseHeaders.Upgrade = HttpForwarder.WebSocketName;
- responseHeaders.SecWebSocketAccept = CreateResponseKey(context.Request.Headers.SecWebSocketKey.ToString());
- var stream = await upgradeFeature!.UpgradeAsync(); // Sets status code to 101
-
- // 建原始 websocket stream 包装成 pipe 方便使用原来的 socks5Middleware 实现
- var memoryPool = context is IMemoryPoolFeature s ? s.MemoryPool : MemoryPool<byte>.Shared;
- StreamPipeReaderOptions readerOptions = new StreamPipeReaderOptions
- (
- pool: memoryPool,
- bufferSize: memoryPool.GetMinimumSegmentSize(),
- minimumReadSize: memoryPool.GetMinimumAllocSize(),
- leaveOpen: true,
- useZeroByteReads: true
- );
- var writerOptions = new StreamPipeWriterOptions
- (
- pool: memoryPool,
- leaveOpen: true
- );
- var input = PipeReader.Create(stream, readerOptions);
- var output = PipeWriter.Create(stream, writerOptions);
- var feature = context.Features.Get<IReverseProxyFeature>();
- var route = feature.Route;
- using var cts = CancellationTokenSourcePool.Default.Rent(route.Timeout);
- var token = cts.Token;
- context.Features.Set<IL4ReverseProxyFeature>(new L4ReverseProxyFeature() { IsDone = true, Route = route });
- // socks5Middleware 进行转发
- await socks5Middleware.Proxy(new WebSocketConnection(context.Features)
- {
- Transport = new WebSocketDuplexPipe() { Input = input, Output = output },
- ConnectionId = context.Connection.Id,
- Items = context.Items,
- }, null, token);
- }
- else
- {
- context.Response.StatusCode = StatusCodes.Status400BadRequest;
- }
- }
- public static string CreateResponseKey(string requestKey)
- {
- // "The value of this header field is constructed by concatenating /key/, defined above in step 4
- // in Section 4.2.2, with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of
- // this concatenated value to obtain a 20-byte value and base64-encoding"
- // https://tools.ietf.org/html/rfc6455#section-4.2.2
- // requestKey is already verified to be small (24 bytes) by 'IsRequestKeyValid()' and everything is 1:1 mapping to UTF8 bytes
- // so this can be hardcoded to 60 bytes for the requestKey + static websocket string
- Span<byte> mergedBytes = stackalloc byte[60];
- Encoding.UTF8.GetBytes(requestKey, mergedBytes);
- EncodedWebSocketKey.CopyTo(mergedBytes[24..]);
- Span<byte> hashedBytes = stackalloc byte[20];
- var written = SHA1.HashData(mergedBytes, hashedBytes);
- if (written != 20)
- {
- throw new InvalidOperationException("Could not compute the hash for the 'Sec-WebSocket-Accept' header.");
- }
- return Convert.ToBase64String(hashedBytes);
- }
- public Task InvokeAsync(HttpContext context, RequestDelegate next)
- {
- // 过滤符合的路由配置
- var feature = context.Features.Get<IReverseProxyFeature>();
- if (feature is not null)
- {
- var route = feature.Route;
- if (route is not null && route.Metadata is not null
- && route.Metadata.TryGetValue("WSToSocks5", out var b) && bool.TryParse(b, out var isSocks5) && isSocks5)
- {
- // 这里偷个懒,利用现成的 WebSocketMiddleware 检查 websocket 请求,
- return middleware.Invoke(context);
- }
- }
- return next(context);
- }
- }
- internal class WebSocketConnection : ConnectionContext
- {
- public WebSocketConnection(IFeatureCollection features)
- {
- this.features = features;
- }
- public override IDuplexPipe Transport { get; set; }
- public override string ConnectionId { get; set; }
- private IFeatureCollection features;
- public override IFeatureCollection Features => features;
- public override IDictionary<object, object?> Items { get; set; }
- }
- internal class WebSocketDuplexPipe : IDuplexPipe
- {
- public PipeReader Input { get; set; }
- public PipeWriter Output { get; set; }
- }
复制代码 所以利用 websocket 伪装的例子大致就是这样就可以完成 tcp的 socks5 处理了 udp我就不来了
最后有兴趣的同学给 L4/L7的代理 VKProxy 点个赞呗 (暂时没有使用文档,等啥时候有空把配置ui站点完成了再来吧)
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |