Go 实践之Socks5代理解析 | 青训营笔记

197 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第2天

Go 实践之Socks5代理解析

Socks5代理

  1. SOCKS是什么?

是一种网络传输协议,为Socket Secure的缩写,主要用于客户端与外网服务器之间通讯的中间传递。
当防火墙后的客户端要访问外部的服务器时,就跟SOCKS代理服务器连接。这个代理服务器控制客户端访问外网的资格,允许的话,就将客户端的请求发往外部的服务器。
这个协议最初由David Koblas开发,而后由NEC的Ying-Da Lee将其扩展到SOCKS4。最新协议是SOCKS5,与前一版本相比,增加支持UDP、验证,以及IPv6。
根据OSI模型,SOCKS是会话层的协议,位于表示层与传输层之间。 SOCKS协议不提供加密。

  1. 代理过程
    视频中的图给大家截下来了,代理原理就是先连接代理服务器,然后让代理服务器去连接目的服务器,然后在访问网站的时候就是本地把req请求给代理服务器,代理服务器代替我们转交给目的服务器,然后再代替目的服务器转交res结果 image.png

  2. 代码

github.com/wangkechun/…

那么接下来我们来一步步分块解析

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(大段序就是低地址端存放高位字节)

    image.png

  • context.WithCancel()生成一个独立的上下文,并返回一个cancel函数,一旦调用可以清除上下文

  • io.Copy(dest, reader)一个高效的copy,不用读入内存的copy

总之

虽然我没讲代码间的逻辑,只是把一些函数介绍一下,但看懂代码基本就能看懂流程了