GOLANG实现Socks5代理(3)|青训营笔记

92 阅读2分钟

(续接上文)
当客户端与Socks5代理服务器TCP握手完成后,会进行一次初始化报文发送,来确认版本协议以及认证方式,也就是auth函数。函数输入只读流readerConn链接(TCP链接)
协商阶段:
浏览器给代理服务器发送一个报文,报文中包含三个字段:第一个字段协议版本号VERsocks5的固定版本号为0x05;第二个字段为鉴权方式的数目NMETHODS,也就是支持认证的方法数量;第三个字段是METHODS: 对应NMETHODSNMETHODS的值为多少,METHODS就有多少个字节。
METHODS 客户端支持的认证方式列表,每个方法占1字节。0x00表示不需要认证,0x01表示GSSAPI0x02表示用户名、密码认证;0x80-0xFE是为私人方法保留,0xFF表示无可接受的方法。

const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04
const socks5Ver = 0x05
// +----+----------+----------+
   // |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

auth函数中,我们首先要读取报文信息,通过ReadByte()reader中的信息先读取一个字节。如果读取信息失败,则返回读取失败信息。如果版本号VER不是socks5Ver:0x05,则返回版本信息错误,并关闭链接。如果成功,则用ReadByte()继续读取一个字节methodSize,失败则返回方法大小错误,若成功,则用methodSize去创建一个method的一个缓冲区,并通过使用io.ReadFull()将缓冲区填满。到目前为止,我们已经读到报文的全部三个字段,我们可以将上述得到的信息打印出来。

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)
   // +----+--------+
   // |VER | METHOD |
   // +----+--------+
   // | 1  |   1    |
   // +----+--------+
   _, err = conn.Write([]byte{socks5Ver, 0x00})
   if err != nil {
      return fmt.Errorf("write failed:%w", err)
   }
   return nil
}