SOCKS 是一種網絡傳輸協議,主要用於客戶端與外網服務器之間通訊的中間傳遞,SOCKS 是"SOCKetS"的縮寫。 SOCKS5 是 SOCKS4 的升級版,其主要多了鑑定、IPv6、UDP 支持。

SOCKS5 協議可以分為三個部分:

  • (1) 協議版本及認證方式
  • (2) 根據認證方式執行對應的認證
  • (3) 請求信息

(1)協議版本及認證方式

創建與 SOCKS5 服務器的 TCP 連接後客戶端需要先發送請求來協議版本及認證方式,

VERNMETHODSMETHODS
111-255
  • VER 是 SOCKS 版本,這裡應該是 0x05;
  • NMETHODS 是 METHODS 部分的長度;
  • METHODS 是客戶端支持的認證方式列表,每個方法佔 1 字節。當前的定義是:
    • 0x00 不需要認證
    • 0x01 GSSAPI
    • 0x02 用戶名、密碼認證
    • 0x03 - 0x7F 由 IANA 分配(保留)
    • 0x80 - 0xFE 為私人方法保留
    • 0xFF 無可接受的方法

服務器回覆客戶端可用方法:

VERMETHOD
11
  • VER 是 SOCKS 版本,這裡應該是 0x05;
  • METHOD 是服務端選中的方法。如果返回 0xFF 表示沒有一個認證方法被選中,客戶端需要關閉連接。

代碼實現:

 1type ProtocolVersion struct {
 2    VER uint8
 3    NMETHODS uint8
 4    METHODS []uint8
 5}
 6
 7
 8func (s *ProtocolVersion) handshake(conn net.Conn) error {
 9    b := make([]byte, 255)
10    n, err := conn.Read(b)
11    if err != nil {
12        log.Println(err)
13        return err
14    }
15    s.VER = b[0] //ReadByte reads and returns a single byte,第一個參數為socks的版本號
16    s.NMETHODS = b[1] //nmethods是記錄methods的長度的。nmethods的長度是1個字節
17    if n != int(2+s.NMETHODS) {
18        return errors.New("協議錯誤, sNMETHODS不對")
19    }
20    s.METHODS = b[2:2+s.NMETHODS] //讀取指定長度信息,讀取正好len(buf)長度的字節。如果字節數不是指定長度,則返回錯誤信息和正確的字節數
21
22    if s.VER != 5 {
23        return errors.New("該協議不是socks5協議")
24    }
25
26    //服務器回應客戶端消息:
27    //第一個參數表示版本號為5,即socks5協議,
28    // 第二個參數表示服務端選中的認證方法,0即無需密碼訪問, 2表示需要用戶名和密碼進行驗證。
29    resp :=[]byte{5, 0}
30    conn.Write(resp)
31    return nil
32}

(2)根據認證方式執行對應的認證

SOCKS5 協議提供 5 種認證方式:

  • 0x00 不需要認證
  • 0x01 GSSAPI
  • 0x02 用戶名、密碼認證
  • 0x03 - 0x7F 由 IANA 分配(保留)
  • 0x80 - 0xFE 為私人方法保留

這裡就主要介紹用戶名、密碼認證。 在客戶端、服務端協商使用用戶名密碼認證後,客戶端發出用戶名密碼:

鑑定協議版本用戶名長度用戶名密碼長度密碼
11動態1動態

服務器鑑定後發出如下回應:

鑑定協議版本鑑定狀態
11

其中鑑定狀態 0x00 表示成功,0x01 表示失敗。

代碼實現:

 1type Socks5Auth struct {
 2    VER uint8
 3    ULEN uint8
 4    UNAME string
 5    PLEN uint8
 6    PASSWD string
 7}
 8
 9func (s *Socks5Auth) authenticate(conn net.Conn) error {
10    b := make([]byte, 128)
11    n, err := conn.Read(b)
12    if err != nil{
13        log.Println(err)
14        return err
15    }
16
17    s.VER = b[0]
18    if s.VER != 5 {
19        return errors.New("該協議不是socks5協議")
20    }
21
22    s.ULEN = b[1]
23    s.UNAME = string(b[2:2+s.ULEN])
24    s.PLEN = b[2+s.ULEN+1]
25    s.PASSWD = string(b[n-int(s.PLEN):n])
26    log.Println(s.UNAME, s.PASSWD)
27    if username != s.UNAME || passwd != s.PASSWD {
28        return errors.New("賬號密碼錯誤")
29    }
30
31    /**
32    回應客戶端,響應客戶端連接成功
33    The server verifies the supplied UNAME and PASSWD, and sends the
34    following response:
35                            +----+--------+
36                            |VER | STATUS |
37                            +----+--------+
38                            | 1  |   1    |
39                            +----+--------+
40    A STATUS field of X'00' indicates success. If the server returns a
41    `failure' (STATUS value other than X'00') status, it MUST close the
42    connection.
43    */
44	resp := []byte{0x05, 0x00}
45    conn.Write(resp)
46
47    return nil
48}

