From 892eb1d7923aa2fb992f735be281611d933d7439 Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Thu, 25 Apr 2019 13:48:47 +0800 Subject: [PATCH] Feature: support outbound socks5 udp --- README.md | 2 +- adapters/inbound/socket.go | 4 +- adapters/inbound/util.go | 10 +- adapters/outbound/shadowsocks.go | 27 +--- adapters/outbound/socks5.go | 81 ++++------ adapters/outbound/util.go | 36 +++++ component/socks5/socks5.go | 246 +++++++++++++++++++++++++++++++ proxy/redir/tcp_darwin.go | 6 +- proxy/redir/tcp_freebsd.go | 10 +- proxy/redir/tcp_linux.go | 10 +- proxy/redir/tcp_windows.go | 4 +- proxy/socks/tcp.go | 107 +------------- 12 files changed, 337 insertions(+), 206 deletions(-) create mode 100644 component/socks5/socks5.go diff --git a/README.md b/README.md index 3b753a6d..6197d73c 100644 --- a/README.md +++ b/README.md @@ -230,5 +230,5 @@ https://clash.gitbook.io/ - [x] Complementing the necessary rule operators - [x] Redir proxy -- [ ] UDP support (vmess, outbound socks5) +- [ ] UDP support (vmess) - [ ] Connection manager diff --git a/adapters/inbound/socket.go b/adapters/inbound/socket.go index 0d1be44b..fae80e01 100644 --- a/adapters/inbound/socket.go +++ b/adapters/inbound/socket.go @@ -3,8 +3,8 @@ package adapters import ( "net" + "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/go-shadowsocks2/socks" ) // SocketAdapter is a adapter for socks and redir connection @@ -19,7 +19,7 @@ func (s *SocketAdapter) Metadata() *C.Metadata { } // NewSocket is SocketAdapter generator -func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType, netType C.NetWork) *SocketAdapter { +func NewSocket(target socks5.Addr, conn net.Conn, source C.SourceType, netType C.NetWork) *SocketAdapter { metadata := parseSocksAddr(target) metadata.NetWork = netType metadata.Source = source diff --git a/adapters/inbound/util.go b/adapters/inbound/util.go index c29c06c8..d5bca74d 100644 --- a/adapters/inbound/util.go +++ b/adapters/inbound/util.go @@ -5,24 +5,24 @@ import ( "net/http" "strconv" + "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/go-shadowsocks2/socks" ) -func parseSocksAddr(target socks.Addr) *C.Metadata { +func parseSocksAddr(target socks5.Addr) *C.Metadata { metadata := &C.Metadata{ AddrType: int(target[0]), } switch target[0] { - case socks.AtypDomainName: + case socks5.AtypDomainName: metadata.Host = string(target[2 : 2+target[1]]) metadata.Port = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) - case socks.AtypIPv4: + case socks5.AtypIPv4: ip := net.IP(target[1 : 1+net.IPv4len]) metadata.IP = &ip metadata.Port = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) - case socks.AtypIPv6: + case socks5.AtypIPv6: ip := net.IP(target[1 : 1+net.IPv6len]) metadata.IP = &ip metadata.Port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index b51f2354..8c8e92bf 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -1,7 +1,6 @@ package adapters import ( - "bytes" "crypto/tls" "encoding/json" "fmt" @@ -11,11 +10,11 @@ import ( "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/structure" obfs "github.com/Dreamacro/clash/component/simple-obfs" + "github.com/Dreamacro/clash/component/socks5" v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/go-shadowsocks2/core" - "github.com/Dreamacro/go-shadowsocks2/socks" ) type ShadowSocks struct { @@ -177,26 +176,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { }, nil } -func serializesSocksAddr(metadata *C.Metadata) []byte { - var buf [][]byte - aType := uint8(metadata.AddrType) - p, _ := strconv.Atoi(metadata.Port) - port := []byte{uint8(p >> 8), uint8(p & 0xff)} - switch metadata.AddrType { - case socks.AtypDomainName: - len := uint8(len(metadata.Host)) - host := []byte(metadata.Host) - buf = [][]byte{{aType, len}, host, port} - case socks.AtypIPv4: - host := metadata.IP.To4() - buf = [][]byte{{aType}, host, port} - case socks.AtypIPv6: - host := metadata.IP.To16() - buf = [][]byte{{aType}, host, port} - } - return bytes.Join(buf, nil) -} - type ssUDPConn struct { net.PacketConn rAddr net.Addr @@ -205,7 +184,7 @@ type ssUDPConn struct { func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { buf := pool.BufPool.Get().([]byte) defer pool.BufPool.Put(buf[:cap(buf)]) - rAddr := socks.ParseAddr(uc.rAddr.String()) + rAddr := socks5.ParseAddr(uc.rAddr.String()) copy(buf[len(rAddr):], b) copy(buf, rAddr) return uc.PacketConn.WriteTo(buf[:len(rAddr)+len(b)], addr) @@ -213,7 +192,7 @@ func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { n, a, e := uc.PacketConn.ReadFrom(b) - addr := socks.SplitAddr(b[:n]) + addr := socks5.SplitAddr(b[:n]) copy(b, b[len(addr):]) return n - len(addr), a, e } diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index bd8680d5..7f52b58e 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -1,17 +1,13 @@ package adapters import ( - "bytes" "crypto/tls" - "errors" "fmt" - "io" "net" "strconv" + "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" - - "github.com/Dreamacro/go-shadowsocks2/socks" ) type Socks5 struct { @@ -48,69 +44,44 @@ func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) { return nil, fmt.Errorf("%s connect error", ss.addr) } tcpKeepAlive(c) - if err := ss.shakeHand(metadata, c); err != nil { + var user *socks5.User + if ss.user != "" { + user = &socks5.User{ + Username: ss.user, + Password: ss.pass, + } + } + if err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil { return nil, err } return c, nil } -func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { - buf := make([]byte, socks.MaxAddrLen) - var err error +func (ss *Socks5) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { + c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) - // VER, NMETHODS, METHODS - if len(ss.user) > 0 { - _, err = rw.Write([]byte{5, 1, 2}) - } else { - _, err = rw.Write([]byte{5, 1, 0}) + if err == nil && ss.tls { + cc := tls.Client(c, ss.tlsConfig) + err = cc.Handshake() + c = cc } + if err != nil { - return err + return nil, nil, fmt.Errorf("%s connect error", ss.addr) } - - // VER, METHOD - if _, err := io.ReadFull(rw, buf[:2]); err != nil { - return err - } - - if buf[0] != 5 { - return errors.New("SOCKS version error") - } - - if buf[1] == 2 { - // password protocol version - authMsg := &bytes.Buffer{} - authMsg.WriteByte(1) - authMsg.WriteByte(uint8(len(ss.user))) - authMsg.WriteString(ss.user) - authMsg.WriteByte(uint8(len(ss.pass))) - authMsg.WriteString(ss.pass) - - if _, err := rw.Write(authMsg.Bytes()); err != nil { - return err + tcpKeepAlive(c) + var user *socks5.User + if ss.user != "" { + user = &socks5.User{ + Username: ss.user, + Password: ss.pass, } - - if _, err := io.ReadFull(rw, buf[:2]); err != nil { - return err - } - - if buf[1] != 0 { - return errors.New("rejected username/password") - } - } else if buf[1] != 0 { - return errors.New("SOCKS need auth") } - // VER, CMD, RSV, ADDR - if _, err := rw.Write(bytes.Join([][]byte{{5, 1, 0}, serializesSocksAddr(metadata)}, []byte(""))); err != nil { - return err + if err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user); err != nil { + return nil, nil, err } - - if _, err := io.ReadFull(rw, buf[:10]); err != nil { - return err - } - - return nil + return &fakeUDPConn{Conn: c}, c.LocalAddr(), nil } func NewSocks5(option Socks5Option) *Socks5 { diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 363bcc3e..94d19d4b 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -1,13 +1,16 @@ package adapters import ( + "bytes" "crypto/tls" "fmt" "net" "net/url" + "strconv" "sync" "time" + "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" ) @@ -60,3 +63,36 @@ func getClientSessionCache() tls.ClientSessionCache { }) return globalClientSessionCache } + +func serializesSocksAddr(metadata *C.Metadata) []byte { + var buf [][]byte + aType := uint8(metadata.AddrType) + p, _ := strconv.Atoi(metadata.Port) + port := []byte{uint8(p >> 8), uint8(p & 0xff)} + switch metadata.AddrType { + case socks5.AtypDomainName: + len := uint8(len(metadata.Host)) + host := []byte(metadata.Host) + buf = [][]byte{{aType, len}, host, port} + case socks5.AtypIPv4: + host := metadata.IP.To4() + buf = [][]byte{{aType}, host, port} + case socks5.AtypIPv6: + host := metadata.IP.To16() + buf = [][]byte{{aType}, host, port} + } + return bytes.Join(buf, nil) +} + +type fakeUDPConn struct { + net.Conn +} + +func (fuc *fakeUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { + return fuc.Conn.Write(b) +} + +func (fuc *fakeUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, err := fuc.Conn.Read(b) + return n, fuc.RemoteAddr(), err +} diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go new file mode 100644 index 00000000..0d909e63 --- /dev/null +++ b/component/socks5/socks5.go @@ -0,0 +1,246 @@ +package socks5 + +import ( + "bytes" + "errors" + "io" + "net" + "strconv" +) + +// Error represents a SOCKS error +type Error byte + +func (err Error) Error() string { + return "SOCKS error: " + strconv.Itoa(int(err)) +} + +// Command is request commands as defined in RFC 1928 section 4. +type Command = uint8 + +// SOCKS request commands as defined in RFC 1928 section 4. +const ( + CmdConnect Command = 1 + CmdBind Command = 2 + CmdUDPAssociate Command = 3 +) + +// SOCKS address types as defined in RFC 1928 section 5. +const ( + AtypIPv4 = 1 + AtypDomainName = 3 + AtypIPv6 = 4 +) + +// MaxAddrLen is the maximum size of SOCKS address in bytes. +const MaxAddrLen = 1 + 1 + 255 + 2 + +// Addr represents a SOCKS address as defined in RFC 1928 section 5. +type Addr = []byte + +// SOCKS errors as defined in RFC 1928 section 6. +const ( + ErrGeneralFailure = Error(1) + ErrConnectionNotAllowed = Error(2) + ErrNetworkUnreachable = Error(3) + ErrHostUnreachable = Error(4) + ErrConnectionRefused = Error(5) + ErrTTLExpired = Error(6) + ErrCommandNotSupported = Error(7) + ErrAddressNotSupported = Error(8) +) + +type User struct { + Username string + Password string +} + +// ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side. +func ServerHandshake(rw io.ReadWriter) (addr Addr, command Command, err error) { + // Read RFC 1928 for request and reply structure and sizes. + buf := make([]byte, MaxAddrLen) + // read VER, NMETHODS, METHODS + if _, err = io.ReadFull(rw, buf[:2]); err != nil { + return + } + nmethods := buf[1] + if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil { + return + } + // write VER METHOD + if _, err = rw.Write([]byte{5, 0}); err != nil { + return + } + // read VER CMD RSV ATYP DST.ADDR DST.PORT + if _, err = io.ReadFull(rw, buf[:3]); err != nil { + return + } + + if buf[1] != CmdConnect && buf[1] != CmdUDPAssociate { + err = ErrCommandNotSupported + return + } + + command = buf[1] + addr, err = readAddr(rw, buf) + if err != nil { + return + } + // write VER REP RSV ATYP BND.ADDR BND.PORT + _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) + return +} + +// ClientHandshake fast-tracks SOCKS initialization to get target address to connect on client side. +func ClientHandshake(rw io.ReadWriter, addr Addr, cammand Command, user *User) error { + buf := make([]byte, MaxAddrLen) + var err error + + // VER, NMETHODS, METHODS + if user != nil { + _, err = rw.Write([]byte{5, 1, 2}) + } else { + _, err = rw.Write([]byte{5, 1, 0}) + } + if err != nil { + return err + } + + // VER, METHOD + if _, err := io.ReadFull(rw, buf[:2]); err != nil { + return err + } + + if buf[0] != 5 { + return errors.New("SOCKS version error") + } + + if buf[1] == 2 { + // password protocol version + authMsg := &bytes.Buffer{} + authMsg.WriteByte(1) + authMsg.WriteByte(uint8(len(user.Username))) + authMsg.WriteString(user.Username) + authMsg.WriteByte(uint8(len(user.Password))) + authMsg.WriteString(user.Password) + + if _, err := rw.Write(authMsg.Bytes()); err != nil { + return err + } + + if _, err := io.ReadFull(rw, buf[:2]); err != nil { + return err + } + + if buf[1] != 0 { + return errors.New("rejected username/password") + } + } else if buf[1] != 0 { + return errors.New("SOCKS need auth") + } + + // VER, CMD, RSV, ADDR + if _, err := rw.Write(bytes.Join([][]byte{{5, cammand, 0}, addr}, []byte(""))); err != nil { + return err + } + + if _, err := io.ReadFull(rw, buf[:10]); err != nil { + return err + } + + return nil +} + +func readAddr(r io.Reader, b []byte) (Addr, error) { + if len(b) < MaxAddrLen { + return nil, io.ErrShortBuffer + } + _, err := io.ReadFull(r, b[:1]) // read 1st byte for address type + if err != nil { + return nil, err + } + + switch b[0] { + case AtypDomainName: + _, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length + if err != nil { + return nil, err + } + _, err = io.ReadFull(r, b[2:2+b[1]+2]) + return b[:1+1+b[1]+2], err + case AtypIPv4: + _, err = io.ReadFull(r, b[1:1+net.IPv4len+2]) + return b[:1+net.IPv4len+2], err + case AtypIPv6: + _, err = io.ReadFull(r, b[1:1+net.IPv6len+2]) + return b[:1+net.IPv6len+2], err + } + + return nil, ErrAddressNotSupported +} + +// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed. +func SplitAddr(b []byte) Addr { + addrLen := 1 + if len(b) < addrLen { + return nil + } + + switch b[0] { + case AtypDomainName: + if len(b) < 2 { + return nil + } + addrLen = 1 + 1 + int(b[1]) + 2 + case AtypIPv4: + addrLen = 1 + net.IPv4len + 2 + case AtypIPv6: + addrLen = 1 + net.IPv6len + 2 + default: + return nil + + } + + if len(b) < addrLen { + return nil + } + + return b[:addrLen] +} + +// ParseAddr parses the address in string s. Returns nil if failed. +func ParseAddr(s string) Addr { + var addr Addr + host, port, err := net.SplitHostPort(s) + if err != nil { + return nil + } + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + addr = make([]byte, 1+net.IPv4len+2) + addr[0] = AtypIPv4 + copy(addr[1:], ip4) + } else { + addr = make([]byte, 1+net.IPv6len+2) + addr[0] = AtypIPv6 + copy(addr[1:], ip) + } + } else { + if len(host) > 255 { + return nil + } + addr = make([]byte, 1+1+len(host)+2) + addr[0] = AtypDomainName + addr[1] = byte(len(host)) + copy(addr[2:], host) + } + + portnum, err := strconv.ParseUint(port, 10, 16) + if err != nil { + return nil + } + + addr[len(addr)-2], addr[len(addr)-1] = byte(portnum>>8), byte(portnum) + + return addr +} diff --git a/proxy/redir/tcp_darwin.go b/proxy/redir/tcp_darwin.go index f032a03d..4e9ade1e 100644 --- a/proxy/redir/tcp_darwin.go +++ b/proxy/redir/tcp_darwin.go @@ -5,10 +5,10 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/go-shadowsocks2/socks" + "github.com/Dreamacro/clash/component/socks5" ) -func parserPacket(c net.Conn) (socks.Addr, error) { +func parserPacket(c net.Conn) (socks5.Addr, error) { const ( PfInout = 0 PfIn = 1 @@ -51,7 +51,7 @@ func parserPacket(c net.Conn) (socks.Addr, error) { } addr := make([]byte, 1+net.IPv4len+2) - addr[0] = socks.AtypIPv4 + addr[0] = socks5.AtypIPv4 copy(addr[1:1+net.IPv4len], nl.rdaddr[:4]) copy(addr[1+net.IPv4len:], nl.rdxport[:2]) return addr, nil diff --git a/proxy/redir/tcp_freebsd.go b/proxy/redir/tcp_freebsd.go index 1f90121b..f9cdf5a6 100644 --- a/proxy/redir/tcp_freebsd.go +++ b/proxy/redir/tcp_freebsd.go @@ -6,7 +6,7 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/go-shadowsocks2/socks" + "github.com/Dreamacro/clash/component/socks5" ) const ( @@ -14,7 +14,7 @@ const ( IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h ) -func parserPacket(conn net.Conn) (socks.Addr, error) { +func parserPacket(conn net.Conn) (socks5.Addr, error) { c, ok := conn.(*net.TCPConn) if !ok { return nil, errors.New("only work with TCP connection") @@ -25,7 +25,7 @@ func parserPacket(conn net.Conn) (socks.Addr, error) { return nil, err } - var addr socks.Addr + var addr socks5.Addr rc.Control(func(fd uintptr) { addr, err = getorigdst(fd) @@ -35,7 +35,7 @@ func parserPacket(conn net.Conn) (socks.Addr, error) { } // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c -func getorigdst(fd uintptr) (socks.Addr, error) { +func getorigdst(fd uintptr) (socks5.Addr, error) { raw := syscall.RawSockaddrInet4{} siz := unsafe.Sizeof(raw) _, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0) @@ -44,7 +44,7 @@ func getorigdst(fd uintptr) (socks.Addr, error) { } addr := make([]byte, 1+net.IPv4len+2) - addr[0] = socks.AtypIPv4 + addr[0] = socks5.AtypIPv4 copy(addr[1:1+net.IPv4len], raw.Addr[:]) port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] diff --git a/proxy/redir/tcp_linux.go b/proxy/redir/tcp_linux.go index 1cd5a3ee..ac3a455c 100644 --- a/proxy/redir/tcp_linux.go +++ b/proxy/redir/tcp_linux.go @@ -6,7 +6,7 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/go-shadowsocks2/socks" + "github.com/Dreamacro/clash/component/socks5" ) const ( @@ -14,7 +14,7 @@ const ( IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h ) -func parserPacket(conn net.Conn) (socks.Addr, error) { +func parserPacket(conn net.Conn) (socks5.Addr, error) { c, ok := conn.(*net.TCPConn) if !ok { return nil, errors.New("only work with TCP connection") @@ -25,7 +25,7 @@ func parserPacket(conn net.Conn) (socks.Addr, error) { return nil, err } - var addr socks.Addr + var addr socks5.Addr rc.Control(func(fd uintptr) { addr, err = getorigdst(fd) @@ -35,7 +35,7 @@ func parserPacket(conn net.Conn) (socks.Addr, error) { } // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c -func getorigdst(fd uintptr) (socks.Addr, error) { +func getorigdst(fd uintptr) (socks5.Addr, error) { raw := syscall.RawSockaddrInet4{} siz := unsafe.Sizeof(raw) if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { @@ -43,7 +43,7 @@ func getorigdst(fd uintptr) (socks.Addr, error) { } addr := make([]byte, 1+net.IPv4len+2) - addr[0] = socks.AtypIPv4 + addr[0] = socks5.AtypIPv4 copy(addr[1:1+net.IPv4len], raw.Addr[:]) port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] diff --git a/proxy/redir/tcp_windows.go b/proxy/redir/tcp_windows.go index cce61c07..58b3e912 100644 --- a/proxy/redir/tcp_windows.go +++ b/proxy/redir/tcp_windows.go @@ -4,9 +4,9 @@ import ( "errors" "net" - "github.com/Dreamacro/go-shadowsocks2/socks" + "github.com/Dreamacro/clash/component/socks5" ) -func parserPacket(conn net.Conn) (socks.Addr, error) { +func parserPacket(conn net.Conn) (socks5.Addr, error) { return nil, errors.New("Windows not support yet") } diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 3d884ff5..08789cbe 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -1,57 +1,19 @@ package socks import ( - "io" "net" - "strconv" adapters "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" - - "github.com/Dreamacro/go-shadowsocks2/socks" ) var ( tun = tunnel.Instance() ) -// Error represents a SOCKS error -type Error byte - -func (err Error) Error() string { - return "SOCKS error: " + strconv.Itoa(int(err)) -} - -// SOCKS request commands as defined in RFC 1928 section 4. -const ( - CmdConnect = 1 - CmdBind = 2 - CmdUDPAssociate = 3 -) - -// SOCKS address types as defined in RFC 1928 section 5. -const ( - AtypIPv4 = 1 - AtypDomainName = 3 - AtypIPv6 = 4 -) - -const MaxAddrLen = 1 + 1 + 255 + 2 - -// SOCKS errors as defined in RFC 1928 section 6. -const ( - ErrGeneralFailure = Error(1) - ErrConnectionNotAllowed = Error(2) - ErrNetworkUnreachable = Error(3) - ErrHostUnreachable = Error(4) - ErrConnectionRefused = Error(5) - ErrTTLExpired = Error(6) - ErrCommandNotSupported = Error(7) - ErrAddressNotSupported = Error(8) -) - type SockListener struct { net.Listener address string @@ -92,78 +54,15 @@ func (l *SockListener) Address() string { } func handleSocks(conn net.Conn) { - target, command, err := handshake(conn) + target, command, err := socks5.ServerHandshake(conn) if err != nil { conn.Close() return } conn.(*net.TCPConn).SetKeepAlive(true) - if command == CmdUDPAssociate { + if command == socks5.CmdUDPAssociate { tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.UDP)) return } tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP)) } - -// handshake fast-tracks SOCKS initialization to get target address to connect. -func handshake(rw io.ReadWriter) (addr socks.Addr, command int, err error) { - // Read RFC 1928 for request and reply structure and sizes. - buf := make([]byte, MaxAddrLen) - // read VER, NMETHODS, METHODS - if _, err = io.ReadFull(rw, buf[:2]); err != nil { - return - } - nmethods := buf[1] - if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil { - return - } - // write VER METHOD - if _, err = rw.Write([]byte{5, 0}); err != nil { - return - } - // read VER CMD RSV ATYP DST.ADDR DST.PORT - if _, err = io.ReadFull(rw, buf[:3]); err != nil { - return - } - if buf[1] != CmdConnect && buf[1] != CmdUDPAssociate { - err = ErrCommandNotSupported - return - } - - command = int(buf[1]) - addr, err = readAddr(rw, buf) - if err != nil { - return - } - // write VER REP RSV ATYP BND.ADDR BND.PORT - _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) - return -} - -func readAddr(r io.Reader, b []byte) (socks.Addr, error) { - if len(b) < MaxAddrLen { - return nil, io.ErrShortBuffer - } - _, err := io.ReadFull(r, b[:1]) // read 1st byte for address type - if err != nil { - return nil, err - } - - switch b[0] { - case AtypDomainName: - _, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length - if err != nil { - return nil, err - } - _, err = io.ReadFull(r, b[2:2+b[1]+2]) - return b[:1+1+b[1]+2], err - case AtypIPv4: - _, err = io.ReadFull(r, b[1:1+net.IPv4len+2]) - return b[:1+net.IPv4len+2], err - case AtypIPv6: - _, err = io.ReadFull(r, b[1:1+net.IPv6len+2]) - return b[:1+net.IPv6len+2], err - } - - return nil, ErrAddressNotSupported -}