这是我参与「第五届青训营 」伴学笔记创作活动的第2天
Go 实践之Socks5代理解析
Socks5代理
- SOCKS是什么?
是一种网络传输协议,为Socket Secure的缩写,主要用于客户端与外网服务器之间通讯的中间传递。
当防火墙后的客户端要访问外部的服务器时,就跟SOCKS代理服务器连接。这个代理服务器控制客户端访问外网的资格,允许的话,就将客户端的请求发往外部的服务器。
这个协议最初由David Koblas开发,而后由NEC的Ying-Da Lee将其扩展到SOCKS4。最新协议是SOCKS5,与前一版本相比,增加支持UDP、验证,以及IPv6。
根据OSI模型,SOCKS是会话层的协议,位于表示层与传输层之间。 SOCKS协议不提供加密。
-
代理过程
视频中的图给大家截下来了,代理原理就是先连接代理服务器,然后让代理服务器去连接目的服务器,然后在访问网站的时候就是本地把req请求给代理服务器,代理服务器代替我们转交给目的服务器,然后再代替目的服务器转交res结果 -
代码
那么接下来我们来一步步分块解析
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\n", err)
continue
}
go process(client)
}
}
net.Listen("tcp", "127.0.0.1:1080")首先先监听本地1080端口server.Accept()等待客户端请求go process(client)用一个协程运行process
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
err := auth(reader, conn)
if err != nil {
log.Printf("clien %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
}
}
defer conn.Close()先defer把连接在该协程函数结束时关闭(好习惯)bufio.NewReader(conn)把创建一个Reader,用来方便读取传来的数据auth(reader, conn)验证(在后面)connect(reader, conn)连接(后面)log.Printf("clien %v auth failed:%v", conn.RemoteAddr(), err)出错了打印连接地址(略)
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
ver, err := reader.ReadByte()
if err != nil {}
if ver != socks5Ver {}
methodSize, err := reader.ReadByte()
if err != nil {}
method := make([]byte, methodSize)
_, err = io.ReadFull(reader, method)
if err != nil {}
log.Println("ver", ver, "method", method)
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {}
return nil
}
为了看着不那么长,决定把一些部分省略,仅保留核心
reader.ReadByte()读取一个字节io.ReadFull(reader, method)从reader(这里也就是连接)中读取长度为len(method)的数据
func connect(reader *bufio.Reader, conn net.Conn) (err error) {
//不是重点,略
switch atyp {}
_, err = io.ReadFull(reader, buf[:2])
if err != nil {}
port := binary.BigEndian.Uint16(buf[:2])
dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
if err != nil {}
defer dest.Close()
log.Println("dial", addr, port)
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {}
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
}
-
switch atyp {}里是根据类型给addr赋值,我这里略了,感兴趣的可以看我前面放的github链接 -
binary.BigEndian.Uint16(buf[:2])大端序二进制转Uint(大段序就是低地址端存放高位字节) -
context.WithCancel()生成一个独立的上下文,并返回一个cancel函数,一旦调用可以清除上下文 -
io.Copy(dest, reader)一个高效的copy,不用读入内存的copy
总之
虽然我没讲代码间的逻辑,只是把一些函数介绍一下,但看懂代码基本就能看懂流程了