这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
demo3. Proxy
0. Socks5
Socks5: SOCKS - Wikipedia
1. 客户端发送认证信息
SOCKS5 比 SOCKS4a 多了IPv6、UDP等支持。创建与 SOCKS5 服务器的 TCP 连接后客户端需要先发送请求来确认协议版本及认证方式,格式为(以字节为单位):
| VER | NMETHODS | METHODS |
|---|---|---|
| 1 | 1 | 1 to 255 |
- VER 是 SOCKS 版本,这里应该是 0x05
- NMETHODS 是 METHODS 部分的长度;
- METHODS 是客户端支持的认证方式列表,每个方法占 1 字节。例如:
- 0x00 不需要认证
- 0x02 用户名、密码认证
2. 服务端返回认证信息
服务器从客户端提供的方法中选择一个并通过以下消息通知客户端(以字节为单位):
| VER | METHOD |
|---|---|
| 1 | 1 |
- VER 是 SOCKS 版本,这里应该是 0x05
- METHOD 是服务端选中的方法。如果返回 0xFF 表示没有一个认证方法被选中,客户端需要关闭连接
The values currently defined for METHOD are:
- X'00' NO AUTHENTICATION REQUIRED
- X'01' GSSAPI
- X'02' USERNAME/PASSWORD
- X'03' to X'7F' IANA ASSIGNED
- X'80' to X'FE' RESERVED FOR PRIVATE METHODS
- X'FF' NO ACCEPTABLE METHODS
3. 客户端发送请求信息
认证结束后客户端就可以发送请求信息。如果认证方法有特殊封装要求,请求必须按照方法所定义的方式进行封装。
SOCKS5 请求格式(以字节为单位):
| 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 地址,DST.ADDR 部分 4 字节长度
- 0x03 域名,DST.ADDR 部分第一个字节为域名长度,DST.ADDR 剩余的内容为域名
- 0x04 IPv6 地址,16 个字节长度
- DST.ADDR 目的地址
- DST.PORT 网络字节序表示的目的端口
4. 服务端回应请求信息
服务器按以下格式回应客户端的请求(以字节为单位):
| VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
|---|---|---|---|---|---|
| 1 | 1 | X'00' | 1 | Variable | 2 |
- VER 是 SOCKS 版本,这里应该是 0x05
- REP 应答字段,例如:
- 0x00 表示成功
- 0x01 SOCKS 服务器连接失败
- RSV 0x00,保留
- ATYP 指明 BND.ADDR 类型,内容同上
- BND.ADDR 服务器绑定的地址
- BND.PORT 服务器绑定的端口
1. Socks5 服务端
-
启动服务
// 启动一个监听 localhost:1080 的 net 服务 server, err := net.Listen("tcp", "127.0.0.1:1080") -
监听服务
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) // 新建一个 bufio.Reader 流 err := auth(reader, conn) // 认证 socks5 连接 err = connect(reader, conn) // 连接 socks5 客户端 } -
认证 socks5 连接
func auth(reader *bufio.Reader, conn net.Conn) (err error) { ver, err := reader.ReadByte() // 从流中读取 1 字节 if ver != 0x05 { // 验证该字节是否等于 0x05 return fmt.Errorf("not supported ver:%v", ver) } methodSize, err := reader.ReadByte() // 再读取 1 字节, 该字节表示 method 的大小 method := make([]byte, methodSize) // 建立一个切片,大小为 methodSize _, err = io.ReadFull(reader, method) // 从流中读取数据,直到填满 method 切片 _, err = conn.Write([]byte{0x05, 0x00}) // 向连接中写入两字节数据,第一个字节表示 socks 版本,第二个字节表示认证方式 return nil } -
连接 socks5 客户端
func connect(reader *bufio.Reader, conn net.Conn) (err error) { buf := make([]byte, 4) // 建立 4 字节的切片作为缓冲 _, err = io.ReadFull(reader, buf) // 从流中读取 4 字节 ver, cmd, atyp := buf[0], buf[1], buf[3] // 读取该缓冲中的有效信息 if ver != 0x05 { // 验证是否为 socks5 return fmt.Errorf("not supported ver:%v", ver) } if cmd != 0x01 { // 是否为 connect 请求 return fmt.Errorf("not supported cmd:%v", ver) } addr := "" switch atyp { } // 根据 atyp 处理地址信息 _, err = io.ReadFull(reader, buf[:2]) // 从流中读入 2 字节 port := binary.BigEndian.Uint16(buf[:2]) // 将这 2 字节转为 uint16 表示端口信息 dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port)) // 与主机建立连接 defer dest.Close() 在函数结束时关闭 net 连接 _, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}) // 将 socks5 服务端的应答信息返回给客户端 ctx, cancel := context.WithCancel(context.Background()) // 建立上下文信息,相当于 wait := sync.WaitGroup{} defer cancel() go func() { _, _ = io.Copy(dest, reader) // 从主机拷贝数据到服务端 cancel() // 通知主进程该协程的结束 }() go func() { _, _ = io.Copy(conn, dest) // 从服务端拷贝数据给客户端 cancel() }() <-ctx.Done() // 用于阻塞主进程的关闭,等待协程执行的完成 return nil