原理
通过下图可以看到,通过sock5建立代理服务器的步骤有三步:
- 协商阶段(auth阶段)
- 请求阶段
- relay阶段
实现
1 实现监听并收发数据
就是net包的listen函数和accept函数实现对连接的监听,然后用bufio中缓冲流读取连接中的数据,直接写回去
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("Acceptfailed %v", err)
continue
}
go process(client)
}
}
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
b, err := reader.ReadByte()
if err != nil {
break
}
_, err = conn.Write([]byte{b})
if err != nil {
break
}
}
}
2 auth阶段
auth阶段需要从连接读取字节 需要读取VER NMETHODS METHOD 中的信息,前两个都是一个字节长度,最后一个我们直接读完 这个阶段需要ver为0x05,methods 可为0x00(不需要认证),0x02(用户名密码认证)
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)
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}
3 请求阶段和relay阶段
请求阶段就是在认证过后先建立连接的阶段 需要的请求参数有 VER CMD RSV ATYP DST.ADDR DST.PORT 同样需要 ver 为0x05,cmd表示连接方式,atyp:0x01表示ipv4,0x03表示域名,0x04表示ipv6,dst.addr就是目标地址,dst.port就是目标端口 我们建立连接后,还需要开启两个goroutine进行读写操作,使用context包控制协程的完成情况。
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("not supported ver: %v", ver)
}
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 atypeIPV6:
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])
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)
}
return nil
}
总结
之前学习golang也只是一个很浅的状态吧,尤其是没用过熟悉的语言实现这些底层的东西,通过这次学习,进一步得到了提升