diff --git a/cmd/gost/route.go b/cmd/gost/route.go index 4032884..dc00766 100644 --- a/cmd/gost/route.go +++ b/cmd/gost/route.go @@ -216,8 +216,6 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) { connector = gost.SOCKS4AConnector() case "ss": connector = gost.ShadowConnector(node.User) - case "ss2": - connector = gost.Shadow2Connector(node.User) case "ssu": connector = gost.ShadowUDPConnector(node.User) case "direct": @@ -505,8 +503,6 @@ func (r *route) GenRouters() ([]router, error) { handler = gost.SOCKS4Handler() case "ss": handler = gost.ShadowHandler() - case "ss2": - handler = gost.Shadow2Handler() case "http": handler = gost.HTTPHandler() case "tcp": diff --git a/node.go b/node.go index 839cb6f..0d45fb2 100644 --- a/node.go +++ b/node.go @@ -102,7 +102,9 @@ func ParseNode(s string) (node Node, err error) { case "socks4", "socks4a": case "socks", "socks5": node.Protocol = "socks5" - case "ss", "ss2", "ssu": + case "ss", "ssu": + case "ss2": // as of 2.10.1, ss2 is same as ss + node.Protocol = "ss" case "sni": case "tcp", "udp", "rtcp", "rudp": // port forwarding case "direct", "remote", "forward": // forwarding diff --git a/ss.go b/ss.go index c698d5c..58e3f24 100644 --- a/ss.go +++ b/ss.go @@ -7,7 +7,6 @@ import ( "io" "net" "net/url" - "strconv" "time" "github.com/ginuerzh/gosocks5" @@ -17,14 +16,15 @@ import ( ) type shadowConnector struct { - Cipher *url.Userinfo + cipher core.Cipher } // ShadowConnector creates a Connector for shadowsocks proxy client. -// It accepts a cipher info for shadowsocks data encryption/decryption. -// The cipher must not be nil. -func ShadowConnector(cipher *url.Userinfo) Connector { - return &shadowConnector{Cipher: cipher} +// It accepts an optional cipher info for shadowsocks data encryption/decryption. +func ShadowConnector(info *url.Userinfo) Connector { + return &shadowConnector{ + cipher: initShadowCipher(info), + } } func (c *shadowConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { @@ -38,37 +38,32 @@ func (c *shadowConnector) Connect(conn net.Conn, addr string, options ...Connect timeout = ConnectTimeout } + socksAddr, err := gosocks5.NewAddr(addr) + if err != nil { + return nil, err + } + rawaddr := sPool.Get().([]byte) + defer sPool.Put(rawaddr) + + n, err := socksAddr.Encode(rawaddr) + if err != nil { + return nil, err + } + conn.SetDeadline(time.Now().Add(timeout)) defer conn.SetDeadline(time.Time{}) - rawaddr, err := ss.RawAddr(addr) - if err != nil { + if c.cipher != nil { + conn = c.cipher.StreamConn(conn) + } + if _, err := conn.Write(rawaddr[:n]); err != nil { return nil, err } - - var method, password string - cp := opts.User - if cp == nil { - cp = c.Cipher - } - if cp != nil { - method = cp.Username() - password, _ = cp.Password() - } - - cipher, err := ss.NewCipher(method, password) - if err != nil { - return nil, err - } - - sc := &shadowConn{ - Conn: ss.NewConn(conn, cipher), - } - _, err = sc.Write(rawaddr) - return sc, err + return conn, nil } type shadowHandler struct { + cipher core.Cipher options *HandlerOptions } @@ -88,37 +83,32 @@ func (h *shadowHandler) Init(options ...HandlerOption) { for _, opt := range options { opt(h.options) } + if len(h.options.Users) > 0 { + h.cipher = initShadowCipher(h.options.Users[0]) + } } func (h *shadowHandler) Handle(conn net.Conn) { defer conn.Close() - var method, password string - users := h.options.Users - if len(users) > 0 { - method = users[0].Username() - password, _ = users[0].Password() + if h.cipher != nil { + conn = h.cipher.StreamConn(conn) } - cipher, err := ss.NewCipher(method, password) - if err != nil { - log.Logf("[ss] %s -> %s : %s", - conn.RemoteAddr(), conn.LocalAddr(), err) - return - } - conn = &shadowConn{Conn: ss.NewConn(conn, cipher)} conn.SetReadDeadline(time.Now().Add(ReadTimeout)) - host, err := h.getRequest(conn) + + addr, err := readSocksAddr(conn) if err != nil { log.Logf("[ss] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) return } - // clear timer + conn.SetReadDeadline(time.Time{}) - log.Logf("[ss] %s -> %s -> %s", - conn.RemoteAddr(), h.options.Node.String(), host) + host := addr.String() + log.Logf("[ss] %s -> %s", + conn.RemoteAddr(), host) if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) { log.Logf("[ss] %s - %s : Unauthorized to tcp connect to %s", @@ -181,104 +171,15 @@ func (h *shadowHandler) Handle(conn net.Conn) { log.Logf("[ss] %s >-< %s", conn.RemoteAddr(), host) } -const ( - idType = 0 // address type index - idIP0 = 1 // ip address start index - idDmLen = 1 // domain address length index - idDm0 = 2 // domain address start index - - typeIPv4 = 1 // type is ipv4 address - typeDm = 3 // type is domain address - typeIPv6 = 4 // type is ipv6 address - - lenIPv4 = net.IPv4len + 2 // ipv4 + 2port - lenIPv6 = net.IPv6len + 2 // ipv6 + 2port - lenDmBase = 2 // 1addrLen + 2port, plus addrLen - lenHmacSha1 = 10 -) - -// This function is copied from shadowsocks library with some modification. -func (h *shadowHandler) getRequest(r io.Reader) (host string, err error) { - // buf size should at least have the same size with the largest possible - // request size (when addrType is 3, domain name has at most 256 bytes) - // 1(addrType) + 1(lenByte) + 256(max length address) + 2(port) - buf := make([]byte, smallBufferSize) - - // read till we get possible domain length field - if _, err = io.ReadFull(r, buf[:idType+1]); err != nil { - return - } - - var reqStart, reqEnd int - addrType := buf[idType] - switch addrType & ss.AddrMask { - case typeIPv4: - reqStart, reqEnd = idIP0, idIP0+lenIPv4 - case typeIPv6: - reqStart, reqEnd = idIP0, idIP0+lenIPv6 - case typeDm: - if _, err = io.ReadFull(r, buf[idType+1:idDmLen+1]); err != nil { - return - } - reqStart, reqEnd = idDm0, idDm0+int(buf[idDmLen])+lenDmBase - default: - err = fmt.Errorf("addr type %d not supported", addrType&ss.AddrMask) - return - } - - if _, err = io.ReadFull(r, buf[reqStart:reqEnd]); err != nil { - return - } - - // Return string for typeIP is not most efficient, but browsers (Chrome, - // Safari, Firefox) all seems using typeDm exclusively. So this is not a - // big problem. - switch addrType & ss.AddrMask { - case typeIPv4: - host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String() - case typeIPv6: - host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String() - case typeDm: - host = string(buf[idDm0 : idDm0+int(buf[idDmLen])]) - } - // parse port - port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd]) - host = net.JoinHostPort(host, strconv.Itoa(int(port))) - return -} - type shadowUDPConnector struct { cipher core.Cipher } // ShadowUDPConnector creates a Connector for shadowsocks UDP client. -// It accepts a cipher info for shadowsocks data encryption/decryption. -// The cipher must not be nil. +// It accepts an optional cipher info for shadowsocks data encryption/decryption. func ShadowUDPConnector(info *url.Userinfo) Connector { - c := &shadowUDPConnector{} - c.initCipher(info) - return c -} - -func (c *shadowUDPConnector) initCipher(info *url.Userinfo) { - var method, password string - if info != nil { - method = info.Username() - password, _ = info.Password() - } - - if method == "" || password == "" { - return - } - - c.cipher, _ = core.PickCipher(method, nil, password) - if c.cipher == nil { - cp, err := ss.NewCipher(method, password) - if err != nil { - log.Logf("[ssu] %s", err) - return - } - c.cipher = &shadowCipher{cipher: cp} + return &shadowUDPConnector{ + cipher: initShadowCipher(info), } } @@ -346,33 +247,11 @@ func (h *shadowUDPHandler) Init(options ...HandlerOption) { if h.options == nil { h.options = &HandlerOptions{} } - for _, opt := range options { opt(h.options) } - - h.initCipher() -} - -func (h *shadowUDPHandler) initCipher() { - var method, password string - users := h.options.Users - if len(users) > 0 { - method = users[0].Username() - password, _ = users[0].Password() - } - - if method == "" || password == "" { - return - } - h.cipher, _ = core.PickCipher(method, nil, password) - if h.cipher == nil { - cp, err := ss.NewCipher(method, password) - if err != nil { - log.Logf("[ssu] %s", err) - return - } - h.cipher = &shadowCipher{cipher: cp} + if len(h.options.Users) > 0 { + h.cipher = initShadowCipher(h.options.Users[0]) } } @@ -668,9 +547,71 @@ type shadowCipher struct { } func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn { - return ss.NewConn(conn, c.cipher.Copy()) + return &shadowConn{ + Conn: ss.NewConn(conn, c.cipher.Copy()), + } } func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn { return ss.NewSecurePacketConn(conn, c.cipher.Copy(), false) } + +func initShadowCipher(info *url.Userinfo) (cipher core.Cipher) { + var method, password string + if info != nil { + method = info.Username() + password, _ = info.Password() + } + + if method == "" || password == "" { + return + } + + cipher, _ = core.PickCipher(method, nil, password) + if cipher == nil { + cp, err := ss.NewCipher(method, password) + if err != nil { + log.Logf("[ss] %s", err) + return + } + cipher = &shadowCipher{cipher: cp} + } + return +} + +func readSocksAddr(r io.Reader) (*gosocks5.Addr, error) { + addr := &gosocks5.Addr{} + b := sPool.Get().([]byte) + defer sPool.Put(b) + + _, err := io.ReadFull(r, b[:1]) + if err != nil { + return nil, err + } + addr.Type = b[0] + + switch addr.Type { + case gosocks5.AddrIPv4: + _, err = io.ReadFull(r, b[:net.IPv4len]) + addr.Host = net.IP(b[0:net.IPv4len]).String() + case gosocks5.AddrIPv6: + _, err = io.ReadFull(r, b[:net.IPv6len]) + addr.Host = net.IP(b[0:net.IPv6len]).String() + case gosocks5.AddrDomain: + if _, err = io.ReadFull(r, b[:1]); err != nil { + return nil, err + } + addrlen := int(b[0]) + _, err = io.ReadFull(r, b[:addrlen]) + addr.Host = string(b[:addrlen]) + default: + return nil, gosocks5.ErrBadAddrType + } + if err != nil { + return nil, err + } + + _, err = io.ReadFull(r, b[:2]) + addr.Port = binary.BigEndian.Uint16(b[:2]) + return addr, err +} diff --git a/ss2.go b/ss2.go deleted file mode 100644 index 8d5961b..0000000 --- a/ss2.go +++ /dev/null @@ -1,229 +0,0 @@ -package gost - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "net" - "net/url" - "time" - - "github.com/ginuerzh/gosocks5" - "github.com/go-log/log" - "github.com/shadowsocks/go-shadowsocks2/core" -) - -type shadow2Connector struct { - Cipher *url.Userinfo -} - -// Shadow2Connector creates a Connector for go-shadowsocks2 proxy client. -// It accepts a cipher info for shadowsocks data encryption/decryption. -// The cipher must not be nil. -func Shadow2Connector(cipher *url.Userinfo) Connector { - return &shadow2Connector{Cipher: cipher} -} - -func (c *shadow2Connector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { - opts := &ConnectOptions{} - for _, option := range options { - option(opts) - } - - timeout := opts.Timeout - if timeout <= 0 { - timeout = ConnectTimeout - } - - conn.SetDeadline(time.Now().Add(timeout)) - defer conn.SetDeadline(time.Time{}) - - socksAddr, err := gosocks5.NewAddr(addr) - if err != nil { - return nil, err - } - - rawaddr := sPool.Get().([]byte) - defer sPool.Put(rawaddr) - - n, err := socksAddr.Encode(rawaddr) - if err != nil { - return nil, err - } - - var method, password string - cp := opts.User - if cp == nil { - cp = c.Cipher - } - if cp != nil { - method = cp.Username() - password, _ = cp.Password() - } - - cipher, err := core.PickCipher(method, nil, password) - if err != nil { - return nil, err - } - conn = cipher.StreamConn(conn) - if _, err := conn.Write(rawaddr[:n]); err != nil { - return nil, err - } - - return conn, nil -} - -type shadow2Handler struct { - options *HandlerOptions -} - -// Shadow2Handler creates a server Handler for go-shadowsocks2 proxy server. -func Shadow2Handler(opts ...HandlerOption) Handler { - h := &shadow2Handler{} - h.Init(opts...) - - return h -} - -func (h *shadow2Handler) Init(options ...HandlerOption) { - if h.options == nil { - h.options = &HandlerOptions{} - } - - for _, opt := range options { - opt(h.options) - } -} - -func (h *shadow2Handler) Handle(conn net.Conn) { - defer conn.Close() - - var method, password string - users := h.options.Users - if len(users) > 0 { - method = users[0].Username() - password, _ = users[0].Password() - } - - cipher, err := core.PickCipher(method, nil, password) - if err != nil { - log.Logf("[ss2] %s -> %s : %s", - conn.RemoteAddr(), conn.LocalAddr(), err) - return - } - - conn = cipher.StreamConn(conn) - conn.SetReadDeadline(time.Now().Add(ReadTimeout)) - - addr, err := readSocksAddr(conn) - if err != nil { - log.Logf("[ss2] %s -> %s : %s", - conn.RemoteAddr(), conn.LocalAddr(), err) - return - } - - // clear timer - conn.SetReadDeadline(time.Time{}) - - host := addr.String() - log.Logf("[ss2] %s -> %s -> %s", - conn.RemoteAddr(), h.options.Node.String(), host) - - if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) { - log.Logf("[ss2] %s - %s : Unauthorized to tcp connect to %s", - conn.RemoteAddr(), conn.LocalAddr(), host) - return - } - - if h.options.Bypass.Contains(host) { - log.Logf("[ss2] %s - %s : Bypass %s", - conn.RemoteAddr(), conn.LocalAddr(), host) - return - } - - retries := 1 - if h.options.Chain != nil && h.options.Chain.Retries > 0 { - retries = h.options.Chain.Retries - } - if h.options.Retries > 0 { - retries = h.options.Retries - } - - var cc net.Conn - var route *Chain - for i := 0; i < retries; i++ { - route, err = h.options.Chain.selectRouteFor(host) - if err != nil { - log.Logf("[ss2] %s -> %s : %s", - conn.RemoteAddr(), conn.LocalAddr(), err) - continue - } - - buf := bytes.Buffer{} - fmt.Fprintf(&buf, "%s -> %s -> ", - conn.RemoteAddr(), h.options.Node.String()) - for _, nd := range route.route { - fmt.Fprintf(&buf, "%d@%s -> ", nd.ID, nd.String()) - } - fmt.Fprintf(&buf, "%s", host) - log.Log("[route]", buf.String()) - - cc, err = route.Dial(host, - TimeoutChainOption(h.options.Timeout), - HostsChainOption(h.options.Hosts), - ResolverChainOption(h.options.Resolver), - ) - if err == nil { - break - } - log.Logf("[ss2] %s -> %s : %s", - conn.RemoteAddr(), conn.LocalAddr(), err) - } - - if err != nil { - return - } - defer cc.Close() - - log.Logf("[ss2] %s <-> %s", conn.RemoteAddr(), host) - transport(conn, cc) - log.Logf("[ss2] %s >-< %s", conn.RemoteAddr(), host) -} - -func readSocksAddr(r io.Reader) (*gosocks5.Addr, error) { - addr := &gosocks5.Addr{} - b := sPool.Get().([]byte) - defer sPool.Put(b) - - _, err := io.ReadFull(r, b[:1]) - if err != nil { - return nil, err - } - addr.Type = b[0] - - switch addr.Type { - case gosocks5.AddrIPv4: - _, err = io.ReadFull(r, b[:net.IPv4len]) - addr.Host = net.IP(b[0:net.IPv4len]).String() - case gosocks5.AddrIPv6: - _, err = io.ReadFull(r, b[:net.IPv6len]) - addr.Host = net.IP(b[0:net.IPv6len]).String() - case gosocks5.AddrDomain: - if _, err = io.ReadFull(r, b[:1]); err != nil { - return nil, err - } - addrlen := int(b[0]) - _, err = io.ReadFull(r, b[:addrlen]) - addr.Host = string(b[:addrlen]) - default: - return nil, gosocks5.ErrBadAddrType - } - if err != nil { - return nil, err - } - - _, err = io.ReadFull(r, b[:2]) - addr.Port = binary.BigEndian.Uint16(b[:2]) - return addr, err -} diff --git a/ss2_test.go b/ss2_test.go deleted file mode 100644 index 0147d28..0000000 --- a/ss2_test.go +++ /dev/null @@ -1,425 +0,0 @@ -package gost - -import ( - "crypto/rand" - "fmt" - "net/http/httptest" - "net/url" - "testing" -) - -func init() { - // ss.Debug = true -} - -var ss2Tests = []struct { - clientCipher *url.Userinfo - serverCipher *url.Userinfo - pass bool -}{ - {nil, nil, false}, - {&url.Userinfo{}, &url.Userinfo{}, false}, - {url.User("abc"), url.User("abc"), false}, - {url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), false}, - - {url.User("aes-128-cfb"), url.User("aes-128-cfb"), true}, - {url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false}, - {url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false}, - {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false}, - {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true}, - - {url.User("aes-192-cfb"), url.User("aes-192-cfb"), true}, - {url.User("aes-192-cfb"), url.UserPassword("aes-192-cfb", "123456"), false}, - {url.UserPassword("aes-192-cfb", "123456"), url.User("aes-192-cfb"), false}, - {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "abc"), false}, - {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), true}, - - {url.User("aes-256-cfb"), url.User("aes-256-cfb"), true}, - {url.User("aes-256-cfb"), url.UserPassword("aes-256-cfb", "123456"), false}, - {url.UserPassword("aes-256-cfb", "123456"), url.User("aes-256-cfb"), false}, - {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "abc"), false}, - {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), true}, - - {url.User("aes-128-ctr"), url.User("aes-128-ctr"), true}, - {url.User("aes-128-ctr"), url.UserPassword("aes-128-ctr", "123456"), false}, - {url.UserPassword("aes-128-ctr", "123456"), url.User("aes-128-ctr"), false}, - {url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "abc"), false}, - {url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "123456"), true}, - - {url.User("aes-192-ctr"), url.User("aes-192-ctr"), true}, - {url.User("aes-192-ctr"), url.UserPassword("aes-192-ctr", "123456"), false}, - {url.UserPassword("aes-192-ctr", "123456"), url.User("aes-192-ctr"), false}, - {url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "abc"), false}, - {url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "123456"), true}, - - {url.User("aes-256-ctr"), url.User("aes-256-ctr"), true}, - {url.User("aes-256-ctr"), url.UserPassword("aes-256-ctr", "123456"), false}, - {url.UserPassword("aes-256-ctr", "123456"), url.User("aes-256-ctr"), false}, - {url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "abc"), false}, - {url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "123456"), true}, - - {url.User("chacha20-ietf"), url.User("chacha20-ietf"), true}, - {url.User("chacha20-ietf"), url.UserPassword("chacha20-ietf", "123456"), false}, - {url.UserPassword("chacha20-ietf", "123456"), url.User("chacha20-ietf"), false}, - {url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "abc"), false}, - {url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "123456"), true}, - - {url.User("xchacha20"), url.User("xchacha20"), true}, - {url.User("xchacha20"), url.UserPassword("xchacha20", "123456"), false}, - {url.UserPassword("xchacha20", "123456"), url.User("xchacha20"), false}, - {url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "abc"), false}, - {url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "123456"), true}, - - {url.User("AEAD_AES_128_GCM"), url.User("AEAD_AES_128_GCM"), true}, - {url.User("AEAD_AES_128_GCM"), url.UserPassword("AEAD_AES_128_GCM", "123456"), false}, - {url.UserPassword("AEAD_AES_128_GCM", "123456"), url.User("AEAD_AES_128_GCM"), false}, - {url.UserPassword("AEAD_AES_128_GCM", "123456"), url.UserPassword("AEAD_AES_128_GCM", "abc"), false}, - {url.UserPassword("AEAD_AES_128_GCM", "123456"), url.UserPassword("AEAD_AES_128_GCM", "123456"), true}, - - {url.User("AEAD_AES_192_GCM"), url.User("AEAD_AES_192_GCM"), true}, - {url.User("AEAD_AES_192_GCM"), url.UserPassword("AEAD_AES_192_GCM", "123456"), false}, - {url.UserPassword("AEAD_AES_192_GCM", "123456"), url.User("AEAD_AES_192_GCM"), false}, - {url.UserPassword("AEAD_AES_192_GCM", "123456"), url.UserPassword("AEAD_AES_192_GCM", "abc"), false}, - {url.UserPassword("AEAD_AES_192_GCM", "123456"), url.UserPassword("AEAD_AES_192_GCM", "123456"), true}, - - {url.User("AEAD_AES_256_GCM"), url.User("AEAD_AES_256_GCM"), true}, - {url.User("AEAD_AES_256_GCM"), url.UserPassword("AEAD_AES_256_GCM", "123456"), false}, - {url.UserPassword("AEAD_AES_256_GCM", "123456"), url.User("AEAD_AES_256_GCM"), false}, - {url.UserPassword("AEAD_AES_256_GCM", "123456"), url.UserPassword("AEAD_AES_256_GCM", "abc"), false}, - {url.UserPassword("AEAD_AES_256_GCM", "123456"), url.UserPassword("AEAD_AES_256_GCM", "123456"), true}, - - {url.User("AEAD_CHACHA20_POLY1305"), url.User("AEAD_CHACHA20_POLY1305"), true}, - {url.User("AEAD_CHACHA20_POLY1305"), url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"), false}, - {url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"), url.User("AEAD_CHACHA20_POLY1305"), false}, - {url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"), url.UserPassword("AEAD_CHACHA20_POLY1305", "abc"), false}, - {url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"), url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"), true}, -} - -var ss2ProxyTests = []struct { - clientCipher *url.Userinfo - serverCipher *url.Userinfo - pass bool -}{ - {nil, nil, false}, - {&url.Userinfo{}, &url.Userinfo{}, false}, - {url.User("abc"), url.User("abc"), false}, - {url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), false}, - - {url.User("aes-128-cfb"), url.User("aes-128-cfb"), false}, - {url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false}, - {url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false}, - // {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false}, - {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true}, -} - -func ss2ProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo *url.Userinfo) error { - ln, err := TCPListener("") - if err != nil { - return err - } - - client := &Client{ - Connector: Shadow2Connector(clientInfo), - Transporter: TCPTransporter(), - } - - server := &Server{ - Handler: Shadow2Handler(UsersHandlerOption(serverInfo)), - Listener: ln, - } - - go server.Run() - defer server.Close() - - return proxyRoundtrip(client, server, targetURL, data) -} - -func TestSS2Proxy(t *testing.T) { - httpSrv := httptest.NewServer(httpTestHandler) - defer httpSrv.Close() - - sendData := make([]byte, 128) - rand.Read(sendData) - - for i, tc := range ss2Tests { - tc := tc - t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { - err := ss2ProxyRoundtrip(httpSrv.URL, sendData, - tc.clientCipher, - tc.serverCipher, - ) - if err == nil { - if !tc.pass { - t.Errorf("#%d should failed", i) - } - } else { - // t.Logf("#%d %v", i, err) - if tc.pass { - t.Errorf("#%d got error: %v", i, err) - } - } - }) - } -} - -func BenchmarkSS2Proxy_AES256(b *testing.B) { - httpSrv := httptest.NewServer(httpTestHandler) - defer httpSrv.Close() - - sendData := make([]byte, 128) - rand.Read(sendData) - - ln, err := TCPListener("") - if err != nil { - b.Error(err) - } - - client := &Client{ - Connector: Shadow2Connector(url.UserPassword("aes-256-cfb", "123456")), - Transporter: TCPTransporter(), - } - - server := &Server{ - Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("aes-256-cfb", "123456"))), - Listener: ln, - } - - go server.Run() - defer server.Close() - - for i := 0; i < b.N; i++ { - if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { - b.Error(err) - } - } -} - -func BenchmarkSS2Proxy_XChacha20(b *testing.B) { - httpSrv := httptest.NewServer(httpTestHandler) - defer httpSrv.Close() - - sendData := make([]byte, 128) - rand.Read(sendData) - - ln, err := TCPListener("") - if err != nil { - b.Error(err) - } - - client := &Client{ - Connector: Shadow2Connector(url.UserPassword("xchacha20", "123456")), - Transporter: TCPTransporter(), - } - - server := &Server{ - Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("xchacha20", "123456"))), - Listener: ln, - } - - go server.Run() - defer server.Close() - - for i := 0; i < b.N; i++ { - if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { - b.Error(err) - } - } -} - -func BenchmarkSS2Proxy_Chacha20_ietf(b *testing.B) { - httpSrv := httptest.NewServer(httpTestHandler) - defer httpSrv.Close() - - sendData := make([]byte, 128) - rand.Read(sendData) - - ln, err := TCPListener("") - if err != nil { - b.Error(err) - } - - client := &Client{ - Connector: Shadow2Connector(url.UserPassword("chacha20-ietf", "123456")), - Transporter: TCPTransporter(), - } - - server := &Server{ - Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("chacha20-ietf", "123456"))), - Listener: ln, - } - - go server.Run() - defer server.Close() - - for i := 0; i < b.N; i++ { - if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { - b.Error(err) - } - } -} - -func BenchmarkSS2Proxy_CHACHA20_IETF_Parallel(b *testing.B) { - httpSrv := httptest.NewServer(httpTestHandler) - defer httpSrv.Close() - - sendData := make([]byte, 128) - rand.Read(sendData) - - ln, err := TCPListener("") - if err != nil { - b.Error(err) - } - - client := &Client{ - Connector: Shadow2Connector(url.UserPassword("chacha20-ietf", "123456")), - Transporter: TCPTransporter(), - } - - server := &Server{ - Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("chacha20-ietf", "123456"))), - Listener: ln, - } - - go server.Run() - defer server.Close() - - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { - b.Error(err) - } - } - }) -} - -func BenchmarkSS2Proxy_AEAD_AES_256_GCM(b *testing.B) { - httpSrv := httptest.NewServer(httpTestHandler) - defer httpSrv.Close() - - sendData := make([]byte, 128) - rand.Read(sendData) - - ln, err := TCPListener("") - if err != nil { - b.Error(err) - } - - client := &Client{ - Connector: Shadow2Connector(url.UserPassword("AEAD_AES_256_GCM", "123456")), - Transporter: TCPTransporter(), - } - - server := &Server{ - Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("AEAD_AES_256_GCM", "123456"))), - Listener: ln, - } - - go server.Run() - defer server.Close() - - for i := 0; i < b.N; i++ { - if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { - b.Error(err) - } - } -} - -func BenchmarkSS2Proxy_AEAD_AES_256_GCM_Parallel(b *testing.B) { - httpSrv := httptest.NewServer(httpTestHandler) - defer httpSrv.Close() - - sendData := make([]byte, 128) - rand.Read(sendData) - - ln, err := TCPListener("") - if err != nil { - b.Error(err) - } - - client := &Client{ - Connector: Shadow2Connector(url.UserPassword("AEAD_AES_256_GCM", "123456")), - Transporter: TCPTransporter(), - } - - server := &Server{ - Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("AEAD_AES_256_GCM", "123456"))), - Listener: ln, - } - - go server.Run() - defer server.Close() - - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { - b.Error(err) - } - } - }) -} - -func BenchmarkSS2Proxy_AEAD_CHACHA20_POLY1305(b *testing.B) { - httpSrv := httptest.NewServer(httpTestHandler) - defer httpSrv.Close() - - sendData := make([]byte, 128) - rand.Read(sendData) - - ln, err := TCPListener("") - if err != nil { - b.Error(err) - } - - client := &Client{ - Connector: Shadow2Connector(url.UserPassword("AEAD_CHACHA20_POLY1305", "123456")), - Transporter: TCPTransporter(), - } - - server := &Server{ - Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"))), - Listener: ln, - } - - go server.Run() - defer server.Close() - - for i := 0; i < b.N; i++ { - if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { - b.Error(err) - } - } -} - -func BenchmarkSS2Proxy_AEAD_CHACHA20_POLY1305_Parallel(b *testing.B) { - httpSrv := httptest.NewServer(httpTestHandler) - defer httpSrv.Close() - - sendData := make([]byte, 128) - rand.Read(sendData) - - ln, err := TCPListener("") - if err != nil { - b.Error(err) - } - - client := &Client{ - Connector: Shadow2Connector(url.UserPassword("AEAD_CHACHA20_POLY1305", "123456")), - Transporter: TCPTransporter(), - } - - server := &Server{ - Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"))), - Listener: ln, - } - - go server.Run() - defer server.Close() - - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { - b.Error(err) - } - } - }) -} diff --git a/ss_test.go b/ss_test.go index 21390ef..c57c6fd 100644 --- a/ss_test.go +++ b/ss_test.go @@ -19,88 +19,118 @@ var ssTests = []struct { serverCipher *url.Userinfo pass bool }{ - {nil, nil, false}, - {&url.Userinfo{}, &url.Userinfo{}, false}, - {url.User("abc"), url.User("abc"), false}, - {url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), false}, + {nil, nil, true}, + {&url.Userinfo{}, &url.Userinfo{}, true}, + {url.User("abc"), url.User("abc"), true}, + {url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), true}, - {url.User("aes-128-cfb"), url.User("aes-128-cfb"), false}, + {url.User("aes-128-cfb"), url.User("aes-128-cfb"), true}, {url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false}, {url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false}, {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false}, {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true}, - {url.User("aes-192-cfb"), url.User("aes-192-cfb"), false}, + {url.User("aes-192-cfb"), url.User("aes-192-cfb"), true}, {url.User("aes-192-cfb"), url.UserPassword("aes-192-cfb", "123456"), false}, {url.UserPassword("aes-192-cfb", "123456"), url.User("aes-192-cfb"), false}, {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "abc"), false}, {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), true}, - {url.User("aes-256-cfb"), url.User("aes-256-cfb"), false}, + {url.User("aes-256-cfb"), url.User("aes-256-cfb"), true}, {url.User("aes-256-cfb"), url.UserPassword("aes-256-cfb", "123456"), false}, {url.UserPassword("aes-256-cfb", "123456"), url.User("aes-256-cfb"), false}, {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "abc"), false}, {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), true}, - {url.User("aes-128-ctr"), url.User("aes-128-ctr"), false}, + {url.User("aes-128-ctr"), url.User("aes-128-ctr"), true}, {url.User("aes-128-ctr"), url.UserPassword("aes-128-ctr", "123456"), false}, {url.UserPassword("aes-128-ctr", "123456"), url.User("aes-128-ctr"), false}, {url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "abc"), false}, {url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "123456"), true}, - {url.User("aes-192-ctr"), url.User("aes-192-ctr"), false}, + {url.User("aes-192-ctr"), url.User("aes-192-ctr"), true}, {url.User("aes-192-ctr"), url.UserPassword("aes-192-ctr", "123456"), false}, {url.UserPassword("aes-192-ctr", "123456"), url.User("aes-192-ctr"), false}, {url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "abc"), false}, {url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "123456"), true}, - {url.User("aes-256-ctr"), url.User("aes-256-ctr"), false}, + {url.User("aes-256-ctr"), url.User("aes-256-ctr"), true}, {url.User("aes-256-ctr"), url.UserPassword("aes-256-ctr", "123456"), false}, {url.UserPassword("aes-256-ctr", "123456"), url.User("aes-256-ctr"), false}, {url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "abc"), false}, {url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "123456"), true}, - {url.User("des-cfb"), url.User("des-cfb"), false}, + {url.User("des-cfb"), url.User("des-cfb"), true}, {url.User("des-cfb"), url.UserPassword("des-cfb", "123456"), false}, {url.UserPassword("des-cfb", "123456"), url.User("des-cfb"), false}, {url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "abc"), false}, {url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "123456"), true}, - {url.User("bf-cfb"), url.User("bf-cfb"), false}, + {url.User("bf-cfb"), url.User("bf-cfb"), true}, {url.User("bf-cfb"), url.UserPassword("bf-cfb", "123456"), false}, {url.UserPassword("bf-cfb", "123456"), url.User("bf-cfb"), false}, {url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "abc"), false}, {url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "123456"), true}, - {url.User("cast5-cfb"), url.User("cast5-cfb"), false}, + {url.User("cast5-cfb"), url.User("cast5-cfb"), true}, {url.User("cast5-cfb"), url.UserPassword("cast5-cfb", "123456"), false}, {url.UserPassword("cast5-cfb", "123456"), url.User("cast5-cfb"), false}, {url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "abc"), false}, {url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "123456"), true}, - {url.User("rc4-md5"), url.User("rc4-md5"), false}, + {url.User("rc4-md5"), url.User("rc4-md5"), true}, {url.User("rc4-md5"), url.UserPassword("rc4-md5", "123456"), false}, {url.UserPassword("rc4-md5", "123456"), url.User("rc4-md5"), false}, {url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "abc"), false}, {url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "123456"), true}, - {url.User("chacha20"), url.User("chacha20"), false}, + {url.User("chacha20"), url.User("chacha20"), true}, {url.User("chacha20"), url.UserPassword("chacha20", "123456"), false}, {url.UserPassword("chacha20", "123456"), url.User("chacha20"), false}, {url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "abc"), false}, {url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "123456"), true}, - {url.User("chacha20-ietf"), url.User("chacha20-ietf"), false}, + {url.User("chacha20-ietf"), url.User("chacha20-ietf"), true}, {url.User("chacha20-ietf"), url.UserPassword("chacha20-ietf", "123456"), false}, {url.UserPassword("chacha20-ietf", "123456"), url.User("chacha20-ietf"), false}, {url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "abc"), false}, {url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "123456"), true}, - {url.User("salsa20"), url.User("salsa20"), false}, + {url.User("salsa20"), url.User("salsa20"), true}, {url.User("salsa20"), url.UserPassword("salsa20", "123456"), false}, {url.UserPassword("salsa20", "123456"), url.User("salsa20"), false}, {url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "abc"), false}, {url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "123456"), true}, + + {url.User("xchacha20"), url.User("xchacha20"), true}, + {url.User("xchacha20"), url.UserPassword("xchacha20", "123456"), false}, + {url.UserPassword("xchacha20", "123456"), url.User("xchacha20"), false}, + {url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "abc"), false}, + {url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "123456"), true}, + + {url.User("CHACHA20-IETF-POLY1305"), url.User("CHACHA20-IETF-POLY1305"), true}, + {url.User("CHACHA20-IETF-POLY1305"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), false}, + {url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.User("CHACHA20-IETF-POLY1305"), false}, + {url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "abc"), false}, + {url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), true}, + + {url.User("AES-128-GCM"), url.User("AES-128-GCM"), true}, + {url.User("AES-128-GCM"), url.UserPassword("AES-128-GCM", "123456"), false}, + {url.UserPassword("AES-128-GCM", "123456"), url.User("AES-128-GCM"), false}, + {url.UserPassword("AES-128-GCM", "123456"), url.UserPassword("AES-128-GCM", "abc"), false}, + {url.UserPassword("AES-128-GCM", "123456"), url.UserPassword("AES-128-GCM", "123456"), true}, + + {url.User("AES-192-GCM"), url.User("AES-192-GCM"), true}, + {url.User("AES-192-GCM"), url.UserPassword("AES-192-GCM", "123456"), false}, + {url.UserPassword("AES-192-GCM", "123456"), url.User("AES-192-GCM"), false}, + {url.UserPassword("AES-192-GCM", "123456"), url.UserPassword("AES-192-GCM", "abc"), false}, + {url.UserPassword("AES-192-GCM", "123456"), url.UserPassword("AES-192-GCM", "123456"), true}, + + {url.User("AES-256-GCM"), url.User("AES-256-GCM"), true}, + {url.User("AES-256-GCM"), url.UserPassword("AES-256-GCM", "123456"), false}, + {url.UserPassword("AES-256-GCM", "123456"), url.User("AES-256-GCM"), false}, + {url.UserPassword("AES-256-GCM", "123456"), url.UserPassword("AES-256-GCM", "abc"), false}, + {url.UserPassword("AES-256-GCM", "123456"), url.UserPassword("AES-256-GCM", "123456"), true}, } var ssProxyTests = []struct { @@ -109,15 +139,19 @@ var ssProxyTests = []struct { pass bool }{ {nil, nil, false}, - {&url.Userinfo{}, &url.Userinfo{}, false}, - {url.User("abc"), url.User("abc"), false}, - {url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), false}, + {&url.Userinfo{}, &url.Userinfo{}, true}, + {url.User("abc"), url.User("abc"), true}, + {url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), true}, - {url.User("aes-128-cfb"), url.User("aes-128-cfb"), false}, + {url.User("aes-128-cfb"), url.User("aes-128-cfb"), true}, {url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false}, {url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false}, - // {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false}, {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true}, + + {url.User("CHACHA20-IETF-POLY1305"), url.User("CHACHA20-IETF-POLY1305"), true}, + {url.User("CHACHA20-IETF-POLY1305"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), false}, + {url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.User("CHACHA20-IETF-POLY1305"), false}, + {url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), true}, } func ssProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo *url.Userinfo) error { @@ -142,7 +176,7 @@ func ssProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, s return proxyRoundtrip(client, server, targetURL, data) } -func TestSSProxy(t *testing.T) { +func TestShadowTCP(t *testing.T) { httpSrv := httptest.NewServer(httpTestHandler) defer httpSrv.Close()