diff --git a/cli/ss-local/main.go b/cli/ss-local/main.go index 4a709af..920ba43 100644 --- a/cli/ss-local/main.go +++ b/cli/ss-local/main.go @@ -32,6 +32,7 @@ import ( "github.com/sagernet/sing/protocol/shadowsocks" "github.com/sagernet/sing/protocol/shadowsocks/shadowaead" "github.com/sagernet/sing/protocol/shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/protocol/shadowsocks/shadowstream" "github.com/sagernet/sing/transport/mixed" "github.com/sagernet/sing/transport/system" "github.com/sirupsen/logrus" @@ -81,6 +82,7 @@ func main() { supportedCiphers = append(supportedCiphers, shadowsocks.MethodNone) supportedCiphers = append(supportedCiphers, shadowaead_2022.List...) supportedCiphers = append(supportedCiphers, shadowaead.List...) + supportedCiphers = append(supportedCiphers, shadowstream.List...) command.Flags().StringVarP(&f.Method, "encrypt-method", "m", "", "Store the cipher.\n\nSupported ciphers:\n\n"+strings.Join(supportedCiphers, "\n")) command.Flags().BoolVar(&f.TCPFastOpen, "fast-open", false, `Enable TCP fast open. @@ -180,7 +182,7 @@ func newClient(f *flags) (*client, error) { if f.ReducedSaltEntropy { rng = &shadowsocks.ReducedEntropyReader{Reader: rng} } - if common.Contains(shadowaead.List, f.Method) { + if common.Contains(shadowstream.List, f.Method) { var key []byte if f.Key != "" { kb, err := base64.StdEncoding.DecodeString(f.Key) @@ -189,7 +191,21 @@ func newClient(f *flags) (*client, error) { } key = kb } - method, err := shadowaead.New(f.Method, key, []byte(f.Password), rng, false) + method, err := shadowstream.New(f.Method, key, []byte(f.Password), rng) + if err != nil { + return nil, err + } + c.method = method + } else if common.Contains(shadowaead.List, f.Method) { + var key []byte + if f.Key != "" { + kb, err := base64.StdEncoding.DecodeString(f.Key) + if err != nil { + return nil, E.Cause(err, "decode key") + } + key = kb + } + method, err := shadowaead.New(f.Method, key, []byte(f.Password), rng) if err != nil { return nil, err } diff --git a/cli/ss-server/main.go b/cli/ss-server/main.go index 12475d7..4b12363 100644 --- a/cli/ss-server/main.go +++ b/cli/ss-server/main.go @@ -143,7 +143,7 @@ func newServer(f *flags) (*server, error) { } key = kb } - service, err := shadowaead.NewService(f.Method, key, []byte(f.Password), random.Default, false, udpTimeout, s) + service, err := shadowaead.NewService(f.Method, key, []byte(f.Password), random.Default, udpTimeout, s) if err != nil { return nil, err } diff --git a/common/metadata/addr.go b/common/metadata/addr.go index ab526bc..cf7b192 100644 --- a/common/metadata/addr.go +++ b/common/metadata/addr.go @@ -125,7 +125,11 @@ func SocksaddrFromNet(ap net.Addr) Socksaddr { if socksAddr, ok := ap.(Socksaddr); ok { return socksAddr } - return SocksaddrFromNetIP(AddrPortFromNet(ap)) + addr := SocksaddrFromNetIP(AddrPortFromNet(ap)) + if addr.IsValid() { + return addr + } + return ParseSocksaddr(ap.String()) } func AddrFromNetAddr(netAddr net.Addr) netip.Addr { diff --git a/common/replay/bloomring.go b/common/replay/bloomring.go deleted file mode 100644 index 5e92327..0000000 --- a/common/replay/bloomring.go +++ /dev/null @@ -1,31 +0,0 @@ -package replay - -import ( - "sync" - - "github.com/v2fly/ss-bloomring" -) - -func NewBloomRing() Filter { - const ( - DefaultSFCapacity = 1e6 - DefaultSFFPR = 1e-6 - DefaultSFSlot = 10 - ) - return &bloomRingFilter{BloomRing: ss_bloomring.NewBloomRing(DefaultSFSlot, DefaultSFCapacity, DefaultSFFPR)} -} - -type bloomRingFilter struct { - sync.Mutex - *ss_bloomring.BloomRing -} - -func (f *bloomRingFilter) Check(sum []byte) bool { - f.Lock() - defer f.Unlock() - if f.Test(sum) { - return false - } - f.Add(sum) - return true -} diff --git a/go.mod b/go.mod index aa28647..b14fd3d 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/cloudflare/cloudflare-go v0.38.0 + github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d github.com/go-acme/lego/v4 v4.6.0 github.com/go-resty/resty/v2 v2.7.0 github.com/klauspost/compress v1.15.3 @@ -16,7 +17,6 @@ require ( github.com/spf13/cobra v1.4.0 github.com/u-root/u-root v0.8.1-0.20220504042106-94cc250573fb github.com/ulikunitz/xz v0.5.10 - github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e github.com/v2fly/v2ray-core/v5 v5.0.6 github.com/vishvananda/netlink v1.2.0-beta golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 @@ -49,7 +49,6 @@ require ( github.com/onsi/ginkgo v1.16.4 // indirect github.com/oschwald/maxminddb-golang v1.9.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect diff --git a/go.sum b/go.sum index 71df938..bea69f9 100644 --- a/go.sum +++ b/go.sum @@ -99,6 +99,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deepmap/oapi-codegen v1.6.1/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE= +github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0= github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= @@ -415,8 +417,6 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA= github.com/refraction-networking/utls v1.1.0 h1:dKXJwSqni/t5csYJ+aQcEgqB7AMWYi6EUc9u3bEmjX0= github.com/refraction-networking/utls v1.1.0/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0= -github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= -github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -506,8 +506,6 @@ github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= -github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/v2fly/v2ray-core/v5 v5.0.6 h1:YTE2Ax2T6BpiDw39iGe9RrvwtID0SbQzEYY/5f3ID/I= github.com/v2fly/v2ray-core/v5 v5.0.6/go.mod h1:Rv8eq9KRQWZt177euFJb7i2MOqrzfokVmuHGhlfo/zw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= diff --git a/protocol/shadowsocks/shadowaead/protocol.go b/protocol/shadowsocks/shadowaead/protocol.go index 7304f30..a9ccfee 100644 --- a/protocol/shadowsocks/shadowaead/protocol.go +++ b/protocol/shadowsocks/shadowaead/protocol.go @@ -7,14 +7,12 @@ import ( "io" "net" "runtime" - "sync" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/replay" "github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/protocol/shadowsocks" "github.com/sagernet/sing/protocol/socks5" @@ -35,14 +33,11 @@ var ( ErrMissingPassword = E.New("missing password") ) -func New(method string, key []byte, password []byte, secureRNG io.Reader, replayFilter bool) (shadowsocks.Method, error) { +func New(method string, key []byte, password []byte, secureRNG io.Reader) (shadowsocks.Method, error) { m := &Method{ name: method, secureRNG: secureRNG, } - if replayFilter { - m.replayFilter = replay.NewBloomRing() - } switch method { case "aes-128-gcm": m.keySaltLength = 16 @@ -103,7 +98,6 @@ type Method struct { constructor func(key []byte) cipher.AEAD key []byte secureRNG io.Reader - replayFilter replay.Filter } func (m *Method) Name() string { @@ -122,11 +116,6 @@ func (m *Method) ReadRequest(upstream io.Reader) (io.Reader, error) { if err != nil { return nil, E.Cause(err, "read salt") } - if m.replayFilter != nil { - if !m.replayFilter.Check(salt) { - return nil, E.New("salt not unique") - } - } key := Kdf(m.key, salt, m.keySaltLength) defer runtime.KeepAlive(key) return NewReader(upstream, m.constructor(common.Dup(key)), MaxPacketSize), nil @@ -197,7 +186,6 @@ type clientConn struct { method *Method destination M.Socksaddr - access sync.Mutex reader *Reader writer *Writer } @@ -256,11 +244,6 @@ func (c *clientConn) readResponse() error { if err != nil { return err } - if c.method.replayFilter != nil { - if !c.method.replayFilter.Check(salt) { - return E.New("salt not unique") - } - } key := Kdf(c.method.key, salt, c.method.keySaltLength) defer runtime.KeepAlive(key) c.reader = NewReader( @@ -290,13 +273,6 @@ func (c *clientConn) Write(p []byte) (n int, err error) { return c.writer.Write(p) } - c.access.Lock() - - if c.writer != nil { - c.access.Unlock() - return c.writer.Write(p) - } - err = c.writeRequest(p) if err != nil { return diff --git a/protocol/shadowsocks/shadowaead/service.go b/protocol/shadowsocks/shadowaead/service.go index ab80bb7..1393cd8 100644 --- a/protocol/shadowsocks/shadowaead/service.go +++ b/protocol/shadowsocks/shadowaead/service.go @@ -14,7 +14,6 @@ import ( E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/replay" "github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/udpnat" "github.com/sagernet/sing/protocol/shadowsocks" @@ -28,21 +27,17 @@ type Service struct { constructor func(key []byte) cipher.AEAD key []byte secureRNG io.Reader - replayFilter replay.Filter handler shadowsocks.Handler udpNat *udpnat.Service[netip.AddrPort] } -func NewService(method string, key []byte, password []byte, secureRNG io.Reader, replayFilter bool, udpTimeout int64, handler shadowsocks.Handler) (shadowsocks.Service, error) { +func NewService(method string, key []byte, password []byte, secureRNG io.Reader, udpTimeout int64, handler shadowsocks.Handler) (shadowsocks.Service, error) { s := &Service{ name: method, secureRNG: secureRNG, handler: handler, udpNat: udpnat.New[netip.AddrPort](udpTimeout, handler), } - if replayFilter { - s.replayFilter = replay.NewBloomRing() - } switch method { case "aes-128-gcm": s.keySaltLength = 16 diff --git a/protocol/shadowsocks/shadowaead_2022/protocol.go b/protocol/shadowsocks/shadowaead_2022/protocol.go index 1d5dea7..02fd75a 100644 --- a/protocol/shadowsocks/shadowaead_2022/protocol.go +++ b/protocol/shadowsocks/shadowaead_2022/protocol.go @@ -10,7 +10,6 @@ import ( "math/rand" "net" "runtime" - "sync" "sync/atomic" "time" @@ -188,9 +187,6 @@ type clientConn struct { method *Method destination M.Socksaddr - request sync.Mutex - response sync.Mutex - requestSalt []byte reader *shadowaead.Reader @@ -283,13 +279,6 @@ func (c *clientConn) readResponse() error { return nil } - c.response.Lock() - defer c.response.Unlock() - - if c.reader != nil { - return nil - } - _salt := make([]byte, KeySaltSize) salt := common.Dup(_salt) _, err := io.ReadFull(c.Conn, salt) @@ -358,36 +347,24 @@ func (c *clientConn) WriteTo(w io.Writer) (n int64, err error) { if err = c.readResponse(); err != nil { return } - return c.reader.WriteTo(w) } func (c *clientConn) Write(p []byte) (n int, err error) { - if c.writer != nil { - return c.writer.Write(p) + if c.writer == nil { + err = c.writeRequest(p) + if err == nil { + n = len(p) + } + return } - - c.request.Lock() - - if c.writer != nil { - c.request.Unlock() - return c.writer.Write(p) - } - - defer c.request.Unlock() - - err = c.writeRequest(p) - if err == nil { - n = len(p) - } - return + return c.writer.Write(p) } func (c *clientConn) ReadFrom(r io.Reader) (n int64, err error) { if c.writer == nil { return rw.ReadFrom0(c, r) } - return c.writer.ReadFrom(r) } diff --git a/protocol/shadowsocks/shadowstream/protocol.go b/protocol/shadowsocks/shadowstream/protocol.go new file mode 100644 index 0000000..80ad42b --- /dev/null +++ b/protocol/shadowsocks/shadowstream/protocol.go @@ -0,0 +1,418 @@ +package shadowstream + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/md5" + "crypto/rc4" + "io" + "net" + "os" + "runtime" + + "github.com/dgryski/go-camellia" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/shadowsocks" + "github.com/sagernet/sing/protocol/shadowsocks/shadowaead" + "github.com/sagernet/sing/protocol/socks5" + "golang.org/x/crypto/blowfish" + "golang.org/x/crypto/cast5" + "golang.org/x/crypto/chacha20" +) + +var List = []string{ + "aes-128-ctr", + "aes-192-ctr", + "aes-256-ctr", + "aes-128-cfb", + "aes-192-cfb", + "aes-256-cfb", + "camellia-128-cfb", + "camellia-192-cfb", + "camellia-256-cfb", + "bf-cfb", + "cast5-cfb", + "des-cfb", + "rc4", + "rc4-md5", + "chacha20", + "chacha20-ietf", + "xchacha20", +} + +type Method struct { + name string + keyLength int + saltLength int + encryptConstructor func(key []byte, salt []byte) (cipher.Stream, error) + decryptConstructor func(key []byte, salt []byte) (cipher.Stream, error) + key []byte + secureRNG io.Reader +} + +func New(method string, key []byte, password []byte, secureRNG io.Reader) (shadowsocks.Method, error) { + m := &Method{ + name: method, + secureRNG: secureRNG, + } + switch method { + case "aes-128-ctr": + m.keyLength = 16 + m.saltLength = aes.BlockSize + m.encryptConstructor = blockStream(aes.NewCipher, cipher.NewCTR) + m.decryptConstructor = blockStream(aes.NewCipher, cipher.NewCTR) + case "aes-192-ctr": + m.keyLength = 24 + m.saltLength = aes.BlockSize + m.encryptConstructor = blockStream(aes.NewCipher, cipher.NewCTR) + m.decryptConstructor = blockStream(aes.NewCipher, cipher.NewCTR) + case "aes-256-ctr": + m.keyLength = 32 + m.saltLength = aes.BlockSize + m.encryptConstructor = blockStream(aes.NewCipher, cipher.NewCTR) + m.decryptConstructor = blockStream(aes.NewCipher, cipher.NewCTR) + case "aes-128-cfb": + m.keyLength = 16 + m.saltLength = aes.BlockSize + m.encryptConstructor = blockStream(aes.NewCipher, cipher.NewCFBEncrypter) + m.decryptConstructor = blockStream(aes.NewCipher, cipher.NewCFBDecrypter) + case "aes-192-cfb": + m.keyLength = 24 + m.saltLength = aes.BlockSize + m.encryptConstructor = blockStream(aes.NewCipher, cipher.NewCFBEncrypter) + m.decryptConstructor = blockStream(aes.NewCipher, cipher.NewCFBDecrypter) + case "aes-256-cfb": + m.keyLength = 32 + m.saltLength = aes.BlockSize + m.encryptConstructor = blockStream(aes.NewCipher, cipher.NewCFBEncrypter) + m.decryptConstructor = blockStream(aes.NewCipher, cipher.NewCFBDecrypter) + case "camellia-128-cfb": + m.keyLength = 16 + m.saltLength = camellia.BlockSize + m.encryptConstructor = blockStream(camellia.New, cipher.NewCFBEncrypter) + m.decryptConstructor = blockStream(camellia.New, cipher.NewCFBDecrypter) + case "camellia-192-cfb": + m.keyLength = 24 + m.saltLength = camellia.BlockSize + m.encryptConstructor = blockStream(camellia.New, cipher.NewCFBEncrypter) + m.decryptConstructor = blockStream(camellia.New, cipher.NewCFBDecrypter) + case "camellia-256-cfb": + m.keyLength = 32 + m.saltLength = camellia.BlockSize + m.encryptConstructor = blockStream(camellia.New, cipher.NewCFBEncrypter) + m.decryptConstructor = blockStream(camellia.New, cipher.NewCFBDecrypter) + case "bf-cfb": + m.keyLength = 16 + m.saltLength = blowfish.BlockSize + m.encryptConstructor = blockStream(func(key []byte) (cipher.Block, error) { return blowfish.NewCipher(key) }, cipher.NewCFBEncrypter) + m.decryptConstructor = blockStream(func(key []byte) (cipher.Block, error) { return blowfish.NewCipher(key) }, cipher.NewCFBDecrypter) + case "cast5-cfb": + m.keyLength = 16 + m.saltLength = cast5.BlockSize + m.encryptConstructor = blockStream(func(key []byte) (cipher.Block, error) { return cast5.NewCipher(key) }, cipher.NewCFBEncrypter) + m.decryptConstructor = blockStream(func(key []byte) (cipher.Block, error) { return cast5.NewCipher(key) }, cipher.NewCFBDecrypter) + case "des-cfb": + m.keyLength = 8 + m.saltLength = des.BlockSize + m.encryptConstructor = blockStream(des.NewCipher, cipher.NewCFBEncrypter) + m.decryptConstructor = blockStream(des.NewCipher, cipher.NewCFBDecrypter) + case "rc4": + m.keyLength = 16 + m.saltLength = 0 + m.encryptConstructor = func(key []byte, salt []byte) (cipher.Stream, error) { + return rc4.NewCipher(key) + } + m.decryptConstructor = func(key []byte, salt []byte) (cipher.Stream, error) { + return rc4.NewCipher(key) + } + case "rc4-md5": + m.keyLength = 16 + m.saltLength = 0 + m.encryptConstructor = func(key []byte, salt []byte) (cipher.Stream, error) { + h := md5.New() + h.Write(key) + h.Write(salt) + return rc4.NewCipher(h.Sum(nil)) + } + m.decryptConstructor = func(key []byte, salt []byte) (cipher.Stream, error) { + h := md5.New() + h.Write(key) + h.Write(salt) + return rc4.NewCipher(h.Sum(nil)) + } + case "chacha20", "chacha20-ietf": + m.keyLength = chacha20.KeySize + m.saltLength = chacha20.NonceSize + m.encryptConstructor = func(key []byte, salt []byte) (cipher.Stream, error) { + return chacha20.NewUnauthenticatedCipher(key, salt) + } + m.decryptConstructor = func(key []byte, salt []byte) (cipher.Stream, error) { + return chacha20.NewUnauthenticatedCipher(key, salt) + } + case "xchacha20": + m.keyLength = chacha20.KeySize + m.saltLength = chacha20.NonceSizeX + m.encryptConstructor = func(key []byte, salt []byte) (cipher.Stream, error) { + return chacha20.NewUnauthenticatedCipher(key, salt) + } + m.decryptConstructor = func(key []byte, salt []byte) (cipher.Stream, error) { + return chacha20.NewUnauthenticatedCipher(key, salt) + } + default: + return nil, os.ErrInvalid + } + if len(key) == m.keyLength { + m.key = key + } else if len(key) > 0 { + return nil, shadowaead.ErrBadKey + } else if len(password) > 0 { + m.key = shadowsocks.Key(password, m.keyLength) + } else { + return nil, shadowaead.ErrMissingPassword + } + return m, nil +} + +func blockStream(blockCreator func(key []byte) (cipher.Block, error), streamCreator func(block cipher.Block, iv []byte) cipher.Stream) func([]byte, []byte) (cipher.Stream, error) { + return func(key []byte, iv []byte) (cipher.Stream, error) { + block, err := blockCreator(key) + if err != nil { + return nil, err + } + return streamCreator(block, iv), err + } +} + +func (m *Method) Name() string { + return m.name +} + +func (m *Method) KeyLength() int { + return m.keyLength +} + +func (m *Method) DialConn(conn net.Conn, destination M.Socksaddr) (net.Conn, error) { + shadowsocksConn := &clientConn{ + Conn: conn, + method: m, + destination: destination, + } + return shadowsocksConn, shadowsocksConn.writeRequest(nil) +} + +func (m *Method) DialEarlyConn(conn net.Conn, destination M.Socksaddr) net.Conn { + return &clientConn{ + Conn: conn, + method: m, + destination: destination, + } +} + +func (m *Method) DialPacketConn(conn net.Conn) N.NetPacketConn { + return &clientPacketConn{m, conn} +} + +type clientConn struct { + net.Conn + + method *Method + destination M.Socksaddr + + readStream cipher.Stream + writeStream cipher.Stream +} + +func (c *clientConn) writeRequest(payload []byte) error { + _buffer := buf.Make(c.method.keyLength + socks5.AddressSerializer.AddrPortLen(c.destination) + len(payload)) + defer runtime.KeepAlive(_buffer) + buffer := buf.With(common.Dup(_buffer)) + + salt := buffer.Extend(c.method.keyLength) + common.Must1(io.ReadFull(c.method.secureRNG, salt)) + + key := shadowaead.Kdf(c.method.key, salt, c.method.keyLength) + writer, err := c.method.encryptConstructor(c.method.key, salt) + if err != nil { + return err + } + runtime.KeepAlive(key) + + err = socks5.AddressSerializer.WriteAddrPort(buffer, c.destination) + if err != nil { + return err + } + _, err = buffer.Write(payload) + if err != nil { + return err + } + + _, err = c.Conn.Write(buffer.Bytes()) + if err != nil { + return err + } + + c.writeStream = writer + return nil +} + +func (c *clientConn) readResponse() error { + if c.readStream != nil { + return nil + } + _salt := buf.Make(c.method.keyLength) + defer runtime.KeepAlive(_salt) + salt := common.Dup(_salt) + _, err := io.ReadFull(c.Conn, salt) + if err != nil { + return err + } + key := shadowaead.Kdf(c.method.key, salt, c.method.keyLength) + defer runtime.KeepAlive(key) + c.readStream, err = c.method.decryptConstructor(common.Dup(key), salt) + if err != nil { + return err + } + return nil +} + +func (c *clientConn) Read(p []byte) (n int, err error) { + if err = c.readResponse(); err != nil { + return + } + n, err = c.Conn.Read(p) + if err != nil { + return 0, err + } + c.readStream.XORKeyStream(p[:n], p[:n]) + return +} + +func (c *clientConn) Write(p []byte) (n int, err error) { + if c.writeStream == nil { + err = c.writeRequest(p) + if err == nil { + n = len(p) + } + return + } + + c.writeStream.XORKeyStream(p, p) + return c.Conn.Write(p) +} + +func (c *clientConn) UpstreamReader() io.Reader { + return c.Conn +} + +func (c *clientConn) ReaderReplaceable() bool { + return false +} + +func (c *clientConn) UpstreamWriter() io.Writer { + return c.Conn +} + +func (c *clientConn) WriterReplaceable() bool { + return false +} + +type clientPacketConn struct { + *Method + net.Conn +} + +func (c *clientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + header := buf.With(buffer.ExtendHeader(c.keyLength + socks5.AddressSerializer.AddrPortLen(destination))) + common.Must1(header.ReadFullFrom(c.secureRNG, c.keyLength)) + err := socks5.AddressSerializer.WriteAddrPort(header, destination) + if err != nil { + return err + } + stream, err := c.encryptConstructor(c.key, buffer.To(c.keyLength)) + if err != nil { + return err + } + stream.XORKeyStream(buffer.From(c.keyLength), buffer.From(c.keyLength)) + return common.Error(c.Write(buffer.Bytes())) +} + +func (c *clientPacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) { + n, err := c.Read(buffer.FreeBytes()) + if err != nil { + return M.Socksaddr{}, err + } + buffer.Truncate(n) + stream, err := c.decryptConstructor(c.key, buffer.To(c.keyLength)) + if err != nil { + return M.Socksaddr{}, err + } + stream.XORKeyStream(buffer.From(c.keyLength), buffer.From(c.keyLength)) + buffer.Advance(c.keyLength) + return socks5.AddressSerializer.ReadAddrPort(buffer) +} + +func (c *clientPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + n, err = c.Read(p) + if err != nil { + return + } + stream, err := c.decryptConstructor(c.key, p[:c.keyLength]) + if err != nil { + return + } + buffer := buf.With(p[c.keyLength:n]) + stream.XORKeyStream(buffer.Bytes(), buffer.Bytes()) + destination, err := socks5.AddressSerializer.ReadAddrPort(buffer) + if err != nil { + return + } + addr = destination.UDPAddr() + n = copy(p, buffer.Bytes()) + return +} + +func (c *clientPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + destination := M.SocksaddrFromNet(addr) + _buffer := buf.Make(c.keyLength + socks5.AddressSerializer.AddrPortLen(destination) + len(p)) + defer runtime.KeepAlive(_buffer) + buffer := buf.With(common.Dup(_buffer)) + common.Must1(buffer.ReadFullFrom(c.secureRNG, c.keyLength)) + err = socks5.AddressSerializer.WriteAddrPort(buffer, M.SocksaddrFromNet(addr)) + if err != nil { + return + } + _, err = buffer.Write(p) + if err != nil { + return + } + stream, err := c.encryptConstructor(c.key, buffer.To(c.keyLength)) + if err != nil { + return + } + stream.XORKeyStream(buffer.From(c.keyLength), buffer.From(c.keyLength)) + _, err = c.Write(buffer.Bytes()) + if err != nil { + return + } + return len(p), nil +} + +func (c *clientPacketConn) UpstreamReader() io.Reader { + return c.Conn +} + +func (c *clientPacketConn) ReaderReplaceable() bool { + return false +} + +func (c *clientPacketConn) UpstreamWriter() io.Writer { + return c.Conn +} + +func (c *clientPacketConn) WriterReplaceable() bool { + return false +}