ssr: drop ssr protocol support
Some checks are pending
Build / Build (push) Waiting to run

This commit is contained in:
nadoo 2026-06-25 23:18:47 +08:00
parent 86f9f078fd
commit e7b12e36a9
27 changed files with 3 additions and 2818 deletions

View File

@ -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) [![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) [![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. 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) |AnyTLSc |√| |√|√|anytls cleartext(without tls)
|VLESS |√| |√|√|client & server |VLESS |√| |√|√|client & server
|VMess | | |√|√|client only |VMess | | |√|√|client only
|SSR | | |√| |client only
|SSH | | |√| |client only |SSH | | |√| |client only
|SOCKS4 | | |√| |client only |SOCKS4 | | |√| |client only
|SOCKS4A | | |√| |client only |SOCKS4A | | |√| |client only
@ -120,7 +119,7 @@ OPTION:
-checkdisabledonly -checkdisabledonly
check disabled fowarders only check disabled fowarders only
-checkinterval int -checkinterval int
fowarder check interval(seconds) (default 30) fowarder check interval(seconds) (default 30)ß
-checklatencysamples int -checklatencysamples int
use the average latency of the latest N checks (default 10) use the average latency of the latest N checks (default 10)
-checktimeout int -checktimeout int
@ -200,7 +199,7 @@ URL:
SCHEME: 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 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. 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] ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
timeout: timeout of ssh handshake and channel operation, default: 5 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 client scheme:
tls://host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&alpn=proto1][&alpn=proto2] tls://host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&alpn=proto1][&alpn=proto2]

View File

@ -115,9 +115,6 @@ listen=127.0.0.1:8443
# SS proxy as forwarder # SS proxy as forwarder
# forward=ss://method:pass@1.1.1.1:8443 # 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 # ssh forwarder
# forward=ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS] # forward=ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
# forward=ssh://root:pass@host:port # forward=ssh://root:pass@host:port

View File

@ -17,7 +17,6 @@ import (
_ "github.com/nadoo/glider/proxy/socks5" _ "github.com/nadoo/glider/proxy/socks5"
_ "github.com/nadoo/glider/proxy/ss" _ "github.com/nadoo/glider/proxy/ss"
_ "github.com/nadoo/glider/proxy/ssh" _ "github.com/nadoo/glider/proxy/ssh"
_ "github.com/nadoo/glider/proxy/ssr"
_ "github.com/nadoo/glider/proxy/tcp" _ "github.com/nadoo/glider/proxy/tcp"
_ "github.com/nadoo/glider/proxy/tls" _ "github.com/nadoo/glider/proxy/tls"
_ "github.com/nadoo/glider/proxy/trojan" _ "github.com/nadoo/glider/proxy/trojan"

4
go.mod
View File

@ -4,9 +4,6 @@ go 1.26
require ( require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da 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/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e
github.com/nadoo/conflag v0.3.1 github.com/nadoo/conflag v0.3.1
github.com/nadoo/ipset v0.5.0 github.com/nadoo/ipset v0.5.0
@ -16,7 +13,6 @@ require (
) )
require ( require (
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/reedsolomon v1.14.1 // indirect github.com/klauspost/reedsolomon v1.14.1 // indirect
github.com/pierrec/lz4/v4 v4.1.27 // indirect github.com/pierrec/lz4/v4 v4.1.27 // indirect

8
go.sum
View File

@ -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/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 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 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= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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]
}

View File

@ -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
}

View File

@ -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
`)
}