使用netty實現socks5協議

狂盜一枝梅發表於2021-05-06

一、socks5協議簡介

SOCKS是一種網路傳輸協議,主要用於客戶端與外網伺服器之間通訊的中間傳遞。

SOCKS是”SOCKetS”的縮寫[注 1]。 當防火牆後的客戶端要訪問外部的伺服器時,就跟SOCKS代理伺服器連線,這個代理伺服器控制客戶端訪問外網的資格,允許的話,就將客戶端的請求發往外部的伺服器。

SOCKS 協議第 4 版本為基於 TCP 協議的 C/S 應用,包括 TELNET, FTP 和 使用廣泛的資訊發現協議如 HTTP 、 WAIS 提供了不保證安全性的防火牆穿透服務。

SOCKS 5 擴充套件了第 4 版本,加入了 UDP 協議支援,在框架上加入了強認證功能,並且地址資訊也加入了域名和 IPV6 的支援。

SOCKS協議不提供加密

image-20210506143400298

socks5協議適用如下幾種場景:

  • 區域網內只有某臺機器被授權訪問網路,其它機器需要連線外部網路,但是未被授權,這時候可以在被授權機器上執行socks5協議的服務端,其它區域網內未被授權的機器上執行socks5客戶端程式通過被授權機器上網。
  • 網路管理。socks5代理伺服器會代理所有流量,所以能獲取所有客戶端想要訪問的目標地址和埠號,這時候代理伺服器可以自主決定是否允許客戶端訪問目標伺服器。
  • 其它。懂的自然懂,但是由於流量特徵明顯而且未加密,所以一旦開始用,立馬會被封掉伺服器,不要玩火,這裡僅作為技術研究使用。

二、socks5協議互動過程

socks5協議大體上會經過兩個或者三個互動過程,這取決於是否有認證流程。以使用者名稱密碼認證方式為例,完整的流程如下圖所示

image-20210506153323085

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

https://tools.ietf.org/html/rfc1929

https://tools.ietf.org/html/rfc1961

相關文章