Skip to content

Commit

Permalink
socks5: fix an issue in udp handling with auth (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
nadoo committed Feb 12, 2021
1 parent d2268b6 commit fbf694f
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 115 deletions.
16 changes: 4 additions & 12 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
123 changes: 41 additions & 82 deletions proxy/socks5/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -39,52 +54,25 @@ 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})

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 {
var uAddr socks.Addr
if uAddr, err = s.connect(c, addr, socks.CmdUDPAssociate); err != nil {
c.Close()
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
}
buf := pool.GetBuffer(socks.MaxAddrLen)
defer pool.PutBuffer(buf)

var uAddress string
h, p, _ := net.SplitHostPort(uAddr.String())
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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)))
Expand All @@ -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"
Expand All @@ -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)
}

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 addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
}

return nil
return socks.ReadAddr(conn)
}
40 changes: 21 additions & 19 deletions proxy/socks5/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,95 +157,97 @@ 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
}

// 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")
}
pass := string(buf[:passLen])

// 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
}
return nil, errors.New("auth failed, authinfo: " + user + ":" + pass)
}

// 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]
}
Expand Down
Loading

0 comments on commit fbf694f

Please sign in to comment.