一、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