使用netty实现socks5协议
一、socks5协议简介
SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。
SOCKS是”SOCKetS”的缩写[注 1]。 当防火墙后的客户端要访问外部的服务器时,就跟SOCKS代理服务器连接,这个代理服务器控制客户端访问外网的资格,允许的话,就将客户端的请求发往外部的服务器。
SOCKS 协议第 4 版本为基于 TCP 协议的 C/S 应用,包括 TELNET, FTP 和 使用广泛的信息发现协议如 HTTP 、 WAIS 提供了不保证安全性的防火墙穿透服务。
SOCKS 5 扩展了第 4 版本,加入了 UDP 协议支持,在框架上加入了强认证功能,并且地址信息也加入了域名和 IPV6 的支持。
SOCKS协议不提供加密。
socks5协议适用如下几种场景:
- 局域网内只有某台机器被授权访问网络,其它机器需要连接外部网络,但是未被授权,这时候可以在被授权机器上运行socks5协议的服务端,其它局域网内未被授权的机器上运行socks5客户端程序通过被授权机器上网。
- 网络管理。socks5代理服务器会代理所有流量,所以能获取所有客户端想要访问的目标地址和端口号,这时候代理服务器可以自主决定是否允许客户端访问目标服务器。
- 其它。懂的自然懂,但是由于流量特征明显而且未加密,所以一旦开始用,立马会被封掉服务器,不要玩火,这里仅作为技术研究使用。
二、socks5协议交互过程
socks5协议大体上会经过两个或者三个交互过程,这取决于是否有认证流程。以用户名密码认证方式为例,完整的流程如下图所示
1、版本和认证方式交互
第一步,客户端向代理服务器发送代理请求,其中包含了代理的版本和认证方式:
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
如果是socks5代理,第一个字段VER
的值是0x05
,表明是socks代理的第5个版本。
第二个字段NMETHODS
表示支持的认证方式,第三个字段是一个数组,包含了支持的认证方式列表:
0x00
: 不需要认证0x01
: GSSAPI认证0x02
: 用户名和密码方式认证0x03
: IANA认证0x80-0xfe
: 保留的认证方式0xff
: 不支持任何认证方式
服务端收到客户端的代理请求后,选择双方都支持的认证方式回复给客户端:
+----+--------+
|VER | METHOD |
+----+--------+
| 1 | 1 |
+----+--------+
这个过程完成之后,服务端知道了客户端想要使用的socks版本号,告诉客户端是否使用认证;客户端则通过请求服务端,得知下一步是否需要认证。
2、认证交互
如果上一步版本和认证方式交互的结果,服务器不需要认证,则可以跳过该步骤,否则需要进行认证交互。
上一步协商好了使用的认证方式,这里以使用用户名和密码交互方式为例,接下来客户端需要发送用户名和密码给服务端让服务端进行认证,请求格式如下所示
+----+------+----------+------+----------+
|VER | ULEN | UNAME | PLEN | PASSWD |
+----+------+----------+------+----------+
| 1 | 1 | 1 to 255 | 1 | 1 to 255 |
+----+------+----------+------+----------+
- VER:固定长度一个字节,固定值X'01'表示用户名密码认证
- ULEN:用户名长度,固定一个字节大小
- UNAME:用户名,不固定大小,但是其长度和ULEN一致
- PLEN:密码长度,固定一个字节大小
- PASSWD:密码,不固定大小,但是其长度和PLEN一致
服务端会进行用户名和密码的校验,然后做出如下响应
+----+--------+
|VER | STATUS |
+----+--------+
| 1 | 1 |
+----+--------+
如果服务器响应STATUS的值为X'00',表示认证成功;其它状态表示认证失败,这时候客户端需要关闭连接。
3、数据交互
如果上一步用户名密码认证成功,或者无用户名密码认证,则会进入数据交互阶段,这阶段会进行真正的数据传输。首先,客户端会发送一个请求告诉服务端本次请求的信息,格式如下所示
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
- VER 是SOCKS版本,0x05;
- CMD 是SOCK的命令码
- 0x01 表示CONNECT请求
- 0x02 表示BIND请求
- 0x03 表示UDP转发
- RSV 0x00,保留
- ATYP DST.ADDR类型
- 0x01 IPv4地址
- 0x03 域名类型
- 0x04 IPv6地址
- DST.ADDR 目标服务地址,如果是IPv4类型,则固定4个字节长度;如果是域名类型,第一个字节是域名长度,剩余的内容为域名内容;如果是IPv6类型,固定16个字节长度。
- DST.PORT 目标服务端口,固定两个字节长度
代理服务在接收到该连接报文后,会创建和目标服务器的连接,同时返回和目标服务建立连接的结果报文
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | 0x00 | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
- VER是SOCKS版本,0x05;
- REP应答字段,表示和目标服务建立连接的结果
- 0x00 表示成功
- 0x01 普通SOCKS服务器连接失败
- 0x02 现有规则不允许连接
- 0x03 网络不可达
- 0x04 主机不可达
- 0x05 连接被拒
- 0x06 TTL超时
- 0x07 不支持的命令
- 0x08 不支持的地址类型
- 0x09 - 0xFF未定义
- RSV 0x00,保留
- ATYP BND.ADDR类型
- 0x01 IPv4地址,DST.ADDR部分4字节长度
- 0x03 域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有0结尾。
- 0x04 IPv6地址,16个字节长度。
- BND.ADDR 目标服务地址
- BND.PORT 目标服务端口
至此,Socks5协议的“握手”部分完成,可以开始发送数据。代理服务器只需要将收到的客户端数据“盲目”的转发到目标服务,同时将收到的目标服务数据转发给客户端,只是一个中继(Relay)的角色。
三、netty实现
1.基本实现
netty作为使用java实现的高级网络编程框架,实现socks5协议最终作为代理服务器程序再合适不过了。从上面的交互流程上来看,整个过程还是稍稍有些复杂的,netty框架的特色之一就是实现了各种协议的编解码器给开发人员使用,开箱即用,非常方便。
netty提供了三个解码器和一个编码器帮助开发人员实现socks5协议的服务端的绝大多数功能。
编码器 | 作用 |
---|---|
io.netty.handler.codec.socksx.v5.Socks5ServerEncoder | socks5协议交互过程中编码服务端给客户端的响应 |
解码器 | 作用 |
---|---|
io.netty.handler.codec.socksx.v5.Socks5InitialRequestDecoder | 版本和认证方式交互阶段解码客户端请求 |
io.netty.handler.codec.socksx.v5.Socks5PasswordAuthRequestDecoder | 认证交互阶段解码客户端认证请求 |
io.netty.handler.codec.socksx.v5.Socks5CommandRequestDecoder | 数据交互阶段解码客户端连接请求 |
这几个解码器解决了从抽象的协议请求到对象的转换;而编码器解决了对象到抽象的协议转换。所以这些编解码器只是解决了这些问题还是不够的,剩下的逻辑需要自己实现才行。所以对应着三个解码器,有三个后续的自定义的入栈处理器与其一一对应
处理器 | 作用 |
---|---|
Socks5InitialRequestInboundHandler | 响应版本和认证方式交互阶段客户端请求 |
Socks5PasswordAuthRequestInboundHandler | 响应认证交互阶段客户端认证请求 |
Socks5CommandRequestInboundHandler | 响应数据交互阶段客户端连接请求 |
在第三阶段,在收到客户端发起连接请求后,代理服务器连接目标服务器,这时候涉及到转发客户端的请求到目标服务器以及转发目标服务器的响应到客户端,所以这里设计了两个入栈处理器
处理器 | 作用 | 绑定的Channel |
---|---|---|
Client2DestInboundHandler | 转发客户端请求到目标服务器 | 客户端与代理服务器之间的Channel |
Dest2ClientInboundHandler | 转发目标服务器响应到客户端 | 代理服务器和目标服务器之间的Cahnnel |
2.黑名单处理
这里想要实现一个功能,就是在黑名单中的地址不允许连接,如果是http请求,则直接返回错误页面;https请求或者其它类型协议则直接断开连接。
这个功能在第三阶段连接阶段实现,因为只有这时候才知道客户端想要访问的网络地址。
//检查黑名单
if (inBlackList(msg.dstAddr())) {
log.info("{} 地址在黑名单中,拒绝连接", msg.dstAddr());
//假装连接成功
DefaultSocks5CommandResponse commandResponse = new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, socks5AddressType);
ctx.writeAndFlush(commandResponse);
ctx.pipeline().addLast("HttpServerCodec", new HttpServerCodec());
ctx.pipeline().addLast(new BlackListInboundHandler());
ctx.pipeline().remove(Socks5CommandRequestInboundHandler.class);
ctx.pipeline().remove(Socks5CommandRequestDecoder.class);
return;
}
这里自定义了BlackListInboundHandler
处理http请求类型并返回在黑名单中的友好页面提示。
四、项目地址和使用说明
项目地址:https://gitee.com/kdyzm/socks5-netty
使用方法:由于在windows环境下系统并非天然支持socks5协议,所以需要借助Proxifier工具使其支持socks5;另外,如果出现了连接速度缓慢,有些网页打不开的现象,是Proxifier没设置好,一定要注意使用代理的dns设置,菜单:Profile->Name Resolution 取消Detect DNS settings automatically
选项,勾选Resolve hostnames through proxy
,之后就好了。
五、参考文档
https://zh.wikipedia.org/wiki/SOCKS
https://cloud.tencent.com/developer/article/1781560
https://www.dyxmq.cn/network/socks5.html
https://www.quarkay.com/code/383/socks5-protocol-rfc-chinese-traslation
https://blog.csdn.net/qq_33215972/article/details/105657960
https://segmentfault.com/a/1190000038498680
https://tools.ietf.org/html/rfc1928