这个是NX的Socks5的书写,实不相瞒,写完后Any告诉我这个都没什么用,配网啥的都是靠工具来配的,看个乐子就行

原理

RFC 1928 - SOCKS 5 协议中文文档「译」 - 光韵流转 (quarkay.com)

  1. 认证

    客户端向服务端发送

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // +----+----------+----------+
    // |VER | NMETHODS | METHODS |
    // +----+----------+----------+
    // | 1 | 1 | 1 to 255 |
    // +----+----------+----------+
    // VER: 协议版本,socks5为0x05
    // NMETHODS: 支持认证的方法数量
    // METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
    // X’00’ NO AUTHENTICATION REQUIRED
    // X’02’ USERNAME/PASSWORD

    服务端响应

    1
    2
    3
    4
    5
    // +----+--------+
    // |VER | METHOD |
    // +----+--------+
    // | 1 | 1 |
    // +----+--------+
  2. 连接

    客户端向服务端请求连接

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // +----+-----+-------+------+----------+----------+
    // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
    // +----+-----+-------+------+----------+----------+
    // | 1 | 1 | X'00' | 1 | Variable | 2 |
    // +----+-----+-------+------+----------+----------+
    // VER 版本号,socks5的值为0x05
    // CMD 0x01表示CONNECT请求
    // RSV 保留字段,值为0x00
    // ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
    // 0x01表示IPv4地址,DST.ADDR为4个字节
    // 0x03表示域名,DST.ADDR是一个可变长度的域名
    // DST.ADDR 一个可变长度的值
    // DST.PORT 目标端口,固定2个字节

    服务端响应连接

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // +----+-----+-------+------+----------+----------+
    // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
    // +----+-----+-------+------+----------+----------+
    // | 1 | 1 | X'00' | 1 | Variable | 2 |
    // +----+-----+-------+------+----------+----------+
    // VER socks版本,这里为0x05
    // REP Relay field,内容取值如下 X’00’ succeeded
    // RSV 保留字段
    // ATYPE 地址类型
    // BND.ADDR 服务绑定的地址
    // BND.PORT 服务绑定的端口DST.PORT

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package main

import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

func main() {
server, err := net.Listen("tcp", "127.0.0.1:1080")
if err != nil {
panic(err)
}
for {
client, err := server.Accept()
if err != nil {
log.Printf("Accept failed %v", err)
continue
}
go process(client)
}
}

func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn) // 获得数据流
err := auth(reader, conn) // 进行认证
if err != nil {
log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
return
}
err = connect(reader, conn) // 进行连接
if err != nil {
log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
return
}
}

// 认证过程
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
// 字段为一个字节类型的
ver, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read ver failed:%w", err)
}
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
methodSize, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read methodSize failed:%w", err)
}
method := make([]byte, methodSize)
_, err = io.ReadFull(reader, method) // io.ReadFull 会读取指定长度的数据,如果读取的数据长度不够,会返回错误
if err != nil {
return fmt.Errorf("read method failed:%w", err)
}

// 上面的所有代码都是在测试这个连接是否成立
// 如果连接成立,就要返回一个认证的结果
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}

func connect(reader *bufio.Reader, conn net.Conn) (err error) {
buf := make([]byte, 4)
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read header failed:%w", err)
}
ver, cmd, atyp := buf[0], buf[1], buf[3]
// 是不是使用SOCKS5的连接
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
// 连接是否成立
if cmd != cmdBind {
return fmt.Errorf("not supported cmd:%v", ver)
}
// 获取目标域名
addr := ""
switch atyp {
case atypIPV4:
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read atyp failed:%w", err)
}
addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
case atypeHOST:
hostSize, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read hostSize failed:%w", err)
}
host := make([]byte, hostSize)
_, err = io.ReadFull(reader, host)
if err != nil {
return fmt.Errorf("read host failed:%w", err)
}
addr = string(host)
case atypeIPV6:
return errors.New("IPv6: no supported yet")
default:
return errors.New("invalid atyp")
}
_, err = io.ReadFull(reader, buf[:2])
if err != nil {
return fmt.Errorf("read port failed:%w", err)
}
// 获取目标端口
port := binary.BigEndian.Uint16(buf[:2])
// 连接响应的地址
// 可以发现dest是一个net.Conn类型的,这个连接是Socks客户端到响应目标地址的连接
// 而之前那个conn是个人浏览器/客户端到Socks服务器的连接
dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
if err != nil {
return fmt.Errorf("dial failed:%w", err)
}
defer dest.Close()
fmt.Printf("connect to %v:%v\n", addr, port)

_, err = conn.Write([]byte{socks5Ver, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 启动一个写成,将客户端接受的数据副本发送给目标服务器
go func() {
_, _ = io.Copy(dest, reader)
cancel()
}()
// 启动一个协程,将目标服务器接受的数据副指挥Socks服务器
go func() {
_, _ = io.Copy(conn, dest)
cancel()
}()
// 等待上下文被取消,意味着数据已经完全被复制
<-ctx.Done()
return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ curl  --socks5 127.0.0.1:1080 -v http://www.qq.com
* processing: http://www.qq.com
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 127.0.0.1:1080...
* Connected to 127.0.0.1 (127.0.0.1) port 1080
* SOCKS5 connect to 101.91.42.232:80 (locally resolved)
* SOCKS5 request granted.
* Connected to 127.0.0.1 (127.0.0.1) port 1080
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0> GET / HTTP/1.1
> Host: www.qq.com
> User-Agent: curl/8.2.1
> Accept: */*
>
< HTTP/1.1 302 Moved Temporarily
< Server: stgw
< Date: Mon, 18 Mar 2024 03:30:10 GMT
< Content-Type: text/html
< Content-Length: 137
< Connection: keep-alive
< Location: https://www.qq.com/
<
{ [137 bytes data]
100 137 100 137 0 0 279 0 --:--:-- --:--:-- --:--:-- 279<html>
<head><title>302 Found</title></head>
<body>
<center><h1>302 Found</h1></center>
<hr><center>stgw</center>
</body>
</html>

* Connection #0 to host 127.0.0.1 left intact

『字节青训营-3rd』L1:Go 语言上手 - 基础语言 | NX の 博客 (nickxu.me)

主要是Any说没啥用,就不讲解了,随便看看吧