package main
import (
"bufio"
"log"
"net"
)
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)
for {
b, err := reader.ReadByte()
if err != nil {
break
}
_, err = conn.Write([]byte{b})
if err != nil {
break
}
}
}
-
net.Listen()是一个函数,用于创建网络监听器。 -
第一个参数
"tcp"指定了监听器的网络协议,这里使用 TCP 协议。 -
第二个参数
"127.0.0.1:1080"指定了服务器监听的地址和端口。127.0.0.1是一个特殊的 IP 地址,表示本地回环地址(loopback address),用于在本机内部进行通信。:1080表示监听端口号是 1080。 -
server是一个net.Listener对象,表示创建的监听器。通过该对象,可以接受客户端的连接请求,并与客户端建立连接。
-
err是一个错误对象,表示在创建监听器时是否发生了错误。如果监听器创建成功,则err的值为nil;否则,它将包含一个非空的错误值,指示出现了什么问题。 -
for {}是一个无限循环,它会一直执行下去,直到程序被显式地中断或退出。
-
在每次循环中,
server.Accept()方法被调用,用于接受客户端的连接请求。这个方法会一直阻塞,直到有客户端连接到达。一旦有连接到达,该方法会返回一个net.Conn对象,表示与客户端建立的连接,以及可能的错误信息。 -
client是一个net.Conn对象,表示与新客户端建立的连接。 -
err是一个错误对象,表示在接受连接时是否发生了错误。如果接受连接成功,则err的值为nil;否则,它将包含一个非空的错误值,指示出现了什么问题。 -
在
if err != nil条件语句中,检查是否发生了错误。如果发生了错误,通过log.Printf()打印错误信息,并使用continue关键字跳过当前循环,继续下一次循环。 -
如果没有发生错误,表示成功接受了一个客户端的连接。然后,通过
go process(client)启动一个新的 Go 协程,调用process()函数来处理这个客户端连接。使用协程的方式可以并发地处理多个客户端连接,而不阻塞主循环。
package main
import (
"bufio"
"fmt"
"io"
"log"
"net"
)
const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 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
}
log.Println("auth success")
}
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
// +----+----------+----------+
// |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
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)
if err != nil {
return fmt.Errorf("read method failed:%w", err)
}
log.Println("ver", ver, "method", method)
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}
这段代码是一个简单的 SOCKS5 代理服务器的实现。以下是代码的逐行解释:
package main
import (
"bufio"
"fmt"
"io"
"log"
"net"
)
代码开始处导入了一些必要的包。
const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04
这里定义了一些常量,用于表示 SOCKS5 协议的各种数值。
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)
}
}
在 main() 函数中,代码首先创建一个 TCP 服务器监听器,并监听在地址 127.0.0.1:1080 上。如果监听器创建失败,则会触发一个 panic,导致程序崩溃。然后进入一个无限循环,通过 server.Accept() 方法接受客户端的连接请求。每当有新的客户端连接到达时,会创建一个新的 net.Conn 对象来表示连接,并在一个单独的 Go 协程中调用 process() 函数来处理连接。
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
}
log.Println("auth success")
}
process() 函数用于处理客户端连接。函数开始时通过 defer 关键字延迟关闭连接,以确保在函数结束时关闭连接。然后创建一个 bufio.Reader 对象,用于从连接中读取数据。接下来调用 auth() 函数进行认证,如果认证失败,则通过日志记录错误信息,并返回。如果认证成功,则打印认证成功的消息。
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)
if err != nil {
return fmt.Errorf("read method failed:%w", err)
}
log.Println("ver", ver, "method", method)
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}
auth() 函数用于进行 SOCKS5 认证过程。首先从 reader 中读取协议版本号 ver,如果读取协议版本号 ver 失败,会返回一个包含错误信息的 fmt.Errorf 对象。
接下来,代码检查协议版本号 ver 是否为预期的 SOCKS5 版本号 socks5Ver,如果不匹配,则返回一个错误对象。
然后,从 reader 中读取方法数量 methodSize,如果读取失败,则返回一个错误对象。
接着,创建一个长度为 methodSize 的字节数组 method,并从 reader 中读取方法数据到该数组中。
接下来,代码打印协议版本号和方法数据。
然后,通过 conn.Write() 方法向客户端发送认证响应,即回复协议版本号和方法选择结果。这里直接回复使用无需认证的方法。
最后,如果发送响应时发生错误,会返回一个错误对象;否则,返回 nil 表示认证成功。
整体上,这段代码实现了 SOCKS5 代理服务器中的认证阶段。它读取客户端发送的协议版本号和支持的认证方法列表,并根据规范进行处理和回复。
关于'process'函数的解释:这段代码定义了一个名为 process 的函数,用于处理与客户端的连接。
函数签名为 func process(conn net.Conn),表示该函数接受一个 net.Conn 类型的参数 conn,表示与客户端建立的连接。
以下是代码的执行流程和解释:
-
延迟关闭连接:
- 使用
defer conn.Close()语句,将连接的关闭操作延迟到函数执行结束时执行。这样可以确保在处理完客户端请求后正确地关闭连接,释放资源。
- 使用
-
创建读取器:
- 使用
bufio.NewReader(conn)方法创建一个bufio.Reader对象reader,用于从连接中读取数据。
- 使用
-
进行认证:
- 调用
auth(reader, conn)函数来进行 SOCKS5 协议的认证阶段。 - 将
reader和conn作为参数传递给auth函数。 - 将
auth函数返回的错误赋值给变量err。
- 调用
-
处理认证结果:
- 如果
err不为nil,表示认证失败。- 使用
log.Printf()方法记录认证失败的信息,包括客户端地址和错误信息。 - 函数提前返回,结束函数执行。
- 使用
- 如果
err为nil,表示认证成功。- 使用
log.Println()方法打印认证成功的信息。
- 使用
- 如果
总结来说,process() 函数用于处理与客户端的连接。它在函数开始时创建了一个读取器,并调用 auth() 函数进行认证。根据认证的结果,它会打印相应的日志信息,表示认证成功或失败。同时,通过使用 defer 关键字延迟关闭连接,确保连接在函数执行结束时被正确关闭。
关于auth函数的解释:这段代码定义了一个名为 process 的函数,用于处理与客户端的连接。
函数签名为 func process(conn net.Conn),表示该函数接受一个 net.Conn 类型的参数 conn,表示与客户端建立的连接。
以下是代码的执行流程和解释:
-
延迟关闭连接:
- 使用
defer conn.Close()语句,将连接的关闭操作延迟到函数执行结束时执行。这样可以确保在处理完客户端请求后正确地关闭连接,释放资源。
- 使用
-
创建读取器:
- 使用
bufio.NewReader(conn)方法创建一个bufio.Reader对象reader,用于从连接中读取数据。
- 使用
-
进行认证:
- 调用
auth(reader, conn)函数来进行 SOCKS5 协议的认证阶段。 - 将
reader和conn作为参数传递给auth函数。 - 将
auth函数返回的错误赋值给变量err。
- 调用
-
处理认证结果:
- 如果
err不为nil,表示认证失败。- 使用
log.Printf()方法记录认证失败的信息,包括客户端地址和错误信息。 - 函数提前返回,结束函数执行。
- 使用
- 如果
err为nil,表示认证成功。- 使用
log.Println()方法打印认证成功的信息。
- 使用
- 如果
总结来说,process() 函数用于处理与客户端的连接。它在函数开始时创建了一个读取器,并调用 auth() 函数进行认证。根据认证的结果,它会打印相应的日志信息,表示认证成功或失败。同时,通过使用 defer 关键字延迟关闭连接,确保连接在函数执行结束时被正确关闭。
关于auth函数的解释:这段代码是一个名为 auth 的函数,用于处理 SOCKS5 协议中的认证阶段。
函数签名为 func auth(reader *bufio.Reader, conn net.Conn) (err error),表示该函数接受一个 bufio.Reader 类型的参数 reader 和一个 net.Conn 类型的参数 conn,并返回一个 error 类型的结果。
下面是代码的执行流程和解释:
-
读取协议版本号
ver:- 使用
reader.ReadByte()方法从reader中读取一个字节作为协议版本号ver。 - 如果读取过程中发生错误
err,函数会返回一个带有错误信息的fmt.Errorf对象,使用%w占位符将原始错误信息包装在其中。
- 使用
-
检查协议版本号
ver:- 如果读取的协议版本号
ver不等于预定义的 SOCKS5 版本号socks5Ver,则表示不支持该协议版本。 - 函数会返回一个带有错误信息的
fmt.Errorf对象,指示不支持的协议版本。
- 如果读取的协议版本号
-
读取方法数量
methodSize:- 使用
reader.ReadByte()方法从reader中读取一个字节作为方法数量methodSize。 - 如果读取过程中发生错误
err,函数会返回一个带有错误信息的fmt.Errorf对象,使用%w占位符将原始错误信息包装在其中。
- 使用
-
读取方法数据
method:- 创建一个长度为
methodSize的字节数组method,用于存储方法数据。 - 使用
io.ReadFull(reader, method)方法从reader中读取完整的方法数据,填充到method数组中。 - 如果读取过程中发生错误
err,函数会返回一个带有错误信息的fmt.Errorf对象,使用%w占位符将原始错误信息包装在其中。
- 创建一个长度为
-
打印协议版本号和方法数据:
- 使用
log.Println()方法打印协议版本号ver和方法数据method。
- 使用
-
发送认证响应:
- 使用
conn.Write([]byte{socks5Ver, 0x00})方法向客户端发送认证响应。 - 认证响应包括回复的协议版本号和选择的认证方法,这里回复的是 SOCKS5 版本号和无需认证的方法。
- 如果发送过程中发生错误
err,函数会返回一个带有错误信息的fmt.Errorf对象,使用%w占位符将原始错误信息包装在其中。
- 使用
-
返回结果:
- 如果函数的执行过程中没有发生错误,函数会返回
nil,表示认证成功。 - 如果发生了错误,函数会返回一个带有错误信息的
fmt.Errorf对象。
- 如果函数的执行过程中没有发生错误,函数会返回
package main
import (
"bufio"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
)
const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 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 | 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
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)
if err != nil {
return fmt.Errorf("read method failed:%w", err)
}
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
_, 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) {
// +----+-----+-------+------+----------+----------+
// |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个字节
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]
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
if cmd != cmdBind {
return fmt.Errorf("not supported cmd:%v", cmd)
}
addr := ""
switch atyp {
case atypeIPV4:
_, 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])
log.Println("dial", addr, port)
// +----+-----+-------+------+----------+----------+
// |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
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
return fmt.Errorf("write failed: %w", err)
}
return nil
}
以下是您提供的代码逐行的解释:
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)
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
}
ver, err := reader.ReadByte(): 从输入流reader中读取一个字节,赋值给变量ver,同时检查是否有错误发生。if ver != socks5Ver: 检查读取的版本号ver是否与socks5Ver相等,如果不相等,则返回一个错误,指示不支持的版本号。methodSize, err := reader.ReadByte(): 从输入流reader中读取一个字节,赋值给变量methodSize,同时检查是否有错误发生。method := make([]byte, methodSize): 创建一个字节切片method,其长度为methodSize。_, err = io.ReadFull(reader, method): 从输入流reader中读取methodSize个字节,并将其存储到method中,同时检查是否有错误发生。_, err = conn.Write([]byte{socks5Ver, 0x00}): 向连接conn写入一个字节切片,该切片包含版本号socks5Ver和方法字节 0x00,同时检查是否有错误发生。
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]
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
if cmd != cmdBind {
return fmt.Errorf("not supported cmd:%v", cmd)
}
addr := ""
switch atyp {
case atypeIPV4:
_, 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])
log.Println("dial", addr, port)
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
return fmt.Errorf("write failed: %w", err)
}
return nil
}
buf := make([]byte, 4): 创建一个长度为4的字节切片buf。_, err = io.ReadFull(reader, buf): 从输入流reader中读取4个字节,并将其存储到buf中,同时检查是否有错误发生。ver, cmd, atyp := buf[0], buf[1], buf[3]: 将buf中的第一个字节赋值给ver,第二个字节赋值给cmd,第四个字节赋值给atyp。if ver != socks5Ver: 检查读取的版本号ver是否与socks5Ver相等,如果不相等,则返回一个错误,指示不支持的版本号。if cmd != cmdBind: 检查读取的命令字节cmd是否与cmdBind相等,如果不相等,则返回一个错误,指示不支持的命令。switch atyp: 根据目标地址类型atyp的值进行分支判断。case atypeIPV4: 如果atyp的值为atypeIPV4,则执行以下操作:_, err = io.ReadFull(reader, buf): 从输入流reader中读取4个字节,并将其存储到buf中,同时检查是否有错误发生。addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3]): 将读取的4个字节解析为 IPv4 地址格式,并使用fmt.Sprintf将其转换为字符串形式,存储到addr中。
case atypeHOST: 如果atyp的值为atypeHOST,则执行以下操作:hostSize, err := reader.ReadByte(): 从输入流reader中读取一个字节,赋值给hostSize,同时检查是否有错误发生。host := make([]byte, hostSize): 创建一个长度为hostSize的字节切片host。_, err = io.ReadFull(reader, host): 从输入流reader中读取hostSize个字节,并将其存储到host中,同时检查是否有错误发生。addr = string(host): 将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]) log.Println("dial", addr, port) _, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}) if err != nil { return fmt.Errorf("write failed: %w", err) } return nil }
- case atypeIPV6`: 如果 `atyp` 的值为 `atypeIPV6`,则返回一个错误,指示不支持 IPv6 地址类型。
- `default`: 如果 `atyp` 的值不匹配任何已知类型,则返回一个错误,指示无效的地址类型。
- `_, err = io.ReadFull(reader, buf[:2])`: 从输入流 `reader` 中读取2个字节,并将其存储到 `buf` 的前两个位置上,同时检查是否有错误发生。
- `port := binary.BigEndian.Uint16(buf[:2])`: 将 `buf` 的前两个字节解析为大端字节序的 16 位无符号整数,并将其存储到变量 `port` 中。
- `log.Println("dial", addr, port)`: 打印日志信息,表示正在连接的目标地址和端口。
- `_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})`: 向连接 `conn` 写入一个字节切片,该切片包含 SOCKS 版本号、回复字段、保留字段、地址类型和绑定的地址信息。
- `return nil`: 返回 `nil`,表示连接处理成功。
```go
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
}
go relay(reader, conn)
}
func relay(reader io.Reader, writer io.Writer) {
_, err := io.Copy(writer, reader)
if err != nil {
log.Printf("relay failed:%v", err)
}
}
func process(conn net.Conn): 定义了处理连接的函数,接受一个net.Conn参数,表示与客户端建立的连接。defer conn.Close(): 使用defer关键字在函数结束时关闭连接,确保连接资源得到正确释放。reader := bufio.NewReader(conn): 创建一个bufio.Reader对象,用于从连接中读取数据。err := auth(reader, conn): 调用auth函数进行身份验证,将reader和连接conn作为参数传递,并将返回的错误存储在err变量中。if err != nil: 检查身份验证过程中是否发生错误。- 如果发生错误,打印错误日志并返回,表示身份验证失败。
err = connect(reader, conn): 调用connect函数进行连接处理,将reader和连接conn作为参数传递,并将返回的错误存储在err变量中。if err != nil: 检查连接处理过程中是否发生错误。- 如果发生错误,打印错误日志并返回,表示连接处理失败。
go relay(reader, conn): 启动一个新的 goroutine,调用relay函数处理数据中继。将reader和连接conn作为参数传递。func relay(reader io.Reader, writer io.Writer): 定义了数据中继的函数,接受一个实现了io.Reader接口和一个实现了io.Writer接口的参数。_, err := io.Copy(writer, reader): 使用io.Copy函数将从reader中读取的数据复制到writer中,直到读取到 EOF(文件末尾)或发生错误。if err != nil: 检查数据中继过程中是否发生错误。- 如果发生错误,打印错误日志。
- 函数执行完毕,连接处理结束。数据中继会在单独的 goroutine 中持续进行,直到发生错误或连接关闭。
func main() {
server, err := net.Listen("tcp", "127.0.0.1:1080")
if err != nil {
panic(err)
}
defer server.Close()
log.Println("SOCKS5 server started")
for {
client, err := server.Accept()
if err != nil {
log.Printf("Accept failed: %v", err)
continue
}
go process(client)
}
}
func main(): 主函数,程序入口。server, err := net.Listen("tcp", "127.0.0.1:1080"): 创建一个 TCP 服务器,监听本地的 1080 端口。if err != nil: 检查是否有错误发生。- 如果有错误,使用
panic函数抛出错误并终止程序执行。
- 如果有错误,使用
defer server.Close(): 使用defer关键字在函数结束时关闭服务器,确保服务器资源得到正确释放。log.Println("SOCKS5 server started"): 打印日志,表示 SOCKS5 服务器已启动。for { ... }: 进入无限循环,接受客户端连接并处理。client, err := server.Accept(): 等待客户端连接并返回与客户端建立的连接。if err != nil: 检查是否有错误发生。- 如果有错误,打印错误日志并继续等待下一个连接。
go process(client): 在新的 goroutine 中调用process函数处理客户端连接。- 循环会继续执行,等待下一个客户端连接。
package main
import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
)
const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 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 | 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
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)
if err != nil {
return fmt.Errorf("read method failed:%w", err)
}
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
_, 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) {
// +----+-----+-------+------+----------+----------+
// |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个字节
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]
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
if cmd != cmdBind {
return fmt.Errorf("not supported cmd:%v", cmd)
}
addr := ""
switch atyp {
case atypeIPV4:
_, 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, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
if err != nil {
return fmt.Errorf("dial dst failed:%w", err)
}
defer dest.Close()
log.Println("dial", addr, port)
// +----+-----+-------+------+----------+----------+
// |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
_, err = conn.Write([]byte{0x05, 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()
}()
go func() {
_, _ = io.Copy(conn, dest)
cancel()
}()
<-ctx.Done()
return nil
}
这段代码是一个基于 SOCKS5 协议的代理服务器实现。它接受来自客户端的连接,并处理客户端的请求。
首先,代码定义了一些常量,包括协议版本号、命令类型、地址类型等。
main 函数是程序的入口函数,它通过 net.Listen 监听在本地的 1080 端口上。然后使用一个无限循环来接受客户端的连接,对每个连接创建一个 goroutine 来处理。
process 函数用于处理客户端连接。它首先调用 auth 函数进行客户端认证,然后调用 connect 函数建立与目标服务器的连接。
auth 函数用于处理客户端认证过程。根据 SOCKS5 协议规定,客户端发送一个包含认证方法的请求,服务器需要回复确认的认证方法。函数首先从客户端读取协议版本号,并检查其是否为 SOCKS5 版本。然后读取客户端支持的认证方法数量和方法列表。根据协议规定,服务器回复一个字节的数据,其中包含协议版本号和选择的认证方法。
connect 函数用于处理客户端连接请求。根据 SOCKS5 协议规定,客户端发送一个包含连接信息的请求,服务器需要解析请求中的目标地址和端口,并与目标服务器建立连接。函数首先从客户端读取请求的头部数据,包括协议版本号、命令类型和地址类型等信息。然后根据地址类型的不同,解析目标地址和端口。根据协议规定,服务器回复一个字节的数据,其中包含协议版本号、响应码等信息。最后,函数通过 net.Dial 建立与目标服务器的连接,并将客户端与目标服务器之间的数据进行转发。
在 connect 函数中,还使用了两个 goroutine 来实现数据的转发。一个 goroutine 将客户端的数据拷贝到目标服务器,另一个 goroutine 将目标服务器的数据拷贝到客户端。同时,使用了 context 和 cancel 函数来控制数据转发的结束。
总体来说,这段代码实现了一个简单的 SOCKS5 代理服务器,可以转发客户端和目标服务器之间的数据流量。