diff --git a/README.md b/README.md index 3c9c60b..3ad1e51 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Actions Status](https://img.shields.io/github/actions/workflow/status/nadoo/glider/build.yml?branch=dev&style=flat-square)](https://github.com/nadoo/glider/actions) [![DockerHub](https://img.shields.io/docker/image-size/nadoo/glider?color=blue&label=docker&style=flat-square)](https://hub.docker.com/r/nadoo/glider) -glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq). +glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features. we can set up local listeners as proxy servers, and forward requests to internet via forwarders. @@ -58,7 +58,6 @@ we can set up local listeners as proxy servers, and forward requests to internet |AnyTLSc |√| |√|√|anytls cleartext(without tls) |VLESS |√| |√|√|client & server |VMess | | |√|√|client only -|SSR | | |√| |client only |SSH | | |√| |client only |SOCKS4 | | |√| |client only |SOCKS4A | | |√| |client only @@ -120,7 +119,7 @@ OPTION: -checkdisabledonly check disabled fowarders only -checkinterval int - fowarder check interval(seconds) (default 30) + fowarder check interval(seconds) (default 30)ß -checklatencysamples int use the average latency of the latest N checks (default 10) -checktimeout int @@ -200,7 +199,7 @@ URL: SCHEME: listen : anytls anytlsc http kcp mixed pxyproto redir redir6 smux sni socks5 ss tcp tls tproxy trojan trojanc udp unix vless vsock ws wss - forward: anytls anytlsc direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh ssr tcp tls trojan trojanc udp unix vless vmess vsock ws wss + forward: anytls anytlsc direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh tcp tls trojan trojanc udp unix vless vmess vsock ws wss Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme. @@ -304,10 +303,6 @@ SSH scheme: ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS] timeout: timeout of ssh handshake and channel operation, default: 5 --- -SSR scheme: - ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz - -- TLS client scheme: tls://host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&alpn=proto1][&alpn=proto2] diff --git a/config/glider.conf.example b/config/glider.conf.example index df4c985..f294d2a 100644 --- a/config/glider.conf.example +++ b/config/glider.conf.example @@ -115,9 +115,6 @@ listen=127.0.0.1:8443 # SS proxy as forwarder # forward=ss://method:pass@1.1.1.1:8443 -# SSR proxy as forwarder -# forward=ssr://method:pass@1.1.1.1:8443?protocol=auth_aes128_md5&protocol_param=xxx&obfs=tls1.2_ticket_auth&obfs_param=yyy - # ssh forwarder # forward=ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS] # forward=ssh://root:pass@host:port diff --git a/feature.go b/feature.go index 9489a92..58c1ac4 100644 --- a/feature.go +++ b/feature.go @@ -17,7 +17,6 @@ import ( _ "github.com/nadoo/glider/proxy/socks5" _ "github.com/nadoo/glider/proxy/ss" _ "github.com/nadoo/glider/proxy/ssh" - _ "github.com/nadoo/glider/proxy/ssr" _ "github.com/nadoo/glider/proxy/tcp" _ "github.com/nadoo/glider/proxy/tls" _ "github.com/nadoo/glider/proxy/trojan" diff --git a/go.mod b/go.mod index 1acbf85..0282ca5 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,6 @@ go 1.26 require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da - github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d - github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb - github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 github.com/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e github.com/nadoo/conflag v0.3.1 github.com/nadoo/ipset v0.5.0 @@ -16,7 +13,6 @@ require ( ) require ( - github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/reedsolomon v1.14.1 // indirect github.com/pierrec/lz4/v4 v4.1.27 // indirect diff --git a/go.sum b/go.sum index 1fb06c8..2368d52 100644 --- a/go.sum +++ b/go.sum @@ -7,14 +7,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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-idea v0.0.0-20170306091226-d2fb45a411fb h1:zXpN5126w/mhECTkqazBkrOJIMatbPP71aSIDR5UuW4= -github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb/go.mod h1:F7WkpqJj9t98ePxB/WJGQTIDeOVPuSJ3qdn6JUjg170= -github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W9dH5xgd/6KjzEACKHBVGQ33czc0= -github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI= -github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk= -github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= diff --git a/proxy/ssr/internal/cipher/cipher.go b/proxy/ssr/internal/cipher/cipher.go deleted file mode 100644 index 7a88aac..0000000 --- a/proxy/ssr/internal/cipher/cipher.go +++ /dev/null @@ -1,342 +0,0 @@ -package cipher - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/des" - "crypto/md5" - "crypto/rand" - "crypto/rc4" - "encoding/binary" - "errors" - - "github.com/aead/chacha20" - "github.com/dgryski/go-camellia" - "github.com/dgryski/go-idea" - "github.com/dgryski/go-rc2" - "golang.org/x/crypto/blowfish" - "golang.org/x/crypto/cast5" - "golang.org/x/crypto/salsa20/salsa" - - "github.com/nadoo/glider/pkg/pool" - "github.com/nadoo/glider/proxy/ssr/internal/tools" -) - -var errEmptyPassword = errors.New("empty key") - -type DecOrEnc int - -const ( - Decrypt DecOrEnc = iota - Encrypt -) - -func newCTRStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - if err != nil { - return nil, err - } - return cipher.NewCTR(block, iv), nil -} - -func newAESCTRStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := aes.NewCipher(key) - return newCTRStream(block, err, key, iv, doe) -} - -func newOFBStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - if err != nil { - return nil, err - } - return cipher.NewCTR(block, iv), nil -} - -func newAESOFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := aes.NewCipher(key) - return newOFBStream(block, err, key, iv, doe) -} - -func newCFBStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - if err != nil { - return nil, err - } - if doe == Encrypt { - return cipher.NewCFBEncrypter(block, iv), nil - } else { - return cipher.NewCFBDecrypter(block, iv), nil - } -} - -func newAESCFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := aes.NewCipher(key) - return newCFBStream(block, err, key, iv, doe) -} - -func newDESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := des.NewCipher(key) - return newCFBStream(block, err, key, iv, doe) -} - -func newBlowFishStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := blowfish.NewCipher(key) - return newCFBStream(block, err, key, iv, doe) -} - -func newCast5Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := cast5.NewCipher(key) - return newCFBStream(block, err, key, iv, doe) -} - -func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { - h := md5.New() - h.Write(key) - h.Write(iv) - rc4key := h.Sum(nil) - - return rc4.NewCipher(rc4key) -} - -func newChaCha20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { - return chacha20.NewCipher(iv, key) -} - -func newChacha20IETFStream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { - return chacha20.NewCipher(iv, key) -} - -type salsaStreamCipher struct { - nonce [8]byte - key [32]byte - counter int -} - -func (c *salsaStreamCipher) XORKeyStream(dst, src []byte) { - var buf []byte - padLen := c.counter % 64 - dataSize := len(src) + padLen - if cap(dst) >= dataSize { - buf = dst[:dataSize] - // nadoo: comment out codes here to use pool buffer - // modify start --> - // } else if leakybuf.GlobalLeakyBufSize >= dataSize { - // buf = leakybuf.GlobalLeakyBuf.Get() - // defer leakybuf.GlobalLeakyBuf.Put(buf) - // buf = buf[:dataSize] - // } else { - // buf = make([]byte, dataSize) - // } - } else { - buf = pool.GetBuffer(dataSize) - defer pool.PutBuffer(buf) - } - // --> modify end - - var subNonce [16]byte - copy(subNonce[:], c.nonce[:]) - binary.LittleEndian.PutUint64(subNonce[len(c.nonce):], uint64(c.counter/64)) - - // It's difficult to avoid data copy here. src or dst maybe slice from - // Conn.Read/Write, which can't have padding. - copy(buf[padLen:], src[:]) - salsa.XORKeyStream(buf, buf, &subNonce, &c.key) - copy(dst, buf[padLen:]) - - c.counter += len(src) -} - -func newSalsa20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { - var c salsaStreamCipher - copy(c.nonce[:], iv[:8]) - copy(c.key[:], key[:32]) - return &c, nil -} - -func newCamelliaStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := camellia.New(key) - return newCFBStream(block, err, key, iv, doe) -} - -func newIdeaStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := idea.NewCipher(key) - return newCFBStream(block, err, key, iv, doe) -} - -func newRC2Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := rc2.New(key, 16) - return newCFBStream(block, err, key, iv, doe) -} - -func newRC4Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - return rc4.NewCipher(key) -} - -func newSeedStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - // TODO: SEED block cipher implementation is required - block, err := rc2.New(key, 16) - return newCFBStream(block, err, key, iv, doe) -} - -type NoneStream struct { - cipher.Stream -} - -func (*NoneStream) XORKeyStream(dst, src []byte) { - copy(dst, src) -} - -func newNoneStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - return new(NoneStream), nil -} - -type cipherInfo struct { - keyLen int - ivLen int - newStream func(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) -} - -var streamCipherMethod = map[string]*cipherInfo{ - "aes-128-cfb": {16, 16, newAESCFBStream}, - "aes-192-cfb": {24, 16, newAESCFBStream}, - "aes-256-cfb": {32, 16, newAESCFBStream}, - "aes-128-ctr": {16, 16, newAESCTRStream}, - "aes-192-ctr": {24, 16, newAESCTRStream}, - "aes-256-ctr": {32, 16, newAESCTRStream}, - "aes-128-ofb": {16, 16, newAESOFBStream}, - "aes-192-ofb": {24, 16, newAESOFBStream}, - "aes-256-ofb": {32, 16, newAESOFBStream}, - "des-cfb": {8, 8, newDESStream}, - "bf-cfb": {16, 8, newBlowFishStream}, - "cast5-cfb": {16, 8, newCast5Stream}, - "rc4-md5": {16, 16, newRC4MD5Stream}, - "rc4-md5-6": {16, 6, newRC4MD5Stream}, - "chacha20": {32, 8, newChaCha20Stream}, - "chacha20-ietf": {32, 12, newChacha20IETFStream}, - "salsa20": {32, 8, newSalsa20Stream}, - "camellia-128-cfb": {16, 16, newCamelliaStream}, - "camellia-192-cfb": {24, 16, newCamelliaStream}, - "camellia-256-cfb": {32, 16, newCamelliaStream}, - "idea-cfb": {16, 8, newIdeaStream}, - "rc2-cfb": {16, 8, newRC2Stream}, - "seed-cfb": {16, 8, newSeedStream}, - "rc4": {16, 0, newRC4Stream}, - "none": {16, 0, newNoneStream}, -} - -func CheckCipherMethod(method string) error { - if method == "" { - method = "rc4-md5" - } - _, ok := streamCipherMethod[method] - if !ok { - return errors.New("Unsupported encryption method: " + method) - } - return nil -} - -type StreamCipher struct { - enc cipher.Stream - dec cipher.Stream - key []byte - info *cipherInfo - iv []byte -} - -// NewStreamCipher creates a cipher that can be used in Dial() etc. -// Use cipher.Copy() to create a new cipher with the same method and password -// to avoid the cost of repeated cipher initialization. -func NewStreamCipher(method, password string) (c *StreamCipher, err error) { - if password == "" { - return nil, errEmptyPassword - } - if method == "" { - method = "rc4-md5" - } - mi, ok := streamCipherMethod[method] - if !ok { - return nil, errors.New("Unsupported encryption method: " + method) - } - - key := tools.EVPBytesToKey(password, mi.keyLen) - - c = &StreamCipher{key: key, info: mi} - - return c, nil -} - -func (c *StreamCipher) EncryptInited() bool { - return c.enc != nil -} - -func (c *StreamCipher) DecryptInited() bool { - return c.dec != nil -} - -// Initializes the block cipher with CFB mode, returns IV. -func (c *StreamCipher) InitEncrypt() (iv []byte, err error) { - if c.iv == nil { - iv = make([]byte, c.info.ivLen) - rand.Read(iv) - c.iv = iv - } else { - iv = c.iv - } - c.enc, err = c.info.newStream(c.key, iv, Encrypt) - return -} - -func (c *StreamCipher) InitDecrypt(iv []byte) (err error) { - c.dec, err = c.info.newStream(c.key, iv, Decrypt) - return -} - -func (c *StreamCipher) Encrypt(dst, src []byte) { - c.enc.XORKeyStream(dst, src) -} - -func (c *StreamCipher) Decrypt(dst, src []byte) { - c.dec.XORKeyStream(dst, src) -} - -// Copy creates a new cipher at it's initial state. -func (c *StreamCipher) Copy() *StreamCipher { - // This optimization maybe not necessary. But without this function, we - // need to maintain a table cache for newTableCipher and use lock to - // protect concurrent access to that cache. - - // AES and DES ciphers does not return specific types, so it's difficult - // to create copy. But their initialization time is less than 4000ns on my - // 2.26 GHz Intel Core 2 Duo processor. So no need to worry. - - // Currently, blow-fish and cast5 initialization cost is an order of - // magnitude slower than other ciphers. (I'm not sure whether this is - // because the current implementation is not highly optimized, or this is - // the nature of the algorithm.) - - nc := *c - nc.enc = nil - nc.dec = nil - return &nc -} - -func (c *StreamCipher) Key() []byte { - return c.key -} - -func (c *StreamCipher) IV() []byte { - return c.iv -} - -func (c *StreamCipher) SetIV(iv []byte) { - c.iv = iv -} - -func (c *StreamCipher) SetKey(key []byte) { - c.key = key -} - -func (c *StreamCipher) InfoIVLen() int { - return c.info.ivLen -} - -func (c *StreamCipher) InfoKeyLen() int { - return c.info.keyLen -} diff --git a/proxy/ssr/internal/client.go b/proxy/ssr/internal/client.go deleted file mode 100644 index 4109350..0000000 --- a/proxy/ssr/internal/client.go +++ /dev/null @@ -1,228 +0,0 @@ -// source code from https://github.com/v2rayA/shadowsocksR -// Just copy here to use glider's builtin buffer pool. -// As this protocol hasn't been maintained since 2017, it doesn't deserve our research to rewrite it. - -package internal - -import ( - "bytes" - "errors" - "fmt" - "net" - - "github.com/nadoo/glider/pkg/pool" - "github.com/nadoo/glider/proxy" - "github.com/nadoo/glider/proxy/ssr/internal/cipher" - "github.com/nadoo/glider/proxy/ssr/internal/obfs" - "github.com/nadoo/glider/proxy/ssr/internal/protocol" -) - -var bufSize = proxy.TCPBufSize - -// SSTCPConn the struct that override the net.Conn methods -type SSTCPConn struct { - net.Conn - *cipher.StreamCipher - IObfs obfs.IObfs - IProtocol protocol.IProtocol - readBuf []byte - underPostdecryptBuf *bytes.Buffer - readIndex uint64 - decryptedBuf *bytes.Buffer - writeBuf []byte - lastReadError error -} - -func NewSSTCPConn(c net.Conn, cipher *cipher.StreamCipher) *SSTCPConn { - return &SSTCPConn{ - Conn: c, - StreamCipher: cipher, - readBuf: pool.GetBuffer(bufSize), - decryptedBuf: pool.GetBytesBuffer(), - underPostdecryptBuf: pool.GetBytesBuffer(), - writeBuf: pool.GetBuffer(bufSize), - } -} - -func (c *SSTCPConn) Close() error { - pool.PutBuffer(c.readBuf) - pool.PutBytesBuffer(c.decryptedBuf) - pool.PutBytesBuffer(c.underPostdecryptBuf) - pool.PutBuffer(c.writeBuf) - return c.Conn.Close() -} - -func (c *SSTCPConn) GetIv() (iv []byte) { - iv = make([]byte, len(c.IV())) - copy(iv, c.IV()) - return -} - -func (c *SSTCPConn) GetKey() (key []byte) { - key = make([]byte, len(c.Key())) - copy(key, c.Key()) - return -} - -func (c *SSTCPConn) initEncryptor(b []byte) (iv []byte, err error) { - if !c.EncryptInited() { - iv, err = c.InitEncrypt() - if err != nil { - return nil, err - } - - overhead := c.IObfs.GetOverhead() + c.IProtocol.GetOverhead() - // should initialize obfs/protocol now, because iv is ready now - obfsServerInfo := c.IObfs.GetServerInfo() - obfsServerInfo.SetHeadLen(b, 30) - obfsServerInfo.IV, obfsServerInfo.IVLen = c.IV(), c.InfoIVLen() - obfsServerInfo.Key, obfsServerInfo.KeyLen = c.Key(), c.InfoKeyLen() - obfsServerInfo.Overhead = overhead - c.IObfs.SetServerInfo(obfsServerInfo) - - protocolServerInfo := c.IProtocol.GetServerInfo() - protocolServerInfo.SetHeadLen(b, 30) - protocolServerInfo.IV, protocolServerInfo.IVLen = c.IV(), c.InfoIVLen() - protocolServerInfo.Key, protocolServerInfo.KeyLen = c.Key(), c.InfoKeyLen() - protocolServerInfo.Overhead = overhead - c.IProtocol.SetServerInfo(protocolServerInfo) - } - return -} - -func (c *SSTCPConn) Read(b []byte) (n int, err error) { - for { - n, err = c.doRead(b) - if b == nil || n != 0 || err != nil { - return n, err - } - } -} - -func (c *SSTCPConn) doRead(b []byte) (n int, err error) { - if c.decryptedBuf.Len() > 0 { - return c.decryptedBuf.Read(b) - } - - n, err = c.Conn.Read(c.readBuf) - if n == 0 || err != nil { - return n, err - } - - decodedData, needSendBack, err := c.IObfs.Decode(c.readBuf[:n]) - if err != nil { - return 0, err - } - - //do send back - if needSendBack { - c.Write(nil) - return 0, nil - } - - decodedDataLen := len(decodedData) - if decodedDataLen == 0 { - return 0, nil - } - - if !c.DecryptInited() { - if len(decodedData) < c.InfoIVLen() { - return 0, errors.New(fmt.Sprintf("invalid ivLen:%v, actual length:%v", c.InfoIVLen(), len(decodedData))) - } - - iv := decodedData[0:c.InfoIVLen()] - if err = c.InitDecrypt(iv); err != nil { - return 0, err - } - - if len(c.IV()) == 0 { - c.SetIV(iv) - } - - decodedDataLen -= c.InfoIVLen() - if decodedDataLen <= 0 { - return 0, nil - } - - decodedData = decodedData[c.InfoIVLen():] - } - - buf1 := pool.GetBuffer(decodedDataLen) - defer pool.PutBuffer(buf1) - - c.Decrypt(buf1, decodedData) - c.underPostdecryptBuf.Write(buf1) - buf := c.underPostdecryptBuf.Bytes() - - postDecryptedData, length, err := c.IProtocol.PostDecrypt(buf) - if err != nil { - c.underPostdecryptBuf.Reset() - return 0, err - } - if length == 0 { - // not enough to postDecrypt - return 0, nil - } else { - c.underPostdecryptBuf.Next(length) - } - - postDecryptedLength := len(postDecryptedData) - blength := len(b) - - if blength >= postDecryptedLength { - copy(b, postDecryptedData) - return postDecryptedLength, nil - } - - copy(b, postDecryptedData[:blength]) - c.decryptedBuf.Write(postDecryptedData[blength:]) - - return blength, nil -} - -func (c *SSTCPConn) preWrite(b []byte) (outData []byte, err error) { - if b == nil { - b = make([]byte, 0) - } - - var iv []byte - if iv, err = c.initEncryptor(b); err != nil { - return - } - - var preEncryptedData []byte - preEncryptedData, err = c.IProtocol.PreEncrypt(b) - if err != nil { - return - } - preEncryptedDataLen := len(preEncryptedData) - //! \attention here the expected output buffer length MUST be accurate, it is preEncryptedDataLen now! - - cipherData := c.writeBuf - dataSize := preEncryptedDataLen + len(iv) - if dataSize > len(cipherData) { - cipherData = make([]byte, dataSize) - } else { - cipherData = cipherData[:dataSize] - } - - if iv != nil { - // Put initialization vector in buffer before be encoded - copy(cipherData, iv) - } - - c.Encrypt(cipherData[len(iv):], preEncryptedData) - return c.IObfs.Encode(cipherData) -} - -func (c *SSTCPConn) Write(b []byte) (n int, err error) { - outData, err := c.preWrite(b) - if err != nil { - return 0, err - } - n, err = c.Conn.Write(outData) - if err != nil { - return 0, err - } - return len(b), nil -} diff --git a/proxy/ssr/internal/obfs/base.go b/proxy/ssr/internal/obfs/base.go deleted file mode 100644 index 57b4f07..0000000 --- a/proxy/ssr/internal/obfs/base.go +++ /dev/null @@ -1,36 +0,0 @@ -package obfs - -import ( - "strings" - - "github.com/nadoo/glider/proxy/ssr/internal/ssr" -) - -type creator func() IObfs - -var ( - creatorMap = make(map[string]creator) -) - -type IObfs interface { - SetServerInfo(s *ssr.ServerInfo) - GetServerInfo() (s *ssr.ServerInfo) - Encode(data []byte) (encodedData []byte, err error) - Decode(data []byte) (decodedData []byte, needSendBack bool, err error) - SetData(data any) - GetData() any - GetOverhead() int -} - -func register(name string, c creator) { - creatorMap[name] = c -} - -// NewObfs create an obfs object by name and return as an IObfs interface -func NewObfs(name string) IObfs { - c, ok := creatorMap[strings.ToLower(name)] - if ok { - return c() - } - return nil -} diff --git a/proxy/ssr/internal/obfs/http_post.go b/proxy/ssr/internal/obfs/http_post.go deleted file mode 100644 index d1dce8f..0000000 --- a/proxy/ssr/internal/obfs/http_post.go +++ /dev/null @@ -1,20 +0,0 @@ -package obfs - -import ( - "math/rand/v2" -) - -func init() { - register("http_post", newHttpPost) -} - -// newHttpPost create a http_post object -func newHttpPost() IObfs { - // newHttpSimple create a http_simple object - - t := &httpSimplePost{ - userAgentIndex: rand.IntN(len(requestUserAgent)), - methodGet: false, - } - return t -} diff --git a/proxy/ssr/internal/obfs/http_simple.go b/proxy/ssr/internal/obfs/http_simple.go deleted file mode 100644 index f438c25..0000000 --- a/proxy/ssr/internal/obfs/http_simple.go +++ /dev/null @@ -1,185 +0,0 @@ -package obfs - -import ( - "bytes" - "encoding/hex" - "fmt" - "math/rand/v2" - "strings" - - "github.com/nadoo/glider/proxy/ssr/internal/ssr" -) - -var ( - requestPath = []string{ - "", "", - "login.php?redir=", "", - "register.php?code=", "", - "?keyword=", "", - "search?src=typd&q=", "&lang=en", - "s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=", - "post.php?id=", "&goto=view.php", - } - requestUserAgent = []string{ - "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0", - "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/44.0", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36", - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0", - "Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C)", - "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko", - "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/BuildID) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36", - "Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3", - "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3", - } -) - -// HttpSimple http_simple obfs encapsulate -type httpSimplePost struct { - ssr.ServerInfo - rawTransSent bool - rawTransReceived bool - userAgentIndex int - methodGet bool // true for get, false for post -} - -func init() { - register("http_simple", newHttpSimple) -} - -// newHttpSimple create a http_simple object -func newHttpSimple() IObfs { - - t := &httpSimplePost{ - rawTransSent: false, - rawTransReceived: false, - userAgentIndex: rand.IntN(len(requestUserAgent)), - methodGet: true, - } - return t -} - -func (t *httpSimplePost) SetServerInfo(s *ssr.ServerInfo) { - t.ServerInfo = *s -} - -func (t *httpSimplePost) GetServerInfo() (s *ssr.ServerInfo) { - return &t.ServerInfo -} - -func (t *httpSimplePost) SetData(data any) { - -} - -func (t *httpSimplePost) GetData() any { - return nil -} - -func (t *httpSimplePost) boundary() (ret string) { - - set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - for range 32 { - ret = fmt.Sprintf("%s%c", ret, set[rand.IntN(len(set))]) - } - return -} - -func (t *httpSimplePost) data2URLEncode(data []byte) (ret string) { - for i := range data { - ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]})) - } - return -} - -func (t *httpSimplePost) Encode(data []byte) (encodedData []byte, err error) { - if t.rawTransSent { - return data, nil - } - - dataLength := len(data) - var headData []byte - if headSize := t.IVLen + t.HeadLen; dataLength-headSize > 64 { - headData = make([]byte, headSize+rand.IntN(64)) - } else { - headData = make([]byte, dataLength) - } - copy(headData, data[0:len(headData)]) - requestPathIndex := rand.IntN(len(requestPath)/2) * 2 - host := t.Host - var customHead string - - if len(t.Param) > 0 { - customHeads := strings.Split(t.Param, "#") - if len(customHeads) > 2 { - customHeads = customHeads[0:2] - } - param := t.Param - if len(customHeads) > 1 { - customHead = customHeads[1] - param = customHeads[0] - } - hosts := strings.Split(param, ",") - if len(hosts) > 0 { - host = strings.TrimSpace(hosts[rand.IntN(len(hosts))]) - } - } - method := "GET /" - if !t.methodGet { - method = "POST /" - } - httpBuf := fmt.Sprintf("%s%s%s%s HTTP/1.1\r\nHost: %s:%d\r\n", - method, - requestPath[requestPathIndex], - t.data2URLEncode(headData), - requestPath[requestPathIndex+1], - host, - t.Port) - if len(customHead) > 0 { - httpBuf = httpBuf + strings.Replace(customHead, "\\n", "\r\n", -1) + "\r\n\r\n" - } else { - var contentType string - if !t.methodGet { - contentType = "Content-Type: multipart/form-data; boundary=" + t.boundary() + "\r\n" - } - httpBuf = httpBuf + - "User-Agent: " + requestUserAgent[t.userAgentIndex] + "\r\n" + - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + - "Accept-Language: en-US,en;q=0.8\r\n" + - "Accept-Encoding: gzip, deflate\r\n" + - contentType + - "DNT: 1\r\n" + - "Connection: keep-alive\r\n" + - "\r\n" - } - - if len(headData) < dataLength { - encodedData = make([]byte, len(httpBuf)+(dataLength-len(headData))) - copy(encodedData, []byte(httpBuf)) - copy(encodedData[len(httpBuf):], data[len(headData):]) - } else { - encodedData = []byte(httpBuf) - } - t.rawTransSent = true - - return -} - -func (t *httpSimplePost) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) { - if t.rawTransReceived { - return data, false, nil - } - - pos := bytes.Index(data, []byte("\r\n\r\n")) - if pos > 0 { - decodedData = make([]byte, len(data)-pos-4) - copy(decodedData, data[pos+4:]) - t.rawTransReceived = true - } - return decodedData, false, nil -} - -func (t *httpSimplePost) GetOverhead() int { - return 0 -} diff --git a/proxy/ssr/internal/obfs/plain.go b/proxy/ssr/internal/obfs/plain.go deleted file mode 100644 index 3a740b8..0000000 --- a/proxy/ssr/internal/obfs/plain.go +++ /dev/null @@ -1,46 +0,0 @@ -package obfs - -import ( - "github.com/nadoo/glider/proxy/ssr/internal/ssr" -) - -func init() { - register("plain", newPlainObfs) -} - -type plain struct { - ssr.ServerInfo -} - -func newPlainObfs() IObfs { - p := &plain{} - return p -} - -func (p *plain) SetServerInfo(s *ssr.ServerInfo) { - p.ServerInfo = *s -} - -func (p *plain) GetServerInfo() (s *ssr.ServerInfo) { - return &p.ServerInfo -} - -func (p *plain) Encode(data []byte) (encodedData []byte, err error) { - return data, nil -} - -func (p *plain) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) { - return data, false, nil -} - -func (p *plain) SetData(data any) { - -} - -func (p *plain) GetData() any { - return nil -} - -func (p *plain) GetOverhead() int { - return 0 -} diff --git a/proxy/ssr/internal/obfs/random_head.go b/proxy/ssr/internal/obfs/random_head.go deleted file mode 100644 index 34fff19..0000000 --- a/proxy/ssr/internal/obfs/random_head.go +++ /dev/null @@ -1,84 +0,0 @@ -package obfs - -import ( - crand "crypto/rand" - "math/rand/v2" - - "github.com/nadoo/glider/proxy/ssr/internal/ssr" -) - -type randomHead struct { - ssr.ServerInfo - rawTransSent bool - rawTransReceived bool - hasSentHeader bool - dataBuffer []byte -} - -func init() { - register("random_head", newRandomHead) -} - -func newRandomHead() IObfs { - p := &randomHead{} - return p -} - -func (r *randomHead) SetServerInfo(s *ssr.ServerInfo) { - r.ServerInfo = *s -} - -func (r *randomHead) GetServerInfo() (s *ssr.ServerInfo) { - return &r.ServerInfo -} - -func (r *randomHead) SetData(data any) { - -} - -func (r *randomHead) GetData() any { - return nil -} - -func (r *randomHead) Encode(data []byte) (encodedData []byte, err error) { - if r.rawTransSent { - return data, nil - } - - dataLength := len(data) - if r.hasSentHeader { - if dataLength > 0 { - d := make([]byte, len(r.dataBuffer)+dataLength) - copy(d, r.dataBuffer) - copy(d[len(r.dataBuffer):], data) - r.dataBuffer = d - } else { - encodedData = r.dataBuffer - r.dataBuffer = nil - r.rawTransSent = true - } - } else { - size := rand.IntN(96) + 8 - encodedData = make([]byte, size) - crand.Read(encodedData) - ssr.SetCRC32(encodedData, size) - - d := make([]byte, dataLength) - copy(d, data) - r.dataBuffer = d - } - r.hasSentHeader = true - return -} - -func (r *randomHead) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) { - if r.rawTransReceived { - return data, false, nil - } - r.rawTransReceived = true - return data, true, nil -} - -func (r *randomHead) GetOverhead() int { - return 0 -} diff --git a/proxy/ssr/internal/obfs/tls12_ticket_auth.go b/proxy/ssr/internal/obfs/tls12_ticket_auth.go deleted file mode 100644 index fb9c9d4..0000000 --- a/proxy/ssr/internal/obfs/tls12_ticket_auth.go +++ /dev/null @@ -1,313 +0,0 @@ -package obfs - -import ( - "bytes" - "crypto/hmac" - crand "crypto/rand" - "encoding/binary" - "fmt" - "log" - "math/rand/v2" - "strings" - "time" - - "github.com/nadoo/glider/proxy/ssr/internal/ssr" - "github.com/nadoo/glider/proxy/ssr/internal/tools" -) - -func init() { - register("tls1.2_ticket_auth", newTLS12TicketAuth) - register("tls1.2_ticket_fastauth", newTLS12TicketFastAuth) -} - -type tlsAuthData struct { - localClientID [32]byte -} - -// tls12TicketAuth tls1.2_ticket_auth obfs encapsulate -type tls12TicketAuth struct { - ssr.ServerInfo - data *tlsAuthData - handshakeStatus int - sendSaver []byte - recvBuffer bytes.Buffer - fastAuth bool -} - -// newTLS12TicketAuth create a tlv1.2_ticket_auth object -func newTLS12TicketAuth() IObfs { - return &tls12TicketAuth{} -} - -// newTLS12TicketFastAuth create a tlv1.2_ticket_fastauth object -func newTLS12TicketFastAuth() IObfs { - return &tls12TicketAuth{ - fastAuth: true, - } -} - -func (t *tls12TicketAuth) SetServerInfo(s *ssr.ServerInfo) { - t.ServerInfo = *s -} - -func (t *tls12TicketAuth) GetServerInfo() (s *ssr.ServerInfo) { - return &t.ServerInfo -} - -func (t *tls12TicketAuth) SetData(data any) { - if auth, ok := data.(*tlsAuthData); ok { - t.data = auth - } -} - -func (t *tls12TicketAuth) GetData() any { - if t.data == nil { - t.data = &tlsAuthData{} - b := make([]byte, 32) - - crand.Read(b) - copy(t.data.localClientID[:], b) - } - return t.data -} - -func (t *tls12TicketAuth) getHost() string { - host := t.Host - if len(t.Param) > 0 { - hosts := strings.Split(t.Param, ",") - if len(hosts) > 0 { - - host = hosts[rand.IntN(len(hosts))] - host = strings.TrimSpace(host) - } - } - if len(host) > 0 && host[len(host)-1] >= byte('0') && host[len(host)-1] <= byte('9') && len(t.Param) == 0 { - host = "" - } - return host -} - -func packData(prefixData []byte, suffixData []byte) (outData []byte) { - d := []byte{0x17, 0x3, 0x3, 0, 0} - binary.BigEndian.PutUint16(d[3:5], uint16(len(suffixData)&0xFFFF)) - outData = append(prefixData, d...) - outData = append(outData, suffixData...) - return -} - -func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) { - encodedData = make([]byte, 0) - switch t.handshakeStatus { - case 8: - if len(data) < 1024 { - d := []byte{0x17, 0x3, 0x3, 0, 0} - binary.BigEndian.PutUint16(d[3:5], uint16(len(data)&0xFFFF)) - encodedData = append(d, data...) - return - } else { - start := 0 - var l int - for len(data)-start > 2048 { - l = rand.IntN(4096) + 100 - if l > len(data)-start { - l = len(data) - start - } - encodedData = packData(encodedData, data[start:start+l]) - start += l - } - if len(data)-start > 0 { - l = len(data) - start - encodedData = packData(encodedData, data[start:start+l]) - } - return - } - case 1: - if len(data) > 0 { - if len(data) < 1024 { - t.sendSaver = packData(t.sendSaver, data) - } else { - start := 0 - var l int - for len(data)-start > 2048 { - l = rand.IntN(4096) + 100 - if l > len(data)-start { - l = len(data) - start - } - encodedData = packData(encodedData, data[start:start+l]) - start += l - } - if len(data)-start > 0 { - l = len(data) - start - encodedData = packData(encodedData, data[start:start+l]) - } - t.sendSaver = append(t.sendSaver, encodedData...) - encodedData = encodedData[:0] - } - return []byte{}, nil - } - hmacData := make([]byte, 43) - handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20") - copy(hmacData, handshakeFinish) - crand.Read(hmacData[11:33]) - h := t.hmacSHA1(hmacData[:33]) - copy(hmacData[33:], h) - encodedData = append(hmacData, t.sendSaver...) - t.sendSaver = t.sendSaver[:0] - t.handshakeStatus = 8 - case 0: - tlsData0 := []byte("\x00\x1c\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xcc\x14\xcc\x13\xc0\x0a\xc0\x14\xc0\x09\xc0\x13\x00\x9c\x00\x35\x00\x2f\x00\x0a\x01\x00") - tlsData1 := []byte("\xff\x01\x00\x01\x00") - tlsData2 := []byte("\x00\x17\x00\x00\x00\x23\x00\xd0") - tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18\x00\x15\x00\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - - var tlsData [2048]byte - tlsDataLen := 0 - copy(tlsData[0:], tlsData1) - tlsDataLen += len(tlsData1) - sni := t.sni(t.getHost()) - copy(tlsData[tlsDataLen:], sni) - tlsDataLen += len(sni) - copy(tlsData[tlsDataLen:], tlsData2) - tlsDataLen += len(tlsData2) - ticketLen := rand.IntN(164)*2 + 64 - tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff) - tlsData[tlsDataLen-2] = uint8(ticketLen >> 8) - //ticketLen := 208 - crand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen]) - tlsDataLen += ticketLen - copy(tlsData[tlsDataLen:], tlsData3) - tlsDataLen += len(tlsData3) - - length := 11 + 32 + 1 + 32 + len(tlsData0) + 2 + tlsDataLen - encodedData = make([]byte, length) - pdata := length - tlsDataLen - l := tlsDataLen - copy(encodedData[pdata:], tlsData[:tlsDataLen]) - encodedData[pdata-1] = uint8(tlsDataLen) - encodedData[pdata-2] = uint8(tlsDataLen >> 8) - pdata -= 2 - l += 2 - copy(encodedData[pdata-len(tlsData0):], tlsData0) - pdata -= len(tlsData0) - l += len(tlsData0) - copy(encodedData[pdata-32:], t.data.localClientID[:]) - pdata -= 32 - l += 32 - encodedData[pdata-1] = 0x20 - pdata -= 1 - l += 1 - copy(encodedData[pdata-32:], t.packAuthData()) - pdata -= 32 - l += 32 - encodedData[pdata-1] = 0x3 - encodedData[pdata-2] = 0x3 // tls version - pdata -= 2 - l += 2 - encodedData[pdata-1] = uint8(l) - encodedData[pdata-2] = uint8(l >> 8) - encodedData[pdata-3] = 0 - encodedData[pdata-4] = 1 - pdata -= 4 - l += 4 - encodedData[pdata-1] = uint8(l) - encodedData[pdata-2] = uint8(l >> 8) - pdata -= 2 - // l += 2 - encodedData[pdata-1] = 0x1 - encodedData[pdata-2] = 0x3 // tls version - pdata -= 2 - // l += 2 - encodedData[pdata-1] = 0x16 // tls handshake - // pdata -= 1 - // l += 1 - - t.sendSaver = packData(t.sendSaver, data) - t.handshakeStatus = 1 - default: - //log.Println(fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus)) - return nil, fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus) - } - return -} - -func (t *tls12TicketAuth) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) { - if t.handshakeStatus == -1 { - return data, false, nil - } - - if t.handshakeStatus == 8 { - t.recvBuffer.Write(data) - for t.recvBuffer.Len() > 5 { - var h [5]byte - _, _ = t.recvBuffer.Read(h[:]) - if !bytes.Equal(h[0:3], []byte{0x17, 0x3, 0x3}) { - log.Println("incorrect magic number", h[0:3], ", 0x170303 is expected") - return nil, false, ssr.ErrTLS12TicketAuthIncorrectMagicNumber - } - size := int(binary.BigEndian.Uint16(h[3:5])) - if t.recvBuffer.Len() < size { - unread := t.recvBuffer.Bytes() - t.recvBuffer.Reset() - t.recvBuffer.Write(h[:]) - t.recvBuffer.Write(unread) - break - } - d := make([]byte, size) - _, _ = t.recvBuffer.Read(d) - decodedData = append(decodedData, d...) - } - return decodedData, false, nil - } - - if len(data) < 11+32+1+32 { - return nil, false, ssr.ErrTLS12TicketAuthTooShortData - } - - hash := t.hmacSHA1(data[11 : 11+22]) - - if !hmac.Equal(data[33:33+ssr.ObfsHMACSHA1Len], hash) { - return nil, false, ssr.ErrTLS12TicketAuthHMACError - } - return nil, true, nil -} - -func (t *tls12TicketAuth) packAuthData() (outData []byte) { - outSize := 32 - outData = make([]byte, outSize) - - now := time.Now().Unix() - binary.BigEndian.PutUint32(outData[0:4], uint32(now)) - - crand.Read(outData[4 : 4+18]) - - hash := t.hmacSHA1(outData[:outSize-ssr.ObfsHMACSHA1Len]) - copy(outData[outSize-ssr.ObfsHMACSHA1Len:], hash) - - return -} - -func (t *tls12TicketAuth) hmacSHA1(data []byte) []byte { - key := make([]byte, t.KeyLen+32) - copy(key, t.Key) - copy(key[t.KeyLen:], t.data.localClientID[:]) - - sha1Data := tools.HmacSHA1(key, data) - return sha1Data[:ssr.ObfsHMACSHA1Len] -} - -func (t *tls12TicketAuth) sni(u string) []byte { - bURL := []byte(u) - length := len(bURL) - ret := make([]byte, length+9) - copy(ret[9:9+length], bURL) - binary.BigEndian.PutUint16(ret[7:], uint16(length&0xFFFF)) - length += 3 - binary.BigEndian.PutUint16(ret[4:], uint16(length&0xFFFF)) - length += 2 - binary.BigEndian.PutUint16(ret[2:], uint16(length&0xFFFF)) - return ret -} - -func (t *tls12TicketAuth) GetOverhead() int { - return 5 -} diff --git a/proxy/ssr/internal/protocol/auth_aes128_md5.go b/proxy/ssr/internal/protocol/auth_aes128_md5.go deleted file mode 100644 index b489aa7..0000000 --- a/proxy/ssr/internal/protocol/auth_aes128_md5.go +++ /dev/null @@ -1,279 +0,0 @@ -package protocol - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - crand "crypto/rand" - "encoding/base64" - "encoding/binary" - "math/rand/v2" - "strconv" - "strings" - "time" - - "github.com/nadoo/glider/proxy/ssr/internal/ssr" - "github.com/nadoo/glider/proxy/ssr/internal/tools" -) - -func init() { - register("auth_aes128_md5", NewAuthAES128MD5) -} - -func NewAuthAES128MD5() IProtocol { - a := &authAES128{ - salt: "auth_aes128_md5", - hmac: tools.HmacMD5, - hashDigest: tools.MD5Sum, - packID: 1, - recvInfo: recvInfo{ - recvID: 1, - buffer: bytes.NewBuffer(nil), - }, - } - return a -} - -type recvInfo struct { - recvID uint32 - buffer *bytes.Buffer -} - -type authAES128 struct { - ssr.ServerInfo - recvInfo - data *AuthData - hasSentHeader bool - packID uint32 - userKey []byte - salt string - hmac hmacMethod - hashDigest hashDigestMethod -} - -func (a *authAES128) SetServerInfo(s *ssr.ServerInfo) { - a.ServerInfo = *s -} - -func (a *authAES128) GetServerInfo() (s *ssr.ServerInfo) { - return &a.ServerInfo -} - -func (a *authAES128) SetData(data any) { - if auth, ok := data.(*AuthData); ok { - a.data = auth - } -} - -func (a *authAES128) GetData() any { - if a.data == nil { - a.data = &AuthData{} - } - return a.data -} - -func (a *authAES128) packData(data []byte) (outData []byte) { - dataLength := len(data) - randLength := 1 - if dataLength <= 1200 { - if a.packID > 4 { - randLength += rand.IntN(32) - } else { - if dataLength > 900 { - randLength += rand.IntN(128) - } else { - randLength += rand.IntN(512) - } - } - } - - outLength := randLength + dataLength + 8 - outData = make([]byte, outLength) - // 0~1, out length - binary.LittleEndian.PutUint16(outData[0:], uint16(outLength&0xFFFF)) - // 2~3, hmac - key := make([]byte, len(a.userKey)+4) - copy(key, a.userKey) - binary.LittleEndian.PutUint32(key[len(key)-4:], a.packID) - h := a.hmac(key, outData[0:2]) - copy(outData[2:4], h[:2]) - // 4~rand length+4, rand number - crand.Read(outData[4 : 4+randLength]) - // 4, rand length - if randLength < 128 { - outData[4] = byte(randLength & 0xFF) - } else { - // 4, magic number 0xFF - outData[4] = 0xFF - // 5~6, rand length - binary.LittleEndian.PutUint16(outData[5:], uint16(randLength&0xFFFF)) - } - // rand length+4~out length-4, data - if dataLength > 0 { - copy(outData[randLength+4:], data) - } - a.packID++ - h = a.hmac(key, outData[:outLength-4]) - copy(outData[outLength-4:], h[:4]) - return -} - -func (a *authAES128) packAuthData(data []byte) (outData []byte) { - dataLength := len(data) - var randLength int - if dataLength > 400 { - randLength = rand.IntN(512) - } else { - randLength = rand.IntN(1024) - } - - dataOffset := randLength + 16 + 4 + 4 + 7 - outLength := dataOffset + dataLength + 4 - outData = make([]byte, outLength) - encrypt := make([]byte, 24) - key := make([]byte, a.IVLen+a.KeyLen) - copy(key, a.IV) - copy(key[a.IVLen:], a.Key) - - crand.Read(outData[dataOffset-randLength:]) - a.data.mutex.Lock() - a.data.connectionID++ - if a.data.connectionID > 0xFF000000 { - a.data.clientID = nil - } - if len(a.data.clientID) == 0 { - a.data.clientID = make([]byte, 8) - crand.Read(a.data.clientID) - b := make([]byte, 4) - crand.Read(b) - a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF - } - copy(encrypt[4:], a.data.clientID) - binary.LittleEndian.PutUint32(encrypt[8:], a.data.connectionID) - a.data.mutex.Unlock() - - now := time.Now().Unix() - binary.LittleEndian.PutUint32(encrypt[0:4], uint32(now)) - - binary.LittleEndian.PutUint16(encrypt[12:], uint16(outLength&0xFFFF)) - binary.LittleEndian.PutUint16(encrypt[14:], uint16(randLength&0xFFFF)) - - params := strings.Split(a.Param, ":") - uid := make([]byte, 4) - if len(params) >= 2 { - if userID, err := strconv.ParseUint(params[0], 10, 32); err != nil { - crand.Read(uid) - } else { - binary.LittleEndian.PutUint32(uid, uint32(userID)) - a.userKey = a.hashDigest([]byte(params[1])) - } - } else { - crand.Read(uid) - } - - if a.userKey == nil { - a.userKey = make([]byte, a.KeyLen) - copy(a.userKey, a.Key) - } - - encryptKey := make([]byte, len(a.userKey)) - copy(encryptKey, a.userKey) - - aesCipherKey := tools.EVPBytesToKey(base64.StdEncoding.EncodeToString(encryptKey)+a.salt, 16) - block, err := aes.NewCipher(aesCipherKey) - if err != nil { - return nil - } - - encryptData := make([]byte, 16) - iv := make([]byte, aes.BlockSize) - cbc := cipher.NewCBCEncrypter(block, iv) - cbc.CryptBlocks(encryptData, encrypt[0:16]) - copy(encrypt[4:4+16], encryptData) - copy(encrypt[0:4], uid) - - h := a.hmac(key, encrypt[0:20]) - copy(encrypt[20:], h[:4]) - - crand.Read(outData[0:1]) - h = a.hmac(key, outData[0:1]) - copy(outData[1:], h[0:7-1]) - - copy(outData[7:], encrypt) - copy(outData[dataOffset:], data) - - h = a.hmac(a.userKey, outData[0:outLength-4]) - copy(outData[outLength-4:], h[:4]) - - //log.Println("clientID:", a.data.clientID, "connectionID:", a.data.connectionID) - return -} - -func (a *authAES128) PreEncrypt(plainData []byte) (outData []byte, err error) { - dataLength := len(plainData) - offset := 0 - if dataLength > 0 && !a.hasSentHeader { - authLength := dataLength - if authLength > 1200 { - authLength = 1200 - } - packData := a.packAuthData(plainData[:authLength]) - a.hasSentHeader = true - outData = append(outData, packData...) - dataLength -= authLength - offset += authLength - } - const blockSize = 4096 - for dataLength > blockSize { - packData := a.packData(plainData[offset : offset+blockSize]) - outData = append(outData, packData...) - dataLength -= blockSize - offset += blockSize - } - if dataLength > 0 { - packData := a.packData(plainData[offset:]) - outData = append(outData, packData...) - } - - return -} - -func (a *authAES128) PostDecrypt(plainData []byte) ([]byte, int, error) { - a.buffer.Reset() - plainLength := len(plainData) - readlenth := 0 - key := make([]byte, len(a.userKey)+4) - copy(key, a.userKey) - for plainLength > 4 { - binary.LittleEndian.PutUint32(key[len(key)-4:], a.recvID) - - h := a.hmac(key, plainData[0:2]) - if h[0] != plainData[2] || h[1] != plainData[3] { - return nil, 0, ssr.ErrAuthAES128IncorrectHMAC - } - length := int(binary.LittleEndian.Uint16(plainData[0:2])) - if length >= 8192 || length < 8 { - return nil, 0, ssr.ErrAuthAES128DataLengthError - } - if length > plainLength { - break - } - a.recvID++ - pos := int(plainData[4]) - if pos < 255 { - pos += 4 - } else { - pos = int(binary.LittleEndian.Uint16(plainData[5:7])) + 4 - } - - a.buffer.Write(plainData[pos : length-4]) - plainData = plainData[length:] - plainLength -= length - readlenth += length - } - return a.buffer.Bytes(), readlenth, nil -} - -func (a *authAES128) GetOverhead() int { - return 9 -} diff --git a/proxy/ssr/internal/protocol/auth_aes128_sha1.go b/proxy/ssr/internal/protocol/auth_aes128_sha1.go deleted file mode 100644 index 298c468..0000000 --- a/proxy/ssr/internal/protocol/auth_aes128_sha1.go +++ /dev/null @@ -1,25 +0,0 @@ -package protocol - -import ( - "bytes" - - "github.com/nadoo/glider/proxy/ssr/internal/tools" -) - -func init() { - register("auth_aes128_sha1", NewAuthAES128SHA1) -} - -func NewAuthAES128SHA1() IProtocol { - a := &authAES128{ - salt: "auth_aes128_sha1", - hmac: tools.HmacSHA1, - hashDigest: tools.SHA1Sum, - packID: 1, - recvInfo: recvInfo{ - recvID: 1, - buffer: bytes.NewBuffer(nil), - }, - } - return a -} diff --git a/proxy/ssr/internal/protocol/auth_chain_a.go b/proxy/ssr/internal/protocol/auth_chain_a.go deleted file mode 100644 index c68c441..0000000 --- a/proxy/ssr/internal/protocol/auth_chain_a.go +++ /dev/null @@ -1,320 +0,0 @@ -// https://github.com/shadowsocksr-backup/shadowsocks-rss/blob/master/doc/auth_chain_a.md - -package protocol - -import ( - "bytes" - "crypto/aes" - stdCipher "crypto/cipher" - "crypto/rand" - "encoding/base64" - "encoding/binary" - "strconv" - "strings" - "time" - - "github.com/nadoo/glider/proxy/ssr/internal/cipher" - "github.com/nadoo/glider/proxy/ssr/internal/ssr" - "github.com/nadoo/glider/proxy/ssr/internal/tools" -) - -func init() { - register("auth_chain_a", NewAuthChainA) -} - -type authChainA struct { - ssr.ServerInfo - randomClient tools.Shift128plusContext - randomServer tools.Shift128plusContext - recvInfo - cipher *cipher.StreamCipher - hasSentHeader bool - lastClientHash []byte - lastServerHash []byte - userKey []byte - uid [4]byte - salt string - data *AuthData - hmac hmacMethod - hashDigest hashDigestMethod - rnd rndMethod - dataSizeList []int - dataSizeList2 []int - chunkID uint32 -} - -func NewAuthChainA() IProtocol { - a := &authChainA{ - salt: "auth_chain_a", - hmac: tools.HmacMD5, - hashDigest: tools.SHA1Sum, - rnd: authChainAGetRandLen, - recvInfo: recvInfo{ - recvID: 1, - buffer: new(bytes.Buffer), - }, - } - return a -} - -func (a *authChainA) SetServerInfo(s *ssr.ServerInfo) { - a.ServerInfo = *s - if a.salt == "auth_chain_b" { - a.authChainBInitDataSize() - } -} - -func (a *authChainA) GetServerInfo() (s *ssr.ServerInfo) { - return &a.ServerInfo -} - -func (a *authChainA) SetData(data any) { - if auth, ok := data.(*AuthData); ok { - a.data = auth - } -} - -func (a *authChainA) GetData() any { - if a.data == nil { - a.data = &AuthData{} - } - return a.data -} - -func authChainAGetRandLen(dataLength int, random *tools.Shift128plusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int { - if dataLength > 1440 { - return 0 - } - random.InitFromBinDatalen(lastHash[:16], dataLength) - if dataLength > 1300 { - return int(random.Next() % 31) - } - if dataLength > 900 { - return int(random.Next() % 127) - } - if dataLength > 400 { - return int(random.Next() % 521) - } - return int(random.Next() % 1021) -} - -func getRandStartPos(random *tools.Shift128plusContext, randLength int) int { - if randLength > 0 { - return int(int64(random.Next()%8589934609) % int64(randLength)) - } - return 0 -} - -func (a *authChainA) getClientRandLen(dataLength int, overhead int) int { - return a.rnd(dataLength, &a.randomClient, a.lastClientHash, a.dataSizeList, a.dataSizeList2, overhead) -} - -func (a *authChainA) getServerRandLen(dataLength int, overhead int) int { - return a.rnd(dataLength, &a.randomServer, a.lastServerHash, a.dataSizeList, a.dataSizeList2, overhead) -} - -func (a *authChainA) packedDataLen(data []byte) (chunkLength, randLength int) { - dataLength := len(data) - randLength = a.getClientRandLen(dataLength, a.Overhead) - chunkLength = randLength + dataLength + 2 + 2 - return -} - -func (a *authChainA) packData(outData []byte, data []byte, randLength int) { - dataLength := len(data) - outLength := randLength + dataLength + 2 - outData[0] = byte(dataLength) ^ a.lastClientHash[14] - outData[1] = byte(dataLength>>8) ^ a.lastClientHash[15] - - { - if dataLength > 0 { - randPart1Length := getRandStartPos(&a.randomClient, randLength) - rand.Read(outData[2 : 2+randPart1Length]) - a.cipher.Encrypt(outData[2+randPart1Length:], data) - rand.Read(outData[2+randPart1Length+dataLength : outLength]) - } else { - rand.Read(outData[2 : 2+randLength]) - } - } - - userKeyLen := uint8(len(a.userKey)) - key := make([]byte, userKeyLen+4) - copy(key, a.userKey) - a.chunkID++ - binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID) - a.lastClientHash = a.hmac(key, outData[:outLength]) - copy(outData[outLength:], a.lastClientHash[:2]) - return -} - -const authheadLength = 4 + 8 + 4 + 16 + 4 - -func (a *authChainA) packAuthData(data []byte) (outData []byte) { - outData = make([]byte, authheadLength, authheadLength+1500) - a.data.connectionID++ - if a.data.connectionID > 0xFF000000 { - rand.Read(a.data.clientID) - b := make([]byte, 4) - rand.Read(b) - a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF - } - var key = make([]byte, a.IVLen+a.KeyLen) - copy(key, a.IV) - copy(key[a.IVLen:], a.Key) - - encrypt := make([]byte, 20) - t := time.Now().Unix() - binary.LittleEndian.PutUint32(encrypt[:4], uint32(t)) - copy(encrypt[4:8], a.data.clientID) - binary.LittleEndian.PutUint32(encrypt[8:], a.data.connectionID) - binary.LittleEndian.PutUint16(encrypt[12:], uint16(a.Overhead)) - //binary.LittleEndian.PutUint16(encrypt[14:], 0) - - // first 12 bytes - { - rand.Read(outData[:4]) - a.lastClientHash = a.hmac(key, outData[:4]) - copy(outData[4:], a.lastClientHash[:8]) - } - var base64UserKey string - // uid & 16 bytes auth data - { - uid := make([]byte, 4) - if a.userKey == nil { - params := strings.Split(a.ServerInfo.Param, ":") - if len(params) >= 2 { - if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { - binary.LittleEndian.PutUint32(a.uid[:], uint32(userID)) - a.userKey = a.hashDigest([]byte(params[1])) - } - } - if a.userKey == nil { - rand.Read(a.uid[:]) - a.userKey = make([]byte, a.KeyLen) - copy(a.userKey, a.Key) - } - } - for i := range 4 { - uid[i] = a.uid[i] ^ a.lastClientHash[8+i] - } - base64UserKey = base64.StdEncoding.EncodeToString(a.userKey) - aesCipherKey := tools.EVPBytesToKey(base64UserKey+a.salt, 16) - block, err := aes.NewCipher(aesCipherKey) - if err != nil { - return - } - encryptData := make([]byte, 16) - iv := make([]byte, aes.BlockSize) - cbc := stdCipher.NewCBCEncrypter(block, iv) - cbc.CryptBlocks(encryptData, encrypt[:16]) - copy(encrypt[:4], uid[:]) - copy(encrypt[4:4+16], encryptData) - } - // final HMAC - { - a.lastServerHash = a.hmac(a.userKey, encrypt[0:20]) - - copy(outData[12:], encrypt) - copy(outData[12+20:], a.lastServerHash[:4]) - } - - // init cipher - password := make([]byte, len(base64UserKey)+base64.StdEncoding.EncodedLen(16)) - copy(password, base64UserKey) - base64.StdEncoding.Encode(password[len(base64UserKey):], a.lastClientHash[:16]) - a.cipher, _ = cipher.NewStreamCipher("rc4", string(password)) - _, _ = a.cipher.InitEncrypt() - _ = a.cipher.InitDecrypt(nil) - - // data - chunkLength, randLength := a.packedDataLen(data) - if chunkLength <= 1500 { - outData = outData[:authheadLength+chunkLength] - } else { - newOutData := make([]byte, authheadLength+chunkLength) - copy(newOutData, outData[:authheadLength]) - outData = newOutData - } - a.packData(outData[authheadLength:], data, randLength) - return -} - -func (a *authChainA) PreEncrypt(plainData []byte) (outData []byte, err error) { - a.buffer.Reset() - dataLength := len(plainData) - length := dataLength - offset := 0 - if length > 0 && !a.hasSentHeader { - headSize := 1200 - if headSize > dataLength { - headSize = dataLength - } - a.buffer.Write(a.packAuthData(plainData[:headSize])) - offset += headSize - dataLength -= headSize - a.hasSentHeader = true - } - var unitSize = a.TcpMss - a.Overhead - for dataLength > unitSize { - dataLen, randLength := a.packedDataLen(plainData[offset : offset+unitSize]) - b := make([]byte, dataLen) - a.packData(b, plainData[offset:offset+unitSize], randLength) - a.buffer.Write(b) - dataLength -= unitSize - offset += unitSize - } - if dataLength > 0 { - dataLen, randLength := a.packedDataLen(plainData[offset:]) - b := make([]byte, dataLen) - a.packData(b, plainData[offset:], randLength) - a.buffer.Write(b) - } - return a.buffer.Bytes(), nil -} - -func (a *authChainA) PostDecrypt(plainData []byte) (outData []byte, n int, err error) { - a.buffer.Reset() - key := make([]byte, len(a.userKey)+4) - readlenth := 0 - copy(key, a.userKey) - for len(plainData) > 4 { - binary.LittleEndian.PutUint32(key[len(a.userKey):], a.recvID) - dataLen := (int)((uint(plainData[1]^a.lastServerHash[15]) << 8) + uint(plainData[0]^a.lastServerHash[14])) - randLen := a.getServerRandLen(dataLen, a.Overhead) - length := randLen + dataLen - if length >= 4096 { - return nil, 0, ssr.ErrAuthChainDataLengthError - } - length += 4 - if length > len(plainData) { - break - } - - hash := a.hmac(key, plainData[:length-2]) - if !bytes.Equal(hash[:2], plainData[length-2:length]) { - return nil, 0, ssr.ErrAuthChainIncorrectHMAC - } - var dataPos int - if dataLen > 0 && randLen > 0 { - dataPos = 2 + getRandStartPos(&a.randomServer, randLen) - } else { - dataPos = 2 - } - b := make([]byte, dataLen) - a.cipher.Decrypt(b, plainData[dataPos:dataPos+dataLen]) - a.buffer.Write(b) - if a.recvID == 1 { - a.TcpMss = int(binary.LittleEndian.Uint16(a.buffer.Next(2))) - } - a.lastServerHash = hash - a.recvID++ - plainData = plainData[length:] - readlenth += length - - } - return a.buffer.Bytes(), readlenth, nil -} - -func (a *authChainA) GetOverhead() int { - return 4 -} diff --git a/proxy/ssr/internal/protocol/auth_chain_b.go b/proxy/ssr/internal/protocol/auth_chain_b.go deleted file mode 100644 index 35458a2..0000000 --- a/proxy/ssr/internal/protocol/auth_chain_b.go +++ /dev/null @@ -1,81 +0,0 @@ -package protocol - -import ( - "bytes" - "sort" - - "github.com/nadoo/glider/proxy/ssr/internal/tools" -) - -func init() { - register("auth_chain_b", NewAuthChainB) -} - -func NewAuthChainB() IProtocol { - a := &authChainA{ - salt: "auth_chain_b", - hmac: tools.HmacMD5, - hashDigest: tools.SHA1Sum, - rnd: authChainBGetRandLen, - recvInfo: recvInfo{ - recvID: 1, - buffer: new(bytes.Buffer), - }, - } - return a -} - -func (a *authChainA) authChainBInitDataSize() { - if len(a.Key) == 0 { - return - } - // libev version - random := &a.randomServer - random.InitFromBin(a.Key) - length := random.Next()%8 + 4 - a.dataSizeList = make([]int, length) - for i := range int(length) { - a.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440) - } - sort.Ints(a.dataSizeList) - - length = random.Next()%16 + 8 - a.dataSizeList2 = make([]int, length) - for i := range int(length) { - a.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440) - } - sort.Ints(a.dataSizeList2) -} - -func authChainBGetRandLen(dataLength int, random *tools.Shift128plusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int { - if dataLength > 1440 { - return 0 - } - random.InitFromBinDatalen(lastHash[:16], dataLength) - // libev version, upper_bound - pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead }) - finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList)) - if finalPos < uint64(len(dataSizeList)) { - return dataSizeList[finalPos] - dataLength - overhead - } - // libev version, upper_bound - pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead }) - finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2)) - if finalPos < uint64(len(dataSizeList2)) { - return dataSizeList2[finalPos] - dataLength - overhead - } - if finalPos < uint64(pos+len(dataSizeList2)-1) { - return 0 - } - - if dataLength > 1300 { - return int(random.Next() % 31) - } - if dataLength > 900 { - return int(random.Next() % 127) - } - if dataLength > 400 { - return int(random.Next() % 521) - } - return int(random.Next() % 1021) -} diff --git a/proxy/ssr/internal/protocol/auth_sha1_v4.go b/proxy/ssr/internal/protocol/auth_sha1_v4.go deleted file mode 100644 index ce2dd55..0000000 --- a/proxy/ssr/internal/protocol/auth_sha1_v4.go +++ /dev/null @@ -1,227 +0,0 @@ -package protocol - -import ( - "bytes" - crand "crypto/rand" - "encoding/binary" - "math/rand/v2" - "time" - - "github.com/nadoo/glider/proxy/ssr/internal/ssr" - "github.com/nadoo/glider/proxy/ssr/internal/tools" -) - -func init() { - register("auth_sha1_v4", NewAuthSHA1v4) -} - -type authSHA1v4 struct { - ssr.ServerInfo - data *AuthData - hasSentHeader bool - buffer bytes.Buffer -} - -func NewAuthSHA1v4() IProtocol { - a := &authSHA1v4{} - return a -} - -func (a *authSHA1v4) SetServerInfo(s *ssr.ServerInfo) { - a.ServerInfo = *s -} - -func (a *authSHA1v4) GetServerInfo() (s *ssr.ServerInfo) { - return &a.ServerInfo -} - -func (a *authSHA1v4) SetData(data any) { - if auth, ok := data.(*AuthData); ok { - a.data = auth - } -} - -func (a *authSHA1v4) GetData() any { - if a.data == nil { - a.data = &AuthData{} - } - return a.data -} - -func (a *authSHA1v4) packData(data []byte) (outData []byte) { - dataLength := len(data) - randLength := 1 - - if dataLength <= 1300 { - if dataLength > 400 { - randLength += rand.IntN(128) - } else { - randLength += rand.IntN(1024) - } - } - - outLength := randLength + dataLength + 8 - outData = make([]byte, outLength) - // 0~1, out length - binary.BigEndian.PutUint16(outData[0:2], uint16(outLength&0xFFFF)) - // 2~3, crc of out length - crc32 := ssr.CalcCRC32(outData, 2, 0xFFFFFFFF) - binary.LittleEndian.PutUint16(outData[2:4], uint16(crc32&0xFFFF)) - // 4, rand length - if randLength < 128 { - outData[4] = uint8(randLength & 0xFF) - } else { - outData[4] = uint8(0xFF) - binary.BigEndian.PutUint16(outData[5:7], uint16(randLength&0xFFFF)) - } - // rand length+4~out length-4, data - if dataLength > 0 { - copy(outData[randLength+4:], data) - } - // out length-4~end, adler32 of full data - adler := ssr.CalcAdler32(outData[:outLength-4]) - binary.LittleEndian.PutUint32(outData[outLength-4:], adler) - - return outData -} - -func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) { - - dataLength := len(data) - randLength := 1 - if dataLength <= 1300 { - if dataLength > 400 { - randLength += rand.IntN(128) - } else { - randLength += rand.IntN(1024) - } - } - dataOffset := randLength + 4 + 2 - outLength := dataOffset + dataLength + 12 + ssr.ObfsHMACSHA1Len - outData = make([]byte, outLength) - a.data.connectionID++ - if a.data.connectionID > 0xFF000000 { - a.data.clientID = nil - } - if len(a.data.clientID) == 0 { - a.data.clientID = make([]byte, 8) - crand.Read(a.data.clientID) - b := make([]byte, 4) - crand.Read(b) - a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF - } - // 0-1, out length - binary.BigEndian.PutUint16(outData[0:2], uint16(outLength&0xFFFF)) - - // 2~6, crc of out length+salt+key - salt := []byte("auth_sha1_v4") - crcData := make([]byte, len(salt)+a.KeyLen+2) - copy(crcData[0:2], outData[0:2]) - copy(crcData[2:], salt) - copy(crcData[2+len(salt):], a.Key) - crc32 := ssr.CalcCRC32(crcData, len(crcData), 0xFFFFFFFF) - // 2~6, crc of out length+salt+key - binary.LittleEndian.PutUint32(outData[2:], crc32) - // 6~rand length+6, rand numbers - crand.Read(outData[dataOffset-randLength : dataOffset]) - // 6, rand length - if randLength < 128 { - outData[6] = byte(randLength & 0xFF) - } else { - // 6, magic number 0xFF - outData[6] = 0xFF - // 7-8, rand length - binary.BigEndian.PutUint16(outData[7:9], uint16(randLength&0xFFFF)) - } - // rand length+6~rand length+10, time stamp - now := time.Now().Unix() - binary.LittleEndian.PutUint32(outData[dataOffset:dataOffset+4], uint32(now)) - // rand length+10~rand length+14, client ID - copy(outData[dataOffset+4:dataOffset+4+4], a.data.clientID[0:4]) - // rand length+14~rand length+18, connection ID - binary.LittleEndian.PutUint32(outData[dataOffset+8:dataOffset+8+4], a.data.connectionID) - // rand length+18~rand length+18+data length, data - copy(outData[dataOffset+12:], data) - - key := make([]byte, a.IVLen+a.KeyLen) - copy(key, a.IV) - copy(key[a.IVLen:], a.Key) - - h := tools.HmacSHA1(key, outData[:outLength-ssr.ObfsHMACSHA1Len]) - // out length-10~out length/rand length+18+data length~end, hmac - copy(outData[outLength-ssr.ObfsHMACSHA1Len:], h[0:ssr.ObfsHMACSHA1Len]) - return outData -} - -func (a *authSHA1v4) PreEncrypt(plainData []byte) (outData []byte, err error) { - a.buffer.Reset() - dataLength := len(plainData) - offset := 0 - if !a.hasSentHeader && dataLength > 0 { - headSize := ssr.GetHeadSize(plainData, 30) - if headSize > dataLength { - headSize = dataLength - } - a.buffer.Write(a.packAuthData(plainData[:headSize])) - offset += headSize - dataLength -= headSize - a.hasSentHeader = true - } - const blockSize = 4096 - for dataLength > blockSize { - a.buffer.Write(a.packData(plainData[offset : offset+blockSize])) - offset += blockSize - dataLength -= blockSize - } - if dataLength > 0 { - a.buffer.Write(a.packData(plainData[offset:])) - } - - return a.buffer.Bytes(), nil -} - -func (a *authSHA1v4) PostDecrypt(plainData []byte) (outData []byte, n int, err error) { - a.buffer.Reset() - dataLength := len(plainData) - plainLength := dataLength - for dataLength > 4 { - crc32 := ssr.CalcCRC32(plainData, 2, 0xFFFFFFFF) - if binary.LittleEndian.Uint16(plainData[2:4]) != uint16(crc32&0xFFFF) { - //common.Error("auth_sha1_v4 post decrypt data crc32 error") - return nil, 0, ssr.ErrAuthSHA1v4CRC32Error - } - length := int(binary.BigEndian.Uint16(plainData[0:2])) - if length >= 8192 || length < 8 { - //common.Error("auth_sha1_v4 post decrypt data length error") - dataLength = 0 - plainData = nil - return nil, 0, ssr.ErrAuthSHA1v4DataLengthError - } - if length > dataLength { - break - } - - if ssr.CheckAdler32(plainData, length) { - pos := int(plainData[4]) - if pos != 0xFF { - pos += 4 - } else { - pos = int(binary.BigEndian.Uint16(plainData[5:5+2])) + 4 - } - outLength := length - pos - 4 - a.buffer.Write(plainData[pos : pos+outLength]) - dataLength -= length - plainData = plainData[length:] - } else { - //common.Error("auth_sha1_v4 post decrypt incorrect checksum") - dataLength = 0 - plainData = nil - return nil, 0, ssr.ErrAuthSHA1v4IncorrectChecksum - } - } - return a.buffer.Bytes(), plainLength - dataLength, nil -} - -func (a *authSHA1v4) GetOverhead() int { - return 7 -} diff --git a/proxy/ssr/internal/protocol/base.go b/proxy/ssr/internal/protocol/base.go deleted file mode 100644 index 0057991..0000000 --- a/proxy/ssr/internal/protocol/base.go +++ /dev/null @@ -1,47 +0,0 @@ -package protocol - -import ( - "strings" - "sync" - - "github.com/nadoo/glider/proxy/ssr/internal/ssr" - "github.com/nadoo/glider/proxy/ssr/internal/tools" -) - -type creator func() IProtocol - -var ( - creatorMap = make(map[string]creator) -) - -type hmacMethod func(key []byte, data []byte) []byte -type hashDigestMethod func(data []byte) []byte -type rndMethod func(dataLength int, random *tools.Shift128plusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int - -type IProtocol interface { - SetServerInfo(s *ssr.ServerInfo) - GetServerInfo() *ssr.ServerInfo - PreEncrypt(data []byte) ([]byte, error) - PostDecrypt(data []byte) ([]byte, int, error) - SetData(data any) - GetData() any - GetOverhead() int -} - -type AuthData struct { - clientID []byte - connectionID uint32 - mutex sync.Mutex -} - -func register(name string, c creator) { - creatorMap[name] = c -} - -func NewProtocol(name string) IProtocol { - c, ok := creatorMap[strings.ToLower(name)] - if ok { - return c() - } - return nil -} diff --git a/proxy/ssr/internal/protocol/origin.go b/proxy/ssr/internal/protocol/origin.go deleted file mode 100644 index 3bf67f6..0000000 --- a/proxy/ssr/internal/protocol/origin.go +++ /dev/null @@ -1,46 +0,0 @@ -package protocol - -import ( - "github.com/nadoo/glider/proxy/ssr/internal/ssr" -) - -func init() { - register("origin", NewOrigin) -} - -type origin struct { - ssr.ServerInfo -} - -func NewOrigin() IProtocol { - a := &origin{} - return a -} - -func (o *origin) SetServerInfo(s *ssr.ServerInfo) { - o.ServerInfo = *s -} - -func (o *origin) GetServerInfo() (s *ssr.ServerInfo) { - return &o.ServerInfo -} - -func (o *origin) PreEncrypt(data []byte) (encryptedData []byte, err error) { - return data, nil -} - -func (o *origin) PostDecrypt(data []byte) ([]byte, int, error) { - return data, len(data), nil -} - -func (o *origin) SetData(data any) { - -} - -func (o *origin) GetData() any { - return nil -} - -func (o *origin) GetOverhead() int { - return 0 -} diff --git a/proxy/ssr/internal/protocol/verify_sha1.go b/proxy/ssr/internal/protocol/verify_sha1.go deleted file mode 100644 index 15966d7..0000000 --- a/proxy/ssr/internal/protocol/verify_sha1.go +++ /dev/null @@ -1,105 +0,0 @@ -package protocol - -import ( - "bytes" - "encoding/binary" - - "github.com/nadoo/glider/proxy/ssr/internal/ssr" - "github.com/nadoo/glider/proxy/ssr/internal/tools" -) - -func init() { - register("verify_sha1", NewVerifySHA1) - register("ota", NewVerifySHA1) -} - -type verifySHA1 struct { - ssr.ServerInfo - hasSentHeader bool - buffer bytes.Buffer - chunkId uint32 -} - -const ( - oneTimeAuthMask byte = 0x10 -) - -func NewVerifySHA1() IProtocol { - a := &verifySHA1{} - return a -} - -func (v *verifySHA1) otaConnectAuth(data []byte) []byte { - return append(data, tools.HmacSHA1(append(v.IV, v.Key...), data)...) -} - -func (v *verifySHA1) otaReqChunkAuth(chunkId uint32, data []byte) []byte { - nb := make([]byte, 2) - binary.BigEndian.PutUint16(nb, uint16(len(data))) - chunkIdBytes := make([]byte, 4) - binary.BigEndian.PutUint32(chunkIdBytes, chunkId) - header := append(nb, tools.HmacSHA1(append(v.IV, chunkIdBytes...), data)...) - return append(header, data...) -} - -func (v *verifySHA1) otaVerifyAuth(iv []byte, chunkId uint32, data []byte, expectedHmacSha1 []byte) bool { - chunkIdBytes := make([]byte, 4) - binary.BigEndian.PutUint32(chunkIdBytes, chunkId) - actualHmacSha1 := tools.HmacSHA1(append(iv, chunkIdBytes...), data) - return bytes.Equal(expectedHmacSha1, actualHmacSha1) -} - -func (v *verifySHA1) getAndIncreaseChunkId() (chunkId uint32) { - chunkId = v.chunkId - v.chunkId += 1 - return -} - -func (v *verifySHA1) SetServerInfo(s *ssr.ServerInfo) { - v.ServerInfo = *s -} - -func (v *verifySHA1) GetServerInfo() (s *ssr.ServerInfo) { - return &v.ServerInfo -} - -func (v *verifySHA1) SetData(data any) { - -} - -func (v *verifySHA1) GetData() any { - return nil -} - -func (v *verifySHA1) PreEncrypt(data []byte) (encryptedData []byte, err error) { - v.buffer.Reset() - dataLength := len(data) - offset := 0 - if !v.hasSentHeader { - data[0] |= oneTimeAuthMask - v.buffer.Write(v.otaConnectAuth(data[:v.HeadLen])) - v.hasSentHeader = true - dataLength -= v.HeadLen - offset += v.HeadLen - } - const blockSize = 4096 - for dataLength > blockSize { - chunkId := v.getAndIncreaseChunkId() - v.buffer.Write(v.otaReqChunkAuth(chunkId, data[offset:offset+blockSize])) - dataLength -= blockSize - offset += blockSize - } - if dataLength > 0 { - chunkId := v.getAndIncreaseChunkId() - v.buffer.Write(v.otaReqChunkAuth(chunkId, data[offset:])) - } - return v.buffer.Bytes(), nil -} - -func (v *verifySHA1) PostDecrypt(data []byte) ([]byte, int, error) { - return data, len(data), nil -} - -func (v *verifySHA1) GetOverhead() int { - return 0 -} diff --git a/proxy/ssr/internal/ssr/adler32.go b/proxy/ssr/internal/ssr/adler32.go deleted file mode 100644 index 6bda937..0000000 --- a/proxy/ssr/internal/ssr/adler32.go +++ /dev/null @@ -1,31 +0,0 @@ -package ssr - -import "encoding/binary" - -func calcShortAdler32(input []byte, a, b uint32) (uint32, uint32) { - for _, i := range input { - a += uint32(i) - b += a - } - a %= 65521 - b %= 65521 - return a, b -} - -func CalcAdler32(input []byte) uint32 { - var a uint32 = 1 - var b uint32 = 0 - const nMax = 5552 - for length := len(input); length > nMax; length -= nMax { - a, b = calcShortAdler32(input[:nMax], a, b) - input = input[nMax:] - } - a, b = calcShortAdler32(input, a, b) - return (b << 16) + a -} - -func CheckAdler32(input []byte, l int) bool { - adler32 := CalcAdler32(input[:l-4]) - checksum := binary.LittleEndian.Uint32(input[l-4:]) - return adler32 == checksum -} diff --git a/proxy/ssr/internal/ssr/crc32.go b/proxy/ssr/internal/ssr/crc32.go deleted file mode 100644 index a36a79c..0000000 --- a/proxy/ssr/internal/ssr/crc32.go +++ /dev/null @@ -1,52 +0,0 @@ -package ssr - -import "encoding/binary" - -var ( - crc32Table = make([]uint32, 256) -) - -func init() { - createCRC32Table() -} - -func createCRC32Table() { - for i := range 256 { - crc := uint32(i) - for j := 8; j > 0; j-- { - if crc&1 == 1 { - crc = (crc >> 1) ^ 0xEDB88320 - } else { - crc >>= 1 - } - } - crc32Table[i] = crc - } -} - -func CalcCRC32(input []byte, length int, value uint32) uint32 { - value = 0xFFFFFFFF - return DoCalcCRC32(input, 0, length, value) -} - -func DoCalcCRC32(input []byte, index int, length int, value uint32) uint32 { - buffer := input - for i := index; i < length; i++ { - value = (value >> 8) ^ crc32Table[byte(value&0xFF)^buffer[i]] - } - return value ^ 0xFFFFFFFF -} - -func DoSetCRC32(buffer []byte, index int, length int) { - crc := CalcCRC32(buffer[:length-4], length-4, 0xFFFFFFFF) - binary.LittleEndian.PutUint32(buffer[length-4:], crc^0xFFFFFFFF) -} - -func SetCRC32(buffer []byte, length int) { - DoSetCRC32(buffer, 0, length) -} - -func CheckCRC32(buffer []byte, length int) bool { - crc := CalcCRC32(buffer, length, 0xFFFFFFFF) - return crc == 0xFFFFFFFF -} diff --git a/proxy/ssr/internal/ssr/obfs.go b/proxy/ssr/internal/ssr/obfs.go deleted file mode 100644 index e7258fa..0000000 --- a/proxy/ssr/internal/ssr/obfs.go +++ /dev/null @@ -1,59 +0,0 @@ -package ssr - -import "errors" - -const ObfsHMACSHA1Len = 10 - -var ( - ErrAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error") - ErrAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error") - ErrAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum") - ErrAuthAES128IncorrectHMAC = errors.New("auth_aes128_* post decrypt incorrect hmac") - ErrAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch") - ErrAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch") - ErrAuthChainIncorrectHMAC = errors.New("auth_chain_* post decrypt incorrect hmac") - ErrAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum") - ErrAuthAES128PosOutOfRange = errors.New("auth_aes128_* post decrypt pos out of range") - ErrTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data") - ErrTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed") - ErrTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number") -) - -type ServerInfo struct { - Host string - Port uint16 - Param string - IV []byte - IVLen int - RecvIV []byte - RecvIVLen int - Key []byte - KeyLen int - HeadLen int - TcpMss int - Overhead int -} - -func GetHeadSize(data []byte, defaultValue int) int { - if data == nil || len(data) < 2 { - return defaultValue - } - headType := data[0] & 0x07 - switch headType { - case 1: - // IPv4 1+4+2 - return 7 - case 4: - // IPv6 1+16+2 - return 19 - case 3: - // domain name, variant length - return 4 + int(data[1]) - } - - return defaultValue -} - -func (s *ServerInfo) SetHeadLen(data []byte, defaultValue int) { - s.HeadLen = GetHeadSize(data, defaultValue) -} diff --git a/proxy/ssr/internal/tools/encrypt.go b/proxy/ssr/internal/tools/encrypt.go deleted file mode 100644 index bb81b07..0000000 --- a/proxy/ssr/internal/tools/encrypt.go +++ /dev/null @@ -1,51 +0,0 @@ -package tools - -import ( - "crypto/hmac" - "crypto/md5" - "crypto/sha1" -) - -func HmacMD5(key []byte, data []byte) []byte { - hmacMD5 := hmac.New(md5.New, key) - hmacMD5.Write(data) - return hmacMD5.Sum(nil)[:16] -} - -func HmacSHA1(key []byte, data []byte) []byte { - hmacSHA1 := hmac.New(sha1.New, key) - hmacSHA1.Write(data) - return hmacSHA1.Sum(nil)[:20] -} - -func MD5Sum(d []byte) []byte { - h := md5.New() - h.Write(d) - return h.Sum(nil) -} - -func SHA1Sum(d []byte) []byte { - h := sha1.New() - h.Write(d) - return h.Sum(nil) -} - -func EVPBytesToKey(password string, keyLen int) (key []byte) { - const md5Len = 16 - - cnt := (keyLen-1)/md5Len + 1 - m := make([]byte, cnt*md5Len) - copy(m, MD5Sum([]byte(password))) - - // Repeatedly call md5 until bytes generated is enough. - // Each call to md5 uses data: prev md5 sum + password. - d := make([]byte, md5Len+len(password)) - start := 0 - for i := 1; i < cnt; i++ { - start += md5Len - copy(d, m[start-md5Len:start]) - copy(d[md5Len:], password) - copy(m[start:], MD5Sum(d)) - } - return m[:keyLen] -} diff --git a/proxy/ssr/internal/tools/obfsutil.go b/proxy/ssr/internal/tools/obfsutil.go deleted file mode 100644 index c5d4bac..0000000 --- a/proxy/ssr/internal/tools/obfsutil.go +++ /dev/null @@ -1,53 +0,0 @@ -package tools - -import ( - "encoding/binary" - "unsafe" -) - -func IsLittleEndian() bool { - const N int = int(unsafe.Sizeof(0)) - x := 0x1234 - p := unsafe.Pointer(&x) - p2 := (*[N]byte)(p) - if p2[0] == 0 { - return false - } else { - return true - } -} - -type Shift128plusContext struct { - v [2]uint64 -} - -func (ctx *Shift128plusContext) InitFromBin(bin []byte) { - var fillBin [16]byte - copy(fillBin[:], bin) - - ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8]) - ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:]) -} - -func (ctx *Shift128plusContext) InitFromBinDatalen(bin []byte, datalen int) { - var fillBin [16]byte - copy(fillBin[:], bin) - binary.LittleEndian.PutUint16(fillBin[:2], uint16(datalen)) - - ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8]) - ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:]) - - for range 4 { - ctx.Next() - } -} - -func (ctx *Shift128plusContext) Next() uint64 { - x := ctx.v[0] - y := ctx.v[1] - ctx.v[0] = y - x ^= x << 23 - x ^= y ^ (x >> 17) ^ (y >> 26) - ctx.v[1] = x - return x + y -} diff --git a/proxy/ssr/ssr.go b/proxy/ssr/ssr.go deleted file mode 100644 index bdd24b4..0000000 --- a/proxy/ssr/ssr.go +++ /dev/null @@ -1,164 +0,0 @@ -package ssr - -import ( - "errors" - "net" - "net/url" - "strconv" - "strings" - - "github.com/nadoo/glider/pkg/log" - "github.com/nadoo/glider/pkg/socks" - "github.com/nadoo/glider/proxy" - - "github.com/nadoo/glider/proxy/ssr/internal" - "github.com/nadoo/glider/proxy/ssr/internal/cipher" - "github.com/nadoo/glider/proxy/ssr/internal/obfs" - "github.com/nadoo/glider/proxy/ssr/internal/protocol" - ssrinfo "github.com/nadoo/glider/proxy/ssr/internal/ssr" -) - -func init() { - proxy.RegisterDialer("ssr", NewSSRDialer) -} - -// SSR struct. -type SSR struct { - dialer proxy.Dialer - addr string - - EncryptMethod string - EncryptPassword string - Obfs string - ObfsParam string - ObfsData any - Protocol string - ProtocolParam string - ProtocolData any -} - -// NewSSR returns a shadowsocksr proxy, ssr://method:pass@host:port/query -func NewSSR(s string, d proxy.Dialer) (*SSR, error) { - u, err := url.Parse(s) - if err != nil { - log.F("[ssr] parse err: %s", err) - return nil, err - } - - addr := u.Host - method := u.User.Username() - pass, _ := u.User.Password() - - p := &SSR{ - dialer: d, - addr: addr, - EncryptMethod: method, - EncryptPassword: pass, - } - - query := u.Query() - p.Protocol = query.Get("protocol") - p.ProtocolParam = query.Get("protocol_param") - p.Obfs = query.Get("obfs") - p.ObfsParam = query.Get("obfs_param") - - p.ProtocolData = new(protocol.AuthData) - - return p, nil -} - -// NewSSRDialer returns a ssr proxy dialer. -func NewSSRDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { - return NewSSR(s, d) -} - -// Addr returns forwarder's address -func (s *SSR) Addr() string { - if s.addr == "" { - return s.dialer.Addr() - } - return s.addr -} - -// Dial connects to the address addr on the network net via the proxy. -func (s *SSR) Dial(network, addr string) (net.Conn, error) { - target := socks.ParseAddr(addr) - if target == nil { - return nil, errors.New("[ssr] unable to parse address: " + addr) - } - - cipher, err := cipher.NewStreamCipher(s.EncryptMethod, s.EncryptPassword) - if err != nil { - return nil, err - } - - c, err := s.dialer.Dial("tcp", s.addr) - if err != nil { - log.F("[ssr] dial to %s error: %s", s.addr, err) - return nil, err - } - - ssrconn := internal.NewSSTCPConn(c, cipher) - if ssrconn.Conn == nil || ssrconn.RemoteAddr() == nil { - return nil, errors.New("[ssr] nil connection") - } - - // should initialize obfs/protocol now - rs := strings.Split(ssrconn.RemoteAddr().String(), ":") - port, _ := strconv.Atoi(rs[1]) - - ssrconn.IObfs = obfs.NewObfs(s.Obfs) - if ssrconn.IObfs == nil { - return nil, errors.New("[ssr] unsupported obfs type: " + s.Obfs) - } - - obfsServerInfo := &ssrinfo.ServerInfo{ - Host: rs[0], - Port: uint16(port), - TcpMss: 1460, - Param: s.ObfsParam, - } - ssrconn.IObfs.SetServerInfo(obfsServerInfo) - - ssrconn.IProtocol = protocol.NewProtocol(s.Protocol) - if ssrconn.IProtocol == nil { - return nil, errors.New("[ssr] unsupported protocol type: " + s.Protocol) - } - - protocolServerInfo := &ssrinfo.ServerInfo{ - Host: rs[0], - Port: uint16(port), - TcpMss: 1460, - Param: s.ProtocolParam, - } - ssrconn.IProtocol.SetServerInfo(protocolServerInfo) - - if s.ObfsData == nil { - s.ObfsData = ssrconn.IObfs.GetData() - } - ssrconn.IObfs.SetData(s.ObfsData) - - if s.ProtocolData == nil { - s.ProtocolData = ssrconn.IProtocol.GetData() - } - ssrconn.IProtocol.SetData(s.ProtocolData) - - if _, err := ssrconn.Write(target); err != nil { - ssrconn.Close() - return nil, err - } - - return ssrconn, err -} - -// DialUDP connects to the given address via the proxy. -func (s *SSR) DialUDP(network, addr string) (net.PacketConn, error) { - return nil, proxy.ErrNotSupported -} - -func init() { - proxy.AddUsage("ssr", ` -SSR scheme: - ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz -`) -}