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

58 阅读2分钟

(续接上文)
按照协议,我们需要给浏览器返回一个数据包,数据包中包含上述过程我们已经读取到的信息。在这里,我们就直接创建一个了。包中第一个字节是版本号,第二个字节是METHOD。这里我们分别选择0x05和0x00.

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

3.请求阶段
在之前的process函数中,当鉴权成功后,我们会调用connect函数,下面我们就来介绍一下connect函数实现了什么功能。
connect函数与上述Auth函数具有相同的签名。

// +----+-----+-------+------+----------+----------+
// |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个字节

请求阶段中浏览器会发送一个报文,报文里面一共包含六个字段,如上图所示。第一个字段VER版本号,socks5的值为0x05,第二个字节CMD值为0x01表示CONNECT请求,第三个字节RSV是保留字段,值为0x00,第四个字节ATYP是目标地址类型,DST.ADDR的数据对应这个字段的类型。第五个字节DST.ADDR是一个可变长度的值。最后,第六个字节DST.PORT是目标端口,固定2个字节。
读取这个报文,我们可以先创建一个4个字节大小的缓冲区,并通过io.ReadFull对其填充,则我们可以一次性读取到4个字节。对于不同的字节,每个字节在一个数组里,可以直接获取。ver, cmd, atyp := buf[0], buf[1], buf[3]
针对不同类型的ATYP,有不同的处理方式。

  1. 如果是atypeIPV4类型的话,我们需要读取4个字节,并将这4个字节按照ip地址的方式进行填充,即X.X.X.X(其中每一个X为读取到的一个字节)。
  2. 如果是 atypeHOST 类型,那么我们先读一个字节 host 的长度,然后再 make 一个对应长度的一个字符串,然后再用 io.ReadFull(reader, host) 把它填充满,填充满之后把它转换成一个字符串即可。