• 客户端连接协议头
    image.png

  • 服务端确认连接协议头
    image.png

  • 连接远端服务器
    image.png

  • 转发远端服务器响应到客户端
    image.png

程序入口

func main() {
	ls, err := net.Listen("tcp", ":1080")
	if err != nil {
		fmt.Printf("Listen failed: %v\n", err)
		return
	}

	for {
		conn, err := ls.Accept()
		if err != nil {
			fmt.Printf("Accept failed: %v\n", err)
			continue
		}
		go process(conn)
	}
}

处理逻辑

func process(conn net.Conn) {
	if err := Socks5Auth(conn); err != nil {
		fmt.Println("Auth error: ", err)
		conn.Close()
		return
	}
	target, err := Socks5Connect(conn)
	if err != nil {
		fmt.Println("Connect error: ", err)
		conn.Close()
		return
	}

	Socks5Forward(conn, target)
}

socks5校验逻辑

func Socks5Auth(conn net.Conn) (err error) {
	buf := make([]byte, 256)
	n, err := io.ReadFull(conn, buf[:2])
	if n != 2 {
		return errors.New("read header: " + err.Error())
	}

	ver, nMethods := int(buf[0]), int(buf[1])
	if ver != 5 {
		return errors.New("invalid version")
	}

	n, err = io.ReadFull(conn, buf[:nMethods])
	if n != nMethods {
		return errors.New("read methods: " + err.Error())
	}

	n, err = conn.Write([]byte{0x05, 0x00})
	if n != 2 {
		return errors.New("write rsp err: " + err.Error())
	}
	return nil
}

代理连接远端服务器

func Socks5Connect(conn net.Conn) (net.Conn, error) {
	buf := make([]byte, 256)
	n, err := io.ReadFull(conn, buf[:4])
	if n != 4 {
		return nil, errors.New("read header: " + err.Error())
	}
	ver, cmd, _, atype := buf[0], buf[1], buf[2], buf[3]
	if ver != 5 || cmd != 1 {
		return nil, errors.New("invalid ver/cmd")
	}

	addr := ""
	switch atype {
	case 1:
		n, err = io.ReadFull(conn, buf[:4])
		if n != 4 {
			return nil, errors.New("invalid ipv4: " + err.Error())
		}
		addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])

	case 3:
		n, err := io.ReadFull(conn, buf[:1])
		if n != 1 {
			return nil, errors.New("invalid hostname: " + err.Error())
		}
		addrLen := int(buf[0])
		n, err = io.ReadFull(conn, buf[:addrLen])
		if n != addrLen {
			return nil, errors.New("invalid hostname: " + err.Error())
		}
		addr = string(buf[:addrLen])

	case 4:
		return nil, errors.New("ipv6: no supported yet")

	default:
		return nil, errors.New("invalid atype")
	}

	n, err = io.ReadFull(conn, buf[:2])
	if n != 2 {
		return nil, errors.New("read port: " + err.Error())
	}
	port := binary.BigEndian.Uint16(buf[:2])

	destAddrPort := fmt.Sprintf("%s:%d", addr, port)
	dest, err := net.Dial("tcp", destAddrPort)
	if err != nil {
		return nil, errors.New("dial dst: " + err.Error())
	}

	n, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
	if err != nil {
		dest.Close()
		return nil, errors.New("write rsp: " + err.Error())
	}
	return dest, nil
}

代理转发响应
因为TCP全双工通信,所以通过两个goroutine来进行读写实现转发

func Socks5Forward(conn, target net.Conn) {
	fmt.Println("转发一条访问")
	forward := func(src, dest net.Conn) {
		defer src.Close()
		defer dest.Close()
		io.Copy(src, dest)
	}

	go forward(conn, target)
	go forward(target, conn)
}

测试和压测
可以通过下面方式发起代理请求

curl --proxy "socks5://127.0.0.1:1080" http://www.baidu.com
github.com/cnlh/benchmark
1.写个http服务

package main

import "github.com/gin-gonic/gin"

func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.String(200, "pong")
  })
  r.Run(":8080")
}

2.代理压测

benchmark -c 10000 -n 1000000 http://127.0.0.1:8080/ping