(续接上文)
3. 如果是atypeIPV6类型,也可以读一个固定长度的字节。ipv6地址为128位,16个字节,那么为了方便,在做缓冲区的时候,可以直接创建16字节的缓冲区。下述代码中IPV6的情况我们暂不考虑,如果遇到,则输出IPv6: no supported yet。
到目前为止,报文的前5个字段已经读完了,仅剩下最后一个字段,最后一个字段DST.PORT为目标端口,固定2个字节。类比上述方法,我们创建两个字节大小的缓冲区,用于读取目标端口。为了节省空间,可以将上述4个字节的缓冲区切成2个字节的缓冲区,用一个切片语法即可。由于新缓冲区与之前的缓冲区,使用相同的数组底层,所以新的缓冲区可以直接读到端口号数据。我们可以用binary.BigEndian.Uint16()读取数据,返回的信息命名为port。之后我们将与这个port地址端口号去做连接。做实验时候可以将port打印输出,验证上述实验的正确性。在此我就不打印了,connect函数的源码都在下方。
根据协议,当我们接收到浏览器的这个行为之后,我们还要给予一个响应报文。
响应报文字段很多都用不上。我们将一些用不到的字段全部填充为0。比如 BND.ADDR、BND.PORT等。因此,我们将第一个字段填成 5 后面 IP(BND.ADDR)我们填充为 0 ,RSV 也为0。 ATYPE的话我们填一个最简单的一就是 IPV 4 后面 address 用 4 个字节,4个 0 ,最后端口两个0,拼成一个 byte数组直接写进去。
此时,connect函数已经算是初步完成,最后我们需要与真正的ip接口建立连接,那我们的代理就做好了。
与真正的服务建立tcp连接,可以使用net.Dial函数,之后还需要将该函数放入defer函数中,以便在函数结束时候关闭连接。
func connect(reader *bufio.Reader, conn net.Conn) (err error) {
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]
if ver != socks5Ver {
return fmt.Errorf("read header failed:%w", err)
}
if cmd != cmdBind {
return fmt.Errorf("not supported cmd:%v", ver)
}
addr := ""
switch atyp {
case atypIPV4:
_, 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 atypeTPV6:
return errors.New("IPv6: no supported yet")
default:
return errors.New("invalid atyp")
}
_, err = io.ReadFull(reader, buf[:2])
if err != nil {
return fmt.Errorf("read port failed:%w", err)
}
port := binary.BigEndian.Uint16(buf[:2])
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)
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
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
}