SOCKS 是一種網絡傳輸協議,主要用於客戶端與外網服務器之間通訊的中間傳遞,SOCKS 是"SOCKetS"的縮寫。 SOCKS5 是 SOCKS4 的升級版,其主要多了鑑定、IPv6、UDP 支持。
SOCKS5 協議可以分為三個部分:
- (1) 協議版本及認證方式
- (2) 根據認證方式執行對應的認證
- (3) 請求信息
(1)協議版本及認證方式
創建與 SOCKS5 服務器的 TCP 連接後客戶端需要先發送請求來協議版本及認證方式,
| VER | NMETHODS | METHODS |
|---|---|---|
| 1 | 1 | 1-255 |
- VER 是 SOCKS 版本,這裡應該是 0x05;
- NMETHODS 是 METHODS 部分的長度;
- METHODS 是客戶端支持的認證方式列表,每個方法佔 1 字節。當前的定義是:
- 0x00 不需要認證
- 0x01 GSSAPI
- 0x02 用戶名、密碼認證
- 0x03 - 0x7F 由 IANA 分配(保留)
- 0x80 - 0xFE 為私人方法保留
- 0xFF 無可接受的方法
服務器回覆客戶端可用方法:
| VER | METHOD |
|---|---|
| 1 | 1 |
- 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 為私人方法保留
這裡就主要介紹用戶名、密碼認證。 在客戶端、服務端協商使用用戶名密碼認證後,客戶端發出用戶名密碼:
| 鑑定協議版本 | 用戶名長度 | 用戶名 | 密碼長度 | 密碼 |
|---|---|---|---|---|
| 1 | 1 | 動態 | 1 | 動態 |
服務器鑑定後發出如下回應:
| 鑑定協議版本 | 鑑定狀態 |
|---|---|
| 1 | 1 |
其中鑑定狀態 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)請求信息
認證結束後客戶端就可以發送請求信息。如果認證方法有特殊封裝要求,請求必須按照方法所定義的方式進行封裝解密,其原始格式如下:
| VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|---|---|---|---|---|---|
| 1 | 1 | 0x00 | 1 | 動態 | 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()