但其實,現在大家都習慣自己採用加密流的方式進行加密,很少採用用戶名密碼的形式進行加密,後面章節會介紹一種對 SOCKS 的混淆加密方式。

(3)請求信息

認證結束後客戶端就可以發送請求信息。如果認證方法有特殊封裝要求,請求必須按照方法所定義的方式進行封裝解密,其原始格式如下:

VERCMDRSVATYPDST.ADDRDST.PORT
110x001動態2
  • VER 是 SOCKS 版本,這裡應該是 0x05;
  • CMD 是 SOCK 的命令碼
    • 0x01 表示 CONNECT 請求
    • 0x02 表示 BIND 請求
    • 0x03 表示 UDP 轉發
  • RSV 0x00,保留
  • ATYP DST.ADDR 類型
  • DST.ADDR 目的地址
    • 0x01 IPv4 地址,DST.ADDR 部分 4 字節長度
    • 0x03 域名,DST.ADDR 部分第一個字節為域名長度,DST.ADDR 剩餘的內容為域名,沒有\0 結尾。
    • 0x04 IPv6 地址,16 個字節長度。
  • DST.PORT 網絡字節序表示的目的端口

代碼實現:

 1type Socks5Resolution struct {
 2    VER uint8
 3    CMD uint8
 4    RSV uint8
 5    ATYP uint8
 6    DSTADDR []byte
 7    DSTPORT uint16
 8    DSTDOMAIN string
 9    RAWADDR *net.TCPAddr
10}
11
12func (s *Socks5Resolution) lstRequest(conn net.Conn) error {
13    b := make([]byte, 128)
14    n, err := conn.Read(b)
15    if err != nil || n < 7 {
16        log.Println(err)
17        return errors.New("請求協議錯誤")
18    }
19    s.VER = b[0]
20    if s.VER != 5 {
21        return errors.New("該協議不是socks5協議")
22    }
23
24    s.CMD = b[1]
25    if s.CMD != 1 {
26        return errors.New("客戶端請求類型不為代理連接, 其他功能暫時不支持.")
27    }
28    s.RSV = b[2] //RSV保留字端,值長度為1個字節
29
30    s.ATYP = b[3]
31
32    switch s.ATYP {
33    case 1:
34        //	IP V4 address: X'01'
35        s.DSTADDR = b[4 : 4+net.IPv4len]
36    case 3:
37        //	DOMAINNAME: X'03'
38        s.DSTDOMAIN = string(b[5:n-2])
39        ipAddr, err := net.ResolveIPAddr("ip", s.DSTDOMAIN)
40		if err != nil {
41			return err
42		}
43        s.DSTADDR = ipAddr.IP
44    case 4:
45        //	IP V6 address: X'04'
46        s.DSTADDR = b[4 : 4+net.IPv6len]
47	default:
48		return errors.New("IP地址錯誤")
49    }
50
51    s.DSTPORT = binary.BigEndian.Uint16(b[n-2:n])
52    // DSTADDR全部換成IP地址,可以防止DNS汙染和封殺
53    s.RAWADDR = &net.TCPAddr{
54		IP:   s.DSTADDR,
55		Port: int(s.DSTPORT),
56    }
57
58    /**
59    回應客戶端,響應客戶端連接成功
60        +----+-----+-------+------+----------+----------+
61        |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
62        +----+-----+-------+------+----------+----------+
63        | 1  |  1  | X'00' |  1   | Variable |    2     |
64        +----+-----+-------+------+----------+----------+
65    */
66	resp := []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
67    conn.Write(resp)
68
69    return nil
70
71
72}

(4)最後將信息進行轉發即可

代碼實現:

 1    wg := new(sync.WaitGroup)
 2    wg.Add(2)
 3
 4    go func() {
 5		defer wg.Done()
 6		defer dstServer.Close()
 7        io.Copy(dstServer, client)
 8    }()
 9
10    go func() {
11		defer wg.Done()
12        defer client.Close()
13        io.Copy(client, dstServer)
14    }()
15
16    wg.Wait()