实战案例proxy的理解

108 阅读3分钟

Go 语言实现的 SOCKS5 代理服务器连接处理

这段代码是一个 Go 语言编写的 SOCKS5 代理服务器的连接处理部分。它主要负责解析客户端发送的连接请求,并根据请求的类型和目标地址进行相应的处理。

代码解析

读取请求头

首先,代码通过 io.ReadFull 函数从 reader 中读取 4 个字节的数据到 buf 数组中。这 4 个字节包含了 SOCKS5 协议的版本号(ver)、命令(cmd)、保留字段(rsv)和地址类型(atyp)。

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 版本(socks5Ver),以及命令是否为支持的连接命令(cmdBind)。如果版本号或命令不匹配,代码会返回相应的错误信息。

if ver!= socks5Ver {
    return fmt.Errorf("not supported ver:%v", ver)
}
if cmd!= cmdBind {
    return fmt.Errorf("not supported cmd:%v", cmd)
}

解析目标地址

接下来,代码根据地址类型(atyp)来解析目标地址。如果地址类型是 IPv4 地址(atypeIPV4),则读取接下来的 4 个字节作为目标地址;如果地址类型是域名(atypeHOST),则先读取一个字节作为域名长度,然后读取相应长度的字节作为域名;如果地址类型是 IPv6 地址(atypeIPV6),则返回一个错误,表示当前不支持 IPv6 地址。

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")
}

读取目标端口

最后,代码读取接下来的 2 个字节作为目标端口,并将其转换为网络字节序。

_, err = io.ReadFull(reader, buf[:2])
if err!= nil {
    return fmt.Errorf("read port failed:%w", err)
}
port := binary.BigEndian.Uint16(buf[:2])

建立到目标服务器的连接

代码使用解析得到的目标地址和端口,通过 net.Dial 函数建立到目标服务器的连接。如果连接失败,代码会返回相应的错误信息。

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)

发送响应

代码构造一个响应数据包,包含 SOCKS5 协议版本号、响应状态(成功)、保留字段、地址类型和绑定地址及端口,然后将这个数据包发送给客户端。

_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err!= nil {
    return fmt.Errorf("write failed: %w", err)
}

数据传输

代码使用 context.WithCancel 创建一个可取消的上下文,并启动两个 goroutine 分别处理从客户端到目标服务器和从目标服务器到客户端的数据传输。当任何一个 goroutine 完成时,它会调用 cancel 函数来通知另一个 goroutine 停止。

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 代理服务器的连接处理逻辑,包括解析客户端请求、建立到目标服务器的连接、发送响应和处理数据传输。通过使用 Go 语言的并发特性,代码能够高效地处理多个客户端连接。