Thales的博客站
切换导航
导航
博客首页
关于博主
CV
工具
博客信息
基于netty的shadowsocks-java(二):原理与结构
标签
:
java
shadowsocks
netty
作者:Thales 尊重博主原创文章,转载请注明文章出于此处。
>本文也同时发表于[CSDN](https://blog.csdn.net/Tales_/article/details/82348322) >GitHub项目[源码](https://github.com/zhihengjiang/shasowsocks-java) 在前文中已经向大家介绍了如何安装以及开始开始使用[shadowsocks-java](https://github.com/zhihengjiang/shasowsocks-java),本文就继续介绍如何使用java实现shadowsocks。Shadowsocks是一个基于socks5代理的加密传输协议,和socks5协议息息相关,但却又和它不同。shadowsocks协议可分为两部分: - 用于传输本地客户端请求到运行在防火墙之内的本地socks5代理服务端的socks5协议,本地socks5代理服务器端一般被成为ss-local。 - 用于传输本地socks5服务器端捕获到的请求到远程服务器端(ss-server)的类socks协议。 整个shadowsocks的结构大致可以如下表示: [![shadowsocks结构](/static/images/201809/32371932-cdc55044-c060-11e7-9b0e-c1a7fec2b428.png "shadowsocks结构")](http://zhihengjiang.com/static/images/201809/32371932-cdc55044-c060-11e7-9b0e-c1a7fec2b428.png "shadowsocks结构") 代理的流程如下: 1. 本地客户端请求经过sock5客户端后,将请求发送到ss-local 2. 实ss-local程序会把流量加密,然后把普通的TCP流量发往海外服务器(ss-server); 3. 海外服务器收到请求后,解密得到要访问的原请求 4. 海外服务器请求原请求的目标后把得到的数据加密返回给ss-local 5. ss-local解密转发给browser。 下面就介绍每一步里设计到的原理。 ## 1. Socks5协议(客户端->ss-local) 在本地客户端发送请求到本地的socks5服务器时,这部分传输由socks协议控制。这一部分和普通的socks5代理没有区别,ss-local在此时的作用完全就是一个sock5代理服务器。本地客户端可以是一个浏览器(有的浏览器如chrome不支持socks5代理,但可以通过swtichyOmega插件将http请求转换为sock5请求),也可以是一个需要网络IO的应用软件。 根据OSI模型,socks5是一个会话层的协议,它位于比http更低的层次,socks5客户端通过tcp或udp协议与socks服务端传输数据。Socks5客户端和socks5服务端的传输大致可分为三个部分:1. 握手阶段; 2. 建立连接;3. 传输阶段。 ### 握手阶段 客户端和服务器在握手阶段协商认证方式,比如:是否采用用户名/密码的方式进行认证,或者不采用任何认证方式。 客户端发送给服务器的消息格式如下(数字表示对应字段占用的字节数): |VER | NMETHODS | METHODS | |:--:|:--------:|:--------:| | 1 | 1 | 1~255 | 其中: - VER 字段是当前协议的版本号,也就是 5; - NMETHODS 字段是 METHODS 字段占用的字节数; - METHODS 字段的每一个字节表示一种认证方式,表示客户端支持的全部认证方式。当前的定义有: - 0x00 不需要认证 - 0x01 GSSAPI - 0x02 用户名、密码认证 - 0x03 - 0x7F由IANA分配(保留) - 0x80 - 0xFE为私人方法保留 - 0xFF 无可接受的方法 服务器在收到客户端的协商请求后,会检查是否有服务器支持的认证方式,并返回客户端消息: |VER | NMETHODS | |:--:|:--------:| | 1 | 1 | 由于ss-local(在此处作为socks5服务端)一般运行的本地,一般无需认证,所以服务端的回复格式一般是: `0x05 0x00` , `0x05 0xff` ### 建立链接 经过握手后,sock5客户端(浏览器或其它应用软件)即可想socks5服务端(ss-local)发起TCP请求,请求的格式如下: |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | |----|-----|-------|------|----------|----------| | 1 | 1 | 1 | 1 | Variable | 2 | 其中: - VER是SOCKS版本,这里应该是0x05; - CMD是SOCK的命令码 - 0x01表示CONNECT请求 - 0x02表示BIND请求 - 0x03表示UDP转发 - RSV 0x00,保留 - ATYP 是DST.ADDR的类型 - 0x01 IPv4地址,DST.ADDR部分4字节长度 - 0x03 域名,DST.ADDR部分第一个字节为域名长度, DST.ADDR剩余的内容为域名,没有\0结尾。 - 0x04 IPv6地址,16个字节长度。 - DST.ADDR 目的地址 - DST.PORT 网络字节序表示的目的端口 服务器按以下格式回应客户端的请求(以字节为单位): |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | |----|-----|-------|------|----------|----------| | 1 | 1 | 1 | 1 | Variable | 2 | 涉及每个字段的解释部分过多,请参考协议[定义文档](https://tools.ietf.org/html/rfc1928),此处不再过多阐述。 ### 传输阶段 经过握手和建立连接的请求后,客户端(浏览器或其它应用软件)向服务端(ss-local)发送需要代理的请求。虽然socks5也可经过udp协议发送请求,但由于ss-local运行在本地,客户端一般只需要通过TCP与socks5服务端(ss-local)建立链接,然后发送请求。 ## 2. Shadowsocks自定协议(加密与传输) 本地客户端与socks5服务端(ss-local)连接后,此时整个shadowsocks的代理进入第二阶段,ss-local对请求进行加密,然后通过自己定义的协议与ss-server通信。 ss-local 转发本地客户端发送过来的请求时,需要对请求封装自定义的协议头,这个协议头和socks5的请求非常相似,但它只包含socks5请求的后三部分,如下所示: | ATYP | DST.ADDR | DST.PORT | |------|----------|----------| | 1 | Variable | 2 | 其中: - ATYP 是DST.ADDR的类型 - DST.ADDR 是请求地址 - DST.PORT 是请求端口 封装此请求头后,ss-local随后对其进行加密,这里就是防止防火墙探测此连接目的地的关键所在 ### 加密与握手 早期的加密方式使用的是流加密,如今的shadowsocks各种版本大多实现了更好的加密方式:AEAD。但由于shdowsocks-java使用的仍然是流加密,所以此处仅仅阐述流加密的处理方式。相关文档请点击[此处](https://shadowsocks.org/en/spec/Stream-Ciphers.html)。 为了使相同的明文和相同的密钥产生不同的密文,让攻击者难以对同一把密钥的密文进行破解,ss-local使用随机数生成器生成的一个固定长度的输入值,称为 IV(Initialization Vector, 初始化向量),用这个IV以及ss-local和ss-server约定的密钥对上述封装了协议头的请求进行加密,并将次IV封装在加密后的密文前面,因此ss-local与ss-server的TCP握手包如下所示: | IV | Payload | |-------|----------| | Fixed | Variable | 其中Payload就是加密后的密文。 ### 传输加密密文 此时ss-local即可通过tcp连接将这种结构的数据发送到ss-server。随后ss-local向ss-server传输的数据包不再在头部添加IV。 ## 3. Shadowsocks自定协议(解密与转发) ss-local与ss-server握手后,ss-server就会得到ss-local发送过来的IV与加密的数据包。ss-server首先使用IV与预先约定的密钥对密文进行解密。解密后即可解析在ss-local添加的自定义的请求头: | ATYP | DST.ADDR | DST.PORT | |------|----------|----------| | 1 | Variable | 2 | 解析成功后即可更具请求头的DST.ADDR 和 DST.PORT与目标服务器建立TCP连接,并将请求头后面的数据转发给目标服务器(DST.ADDR:DST.PORT)。至此,本地客户端(浏览器或其它应用)向目标服务器(本地客户端需要访问的服务器)传输通道已经完全建立: ``` 本地客户端 -> ss-local -> ss-server -> 目标服务器 ``` 之后浏览器的在此通道传输的数据在ss-local不再添加请求头和IV。 ## 4. Response的加密与转发 ss-server得到目标服务器的response后,所做的事情和ss-local收到浏览器的请求后进行加密时所做的类似,即ss-server也要生成一个IV,使用此IV以及预先约定的密钥对response进行加密,同时也在response头部封装一个IV,然后将加密后的密文从之前与ss-local建立的TCP连接转发给ss-local。 ## 5. Response的解密与转发 ss-local获取从ss-server发送过来的IV和密文,然后使用此次得到的IV和预先约定的密钥对密文解密,并将密文从之前与本地客户端建立的tcp链接转发给本地客户端。至此,shadowsocks所做的工作已经全部完成。
发布于:『2018-09-03 06:09』
博客类别:
java
阅读(5971) 评论(3)
上一篇:
基于netty的shaodowsocks(一)
下一篇:
netty 简介
用户评论
1楼 60.173.239.215
不错,博主写的shaodowsocks很好用,本人java菜鸟一个,哈哈,网络编程这块一直欠缺,想做一个java版的,但是受限于本人的技术水平一直未能实现。感谢博主的分享。 [2019-03-22 16:13]
发表评论
验证码:
发表评论
文章分类
java(10)
Spring(0)
其它(1)
文章存档
2018年10月(1)
2018年09月(8)
2018年07月(2)
友情链接
分享到