From fbf694f5cda8481c6ef27d4fa8fe4b9621b087e7 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Fri, 12 Feb 2021 22:11:07 +0800 Subject: [PATCH] socks5: fix an issue in udp handling with auth (#219) --- .github/workflows/build.yml | 16 ++--- .github/workflows/release.yml | 3 +- proxy/socks5/client.go | 123 ++++++++++++---------------------- proxy/socks5/server.go | 40 +++++------ systemd/glider@.service | 1 - 5 files changed, 68 insertions(+), 115 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b82b19d..3e9fa67 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,18 +6,14 @@ jobs: test: name: Test runs-on: ubuntu-latest - strategy: - matrix: - go-version: [ '1.16.0-rc1' ] - steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: - stable: '!contains(${{ matrix.go-version }}, "beta") && !contains(${{ matrix.go-version }}, "rc")' - go-version: ${{ matrix.go-version }} + stable: false + go-version: 1.16.0-rc1 - name: Go Env run: go env - name: Test @@ -27,18 +23,14 @@ jobs: name: Build runs-on: ubuntu-latest needs: [test] - strategy: - matrix: - go-version: [ '1.16.0-rc1' ] - steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: - stable: '!contains(${{ matrix.go-version }}, "beta") && !contains(${{ matrix.go-version }}, "rc")' - go-version: ${{ matrix.go-version }} + stable: false + go-version: 1.16.0-rc1 - name: Go Env run: go env - name: Build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 039a9a0..10216be 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,8 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.16.x + stable: false + go-version: 1.16.0-rc1 - name: Go Env run: go env - name: Run GoReleaser diff --git a/proxy/socks5/client.go b/proxy/socks5/client.go index ddd20bd..a452e17 100644 --- a/proxy/socks5/client.go +++ b/proxy/socks5/client.go @@ -27,6 +27,21 @@ func (s *Socks5) Addr() string { // Dial connects to the address addr on the network net via the SOCKS5 proxy. func (s *Socks5) Dial(network, addr string) (net.Conn, error) { + c, err := s.dial(network, s.addr) + if err != nil { + log.F("[socks5]: dial to %s error: %s", s.addr, err) + return nil, err + } + + if _, err := s.connect(c, addr, socks.CmdConnect); err != nil { + c.Close() + return nil, err + } + + return c, nil +} + +func (s *Socks5) dial(network, addr string) (net.Conn, error) { switch network { case "tcp", "tcp6", "tcp4": default: @@ -39,53 +54,26 @@ func (s *Socks5) Dial(network, addr string) (net.Conn, error) { return nil, err } - if err := s.connect(c, addr); err != nil { - c.Close() - return nil, err - } - return c, nil } // DialUDP connects to the given address via the proxy. func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) { - c, err := s.dialer.Dial("tcp", s.addr) + c, err := s.dial("tcp", s.addr) if err != nil { log.F("[socks5] dialudp dial tcp to %s error: %s", s.addr, err) return nil, nil, err } - // send VER, NMETHODS, METHODS - c.Write([]byte{Version, 1, 0}) + var uAddr socks.Addr + if uAddr, err = s.connect(c, addr, socks.CmdUDPAssociate); err != nil { + c.Close() + return nil, nil, err + } buf := pool.GetBuffer(socks.MaxAddrLen) defer pool.PutBuffer(buf) - // read VER METHOD - if _, err := io.ReadFull(c, buf[:2]); err != nil { - return nil, nil, err - } - - dstAddr := socks.ParseAddr(addr) - // write VER CMD RSV ATYP DST.ADDR DST.PORT - c.Write(append([]byte{Version, socks.CmdUDPAssociate, 0}, dstAddr...)) - - // read VER REP RSV ATYP BND.ADDR BND.PORT - if _, err := io.ReadFull(c, buf[:3]); err != nil { - return nil, nil, err - } - - rep := buf[1] - if rep != 0 { - log.F("[socks5] server reply: %d, not succeeded", rep) - return nil, nil, errors.New("server connect failed") - } - - uAddr, err := socks.ReadAddrBuf(c, buf) - if err != nil { - return nil, nil, err - } - var uAddress string h, p, _ := net.SplitHostPort(uAddr.String()) // if returned bind ip is unspecified @@ -103,25 +91,25 @@ func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.A return nil, nil, err } - pkc := NewPktConn(pc, nextHop, dstAddr, true, c) + pkc := NewPktConn(pc, nextHop, socks.ParseAddr(addr), true, c) return pkc, nextHop, err } // connect takes an existing connection to a socks5 proxy server, // and commands the server to extend that connection to target, // which must be a canonical address with a host and port. -func (s *Socks5) connect(conn net.Conn, target string) error { +func (s *Socks5) connect(conn net.Conn, target string, cmd byte) (addr socks.Addr, err error) { host, portStr, err := net.SplitHostPort(target) if err != nil { - return err + return } port, err := strconv.Atoi(portStr) if err != nil { - return errors.New("proxy: failed to parse port number: " + portStr) + return addr, errors.New("proxy: failed to parse port number: " + portStr) } if port < 1 || port > 0xffff { - return errors.New("proxy: port number out of range: " + portStr) + return addr, errors.New("proxy: port number out of range: " + portStr) } // the size here is just an estimate @@ -135,17 +123,17 @@ func (s *Socks5) connect(conn net.Conn, target string) error { } if _, err := conn.Write(buf); err != nil { - return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + return addr, errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) } if _, err := io.ReadFull(conn, buf[:2]); err != nil { - return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + return addr, errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) } if buf[0] != Version { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) + return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) } if buf[1] == 0xff { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") + return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") } if buf[1] == socks.AuthPassword { @@ -157,20 +145,20 @@ func (s *Socks5) connect(conn net.Conn, target string) error { buf = append(buf, s.password...) if _, err := conn.Write(buf); err != nil { - return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + return addr, errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) } if _, err := io.ReadFull(conn, buf[:2]); err != nil { - return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + return addr, errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) } if buf[1] != 0 { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") + return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") } } buf = buf[:0] - buf = append(buf, Version, socks.CmdConnect, 0 /* reserved */) + buf = append(buf, Version, cmd, 0 /* reserved */) if ip := net.ParseIP(host); ip != nil { if ip4 := ip.To4(); ip4 != nil { @@ -182,7 +170,7 @@ func (s *Socks5) connect(conn net.Conn, target string) error { buf = append(buf, ip...) } else { if len(host) > 255 { - return errors.New("proxy: destination hostname too long: " + host) + return addr, errors.New("proxy: destination hostname too long: " + host) } buf = append(buf, socks.ATypDomain) buf = append(buf, byte(len(host))) @@ -191,11 +179,12 @@ func (s *Socks5) connect(conn net.Conn, target string) error { buf = append(buf, byte(port>>8), byte(port)) if _, err := conn.Write(buf); err != nil { - return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + return addr, errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) } - if _, err := io.ReadFull(conn, buf[:4]); err != nil { - return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + // read VER REP RSV + if _, err := io.ReadFull(conn, buf[:3]); err != nil { + return addr, errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) } failure := "unknown error" @@ -204,38 +193,8 @@ func (s *Socks5) connect(conn net.Conn, target string) error { } if len(failure) > 0 { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) + return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) } - bytesToDiscard := 0 - switch buf[3] { - case socks.ATypIP4: - bytesToDiscard = net.IPv4len - case socks.ATypIP6: - bytesToDiscard = net.IPv6len - case socks.ATypDomain: - _, err := io.ReadFull(conn, buf[:1]) - if err != nil { - return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - bytesToDiscard = int(buf[0]) - default: - return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr) - } - - if cap(buf) < bytesToDiscard { - buf = make([]byte, bytesToDiscard) - } else { - buf = buf[:bytesToDiscard] - } - if _, err := io.ReadFull(conn, buf); err != nil { - return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - - // Also need to discard the port number - if _, err := io.ReadFull(conn, buf[:2]); err != nil { - return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - - return nil + return socks.ReadAddr(conn) } diff --git a/proxy/socks5/server.go b/proxy/socks5/server.go index 1133efe..f915f10 100644 --- a/proxy/socks5/server.go +++ b/proxy/socks5/server.go @@ -157,27 +157,29 @@ func (s *Socks5) ListenAndServeUDP() { } // Handshake fast-tracks SOCKS initialization to get target address to connect. -func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) { +func (s *Socks5) handshake(c net.Conn) (socks.Addr, error) { // Read RFC 1928 for request and reply structure and sizes - buf := make([]byte, socks.MaxAddrLen) + buf := pool.GetBuffer(socks.MaxAddrLen) + defer pool.PutBuffer(buf) + // read VER, NMETHODS, METHODS - if _, err := io.ReadFull(rw, buf[:2]); err != nil { + if _, err := io.ReadFull(c, buf[:2]); err != nil { return nil, err } nmethods := buf[1] - if _, err := io.ReadFull(rw, buf[:nmethods]); err != nil { + if _, err := io.ReadFull(c, buf[:nmethods]); err != nil { return nil, err } // write VER METHOD if s.user != "" && s.password != "" { - _, err := rw.Write([]byte{Version, socks.AuthPassword}) + _, err := c.Write([]byte{Version, socks.AuthPassword}) if err != nil { return nil, err } - _, err = io.ReadFull(rw, buf[:2]) + _, err = io.ReadFull(c, buf[:2]) if err != nil { return nil, err } @@ -185,28 +187,28 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) { // Get username userLen := int(buf[1]) if userLen <= 0 { - rw.Write([]byte{1, 1}) + c.Write([]byte{1, 1}) return nil, errors.New("auth failed: wrong username length") } - if _, err := io.ReadFull(rw, buf[:userLen]); err != nil { + if _, err := io.ReadFull(c, buf[:userLen]); err != nil { return nil, errors.New("auth failed: cannot get username") } user := string(buf[:userLen]) // Get password - _, err = rw.Read(buf[:1]) + _, err = c.Read(buf[:1]) if err != nil { return nil, errors.New("auth failed: cannot get password len") } passLen := int(buf[0]) if passLen <= 0 { - rw.Write([]byte{1, 1}) + c.Write([]byte{1, 1}) return nil, errors.New("auth failed: wrong password length") } - _, err = io.ReadFull(rw, buf[:passLen]) + _, err = io.ReadFull(c, buf[:passLen]) if err != nil { return nil, errors.New("auth failed: cannot get password") } @@ -214,7 +216,7 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) { // Verify if user != s.user || pass != s.password { - _, err = rw.Write([]byte{1, 1}) + _, err = c.Write([]byte{1, 1}) if err != nil { return nil, err } @@ -222,30 +224,30 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) { } // Response auth state - _, err = rw.Write([]byte{1, 0}) + _, err = c.Write([]byte{1, 0}) if err != nil { return nil, err } - } else if _, err := rw.Write([]byte{Version, socks.AuthNone}); err != nil { + } else if _, err := c.Write([]byte{Version, socks.AuthNone}); err != nil { return nil, err } // read VER CMD RSV ATYP DST.ADDR DST.PORT - if _, err := io.ReadFull(rw, buf[:3]); err != nil { + if _, err := io.ReadFull(c, buf[:3]); err != nil { return nil, err } cmd := buf[1] - addr, err := socks.ReadAddrBuf(rw, buf) + addr, err := socks.ReadAddrBuf(c, buf) if err != nil { return nil, err } switch cmd { case socks.CmdConnect: - _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) // SOCKS v5, reply succeeded + _, err = c.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) // SOCKS v5, reply succeeded case socks.CmdUDPAssociate: - listenAddr := socks.ParseAddr(rw.(net.Conn).LocalAddr().String()) - _, err = rw.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded + listenAddr := socks.ParseAddr(c.LocalAddr().String()) + _, err = c.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded if err != nil { return nil, socks.Errors[7] } diff --git a/systemd/glider@.service b/systemd/glider@.service index 4a6bbe0..2d3972a 100644 --- a/systemd/glider@.service +++ b/systemd/glider@.service @@ -7,7 +7,6 @@ Type=simple User=nobody Restart=always LimitNOFILE=102400 -Environment="GODEBUG=madvdontneed=1" # NOTE: CHANGE to your glider path ExecStart=/usr/bin/glider -config /etc/glider/%i.conf