有盆友好奇所谓的网络穿透是怎么做的

然后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站点完成了再来吧)

如何使用 websocket 完成 socks5 网络穿透的更多相关文章

  1. n2n网络穿透内网

    目录 前言 配置 网络拓扑: 公网服务器的配置 公司电脑的配置 家里笔记本的配置 注意事项 使用n2n网络 n2n的各edge之间传输数据 补充:NAT类型 后记 前言 在家里的时候比较经常需要对公司 ...

  2. nat网络穿透整理笔记(思维导图)

    mindmanger整理的,关于Nat穿透,图片太小,可以点击放大,单独看图片.

  3. WebSocket协议中文版

    WebSocket协议中文版 摘要 WebSocket协议实现在受控环境中运行不受信任代码的一个客户端到一个从该代码已经选择加入通信的远程主机之间的全双工通信.用于这个安全模型是通常由web浏览器使用 ...

  4. Issue 7: 网络in action

    网络运维基础 基础参数 配置:IP,子网掩码,网关,dns服务器,dhcp服务器 基础应用 在网关设置上搭建VPN组网 改host文件 单台主机原则上只能配置一个网关 协议 协议是全球都遵守的一套编码 ...

  5. 在线聊天室的实现(1)--websocket协议和javascript版的api

    前言: 大家刚学socket编程的时候, 往往以聊天室作为学习DEMO, 实现简单且上手容易. 该Demo被不同语言实现和演绎, 网上相关资料亦不胜枚举. 以至于很多技术书籍在讲解网络相关的编程时, ...

  6. Unity3d 网络编程(二)(Unity3d内建网络各项參数介绍)

    这里是全部Unity3d在网络中能用到相关的类及方法.纵观參数功能, Unity3d来写一个手游是不二的选择: RPC 能够传递的參数 int float string NetworkPlayer N ...

  7. C#(SuperWebSocket)与websocket通信

    原文:C#(SuperWebSocket)与websocket通信 客户端代码 点击可以查看一些关于websocket的介绍 <!DOCTYPE html> <html> &l ...

  8. WebSocket学习笔记——无痛入门

    WebSocket学习笔记——无痛入门 标签: websocket 2014-04-09 22:05 4987人阅读 评论(1) 收藏 举报  分类: 物联网学习笔记(37)  版权声明:本文为博主原 ...

  9. WebSocket抓包分析

    转载自:https://www.cnblogs.com/songwenjie/p/8575579.html Chrome控制台 (1)F12进入控制台,点击Network,选中ws栏,注意选中Filt ...

  10. 爬取实时变化的 WebSocket 数据(转载)

    本文转自:https://mp.weixin.qq.com/s/fuS3uDvAWOQBQNetLqzO-g 一.前言 作为一名爬虫工程师,在工作中常常会遇到爬取实时数据的需求,比如体育赛事实时数据. ...

随机推荐

  1. MAC消息认证码介绍

    此MAC是密码学概念,与计算机网络不同 为什么有了摘要算法还要有MAC 摘要算法保障的是消息的完整性 归根到底就是由H(x)来保证x的完整 那么问题来了,如果我知道你所使用的摘要算法(例如中间人攻击) ...

  2. vue element UI el-table表格添加行点击事件

    <el-table @row-click="openDetails"></el-table> //对应的 methods 中//点击行事件methods: ...

  3. Android ADB 使用笔记

    ADB 工作原理 当启动某个adb客户端时,该客户端会先检查是否有adb服务器正在运行,如果没有则启动服务器进程.服务器会在启动后与本地TCP端口 5037 绑定,并监听adb客户端 发出的命令. 服 ...

  4. 浅谈Processing中的 println() 打印输出函数[String]

    简单看一下Processing中的打印输出函数println()相关用法. 部分源码学习 /** * ( begin auto-generated from println.xml ) * * Wri ...

  5. 一种基于虚拟摄像头、NDI、OBS以及yolo的多机视觉目标检测方案

    一种基于虚拟摄像头.NDI.OBS以及yolo的多机视觉目标检测方案 绪论 近来为了实现某种实时展示效果,笔者希望通过一套方案实现在两台主机上分别运行仿真平台以及视觉深度学习算法.透过对当下较为流行的 ...

  6. 青岛oj集训1

    2025/3/4 内容:有向无环图(DAG) 优点:DAG有很多良好性质 拓扑排序 用处:可以根据拓扑序进行dp 这次计算所用的所有边的权值都是有计算过的 一张DAG图肯定有拓扑序(bfs序,dfs序 ...

  7. 获取Typora激活码的方法主要有以下几种

    ‌官方购买‌:访问Typora官网下载Typora软件.请注意,官网下载版本需购买激活,否则仅有15天试用期.购买费用为89元‌ 1. ‌使用激活工具‌:可以通过下载特定的激活工具来获取激活码.具体步 ...

  8. 【目标检测】一、初始的R-CNN与SVM

    1.流程 为什么要用SVM而不是CNN最后一层的softmax? 取什么模型必然是有标准衡量,这个流程取得是书上[4]写的,作者说他得实验证明SVM比FC的mAP要高,所以我流程暂且这样画了. R-C ...

  9. Django实战项目-学习任务系统-用户管理

    接着上期代码框架,开发第6个功能,用户管理,查看用户信息和学生用户属性值,尤其是总积分值,还可以查看积分流水明细,完成任务奖励积分,兑换物品消耗积分. 第一步:编写第6个功能-用户管理 1,编辑模型文 ...

  10. BUUCTF---old flashion

    1.题目 2.知识 3.解题 很奇怪,一段英文字母,看起来像维吉尼亚,但需要key,不知道什么是Key,我们丢到q爆破中试试 直接得出来了flag:flag{n1_2hen-d3_hu1-mi-ma_ ...