完整实现流程
启动一个服务
ln, err := net.Listen("tcp", ":8888")if err != nil {fmt.Println("Server listen fail")return}for {sc, err := ln.Accept()if err != nil {fmt.Println("Server accept fail")continue}go handleServer(sc)}
解析请求流程
确认版本和认证方式
服务端会接收到三个字节依次表示 VER、NMETHODS、METHODS,在得到连接请求时,需要响应客户端 VER 和采用的 METHOD,此处响应 5、0 表示无需认证
buf := make([]byte, 1024)// read VER, NMETHODS, METHODSif _, err := io.ReadFull(sc, buf[:2]); err != nil {return}nMethods := buf[1]if _, err := io.ReadFull(sc, buf[:nMethods]); err != nil {return}// write VER METHODif _, err := sc.Write([]byte{5, 0}); err != nil {return}
建立连接
客户端建立连接时会带上 VER、CMD、RSV、ATYP、DST.ADDR、DST.PORT(详见 socks5 协议原理),服务端需要响应客户端连接成功
// read VER CMD RSV ATYP DST.ADDR DST.PORTif _, err := io.ReadFull(sc, buf[:3]); err != nil {return}// reply success_, _ = sc.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0})
转发请求
首先需要从请求里面解析出 remoteAddr,
b := make(byte[], 1024)_, err := io.ReadFull(r, b[:1])if err != nil {return "", err}host, port := "", ""switch b[0] {case 3:_, err = io.ReadFull(r, b[1:2])if err != nil {return "", err}_, err = io.ReadFull(r, b[2:2+int(b[1])+2])host = string(b[2 : 2+int(b[1])])port = strconv.Itoa((int(b[2+int(b[1])]) << 8) | int(b[2+int(b[1])+1]))case 1:_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])host = net.IP(b[1 : 1+net.IPv4len]).String()port = strconv.Itoa((int(b[1+net.IPv4len]) << 8) | int(b[1+net.IPv4len+1]))case 4:_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])host = net.IP(b[1 : 1+net.IPv6len]).String()port = strconv.Itoa((int(b[1+net.IPv6len]) << 8) | int(b[1+net.IPv6len+1]))default:return "", errors.New("invalid aType")}address := net.JoinHostPort(host, port)return address, nil
然后跟 remoteAddr 建立连接
rc, err := net.Dial("tcp", addr)if err != nil {fmt.Println("server dial fail")return}defer rc.Close()
最后中转客户端和远端 remote 之间交互的字节流
go func() {io.Copy(right, left)}()io.Copy(left, right)
完整代码
package mainimport ("errors""fmt""io""net""strconv")func main() {ln, err := net.Listen("tcp", ":8888")if err != nil {fmt.Println("Server listen fail")return}for {sc, err := ln.Accept()if err != nil {fmt.Println("Server accept fail")continue}go handleServer(sc)}}func handleServer(sc net.Conn) {defer sc.Close()buf := make([]byte, 1024)// read VER, NMETHODS, METHODSif _, err := io.ReadFull(sc, buf[:2]); err != nil {return}nmethods := buf[1]if _, err := io.ReadFull(sc, buf[:nmethods]); err != nil {return}// write VER METHODif _, err := sc.Write([]byte{5, 0}); err != nil {return}// read VER CMD RSV ATYP DST.ADDR DST.PORTif _, err := io.ReadFull(sc, buf[:3]); err != nil {return}_, _ = sc.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0})b := make([]byte, 1024)addr, err := ReadAddr(sc, b)if err != nil {return}rc, err := net.Dial("tcp", addr)if err != nil {fmt.Println("server dial fail")return}defer rc.Close()if err = relay(sc, rc); err != nil {fmt.Printf("relay error: %v", err)return}}func ReadAddr(r io.Reader, b []byte) (string, error) {_, err := io.ReadFull(r, b[:1])if err != nil {return "", err}host, port := "", ""switch b[0] {case 3:_, err = io.ReadFull(r, b[1:2])if err != nil {return "", err}_, err = io.ReadFull(r, b[2:2+int(b[1])+2])host = string(b[2 : 2+int(b[1])])port = strconv.Itoa((int(b[2+int(b[1])]) << 8) | int(b[2+int(b[1])+1]))case 1:_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])host = net.IP(b[1 : 1+net.IPv4len]).String()port = strconv.Itoa((int(b[1+net.IPv4len]) << 8) | int(b[1+net.IPv4len+1]))case 4:_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])host = net.IP(b[1 : 1+net.IPv6len]).String()port = strconv.Itoa((int(b[1+net.IPv6len]) << 8) | int(b[1+net.IPv6len+1]))default:return "", errors.New("invalid aType")}address := net.JoinHostPort(host, port)return address, nil}func relay(left, right net.Conn) error {go func() {io.Copy(right, left)}()io.Copy(left, right)return nil}
上述代码运行成功之后,配合浏览器的插件 SwitchyOmega 即可实现代理
