Refactor: new Shadowsocks validator (#629)

* Refactor: new Shadowsocks validator

* Fix NoneCliper cannot work

* Feat: refine the size of drain

* fix: fix validator after merge 'main'

* fix: UDP user logic

* style: refine code style
This commit is contained in:
秋のかえで 2021-11-01 10:10:26 +08:00 committed by GitHub
parent dd6769954c
commit 63d0cb1bd6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 187 deletions

View file

@ -7,8 +7,6 @@ import (
"crypto/md5" "crypto/md5"
"crypto/sha1" "crypto/sha1"
"io" "io"
"reflect"
"strconv"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
@ -28,6 +26,10 @@ type MemoryAccount struct {
replayFilter antireplay.GeneralizedReplayFilter replayFilter antireplay.GeneralizedReplayFilter
} }
var (
ErrIVNotUnique = newError("IV is not unique")
)
// Equals implements protocol.Account.Equals(). // Equals implements protocol.Account.Equals().
func (a *MemoryAccount) Equals(another protocol.Account) bool { func (a *MemoryAccount) Equals(another protocol.Account) bool {
if account, ok := another.(*MemoryAccount); ok { if account, ok := another.(*MemoryAccount); ok {
@ -43,24 +45,7 @@ func (a *MemoryAccount) CheckIV(iv []byte) error {
if a.replayFilter.Check(iv) { if a.replayFilter.Check(iv) {
return nil return nil
} }
return newError("IV is not unique") return ErrIVNotUnique
}
func (a *MemoryAccount) GetCipherName() string {
switch a.Cipher.(type) {
case *AEADCipher:
switch reflect.ValueOf(a.Cipher.(*AEADCipher).AEADAuthCreator).Pointer() {
case reflect.ValueOf(createAesGcm).Pointer():
keyBytes := a.Cipher.(*AEADCipher).KeyBytes
return "AES_" + strconv.FormatInt(int64(keyBytes*8), 10) + "_GCM"
case reflect.ValueOf(createChaCha20Poly1305).Pointer():
return "CHACHA20_POLY1305"
}
case *NoneCipher:
return "NONE"
}
return ""
} }
func createAesGcm(key []byte) cipher.AEAD { func createAesGcm(key []byte) cipher.AEAD {

View file

@ -1,11 +1,7 @@
package shadowsocks package shadowsocks
import ( import (
"crypto/cipher"
"crypto/hmac"
"crypto/rand" "crypto/rand"
"crypto/sha256"
"hash/crc32"
"io" "io"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
@ -54,10 +50,7 @@ func (r *FullReader) Read(p []byte) (n int, err error) {
// ReadTCPSession reads a Shadowsocks TCP session from the given reader, returns its header and remaining parts. // ReadTCPSession reads a Shadowsocks TCP session from the given reader, returns its header and remaining parts.
func ReadTCPSession(validator *Validator, reader io.Reader) (*protocol.RequestHeader, buf.Reader, error) { func ReadTCPSession(validator *Validator, reader io.Reader) (*protocol.RequestHeader, buf.Reader, error) {
hashkdf := hmac.New(sha256.New, []byte("SSBSKDF")) behaviorSeed := validator.GetBehaviorSeed()
behaviorSeed := crc32.ChecksumIEEE(hashkdf.Sum(nil))
behaviorRand := dice.NewDeterministicDice(int64(behaviorSeed)) behaviorRand := dice.NewDeterministicDice(int64(behaviorSeed))
BaseDrainSize := behaviorRand.Roll(3266) BaseDrainSize := behaviorRand.Roll(3266)
RandDrainMax := behaviorRand.Roll(64) + 1 RandDrainMax := behaviorRand.Roll(64) + 1
@ -65,72 +58,52 @@ func ReadTCPSession(validator *Validator, reader io.Reader) (*protocol.RequestHe
DrainSize := BaseDrainSize + 16 + 38 + RandDrainRolled DrainSize := BaseDrainSize + 16 + 38 + RandDrainRolled
readSizeRemain := DrainSize readSizeRemain := DrainSize
var r2 buf.Reader var r buf.Reader
buffer := buf.New() buffer := buf.New()
defer buffer.Release() defer buffer.Release()
var user *protocol.MemoryUser if _, err := buffer.ReadFullFrom(reader, 50); err != nil {
var ivLen int32
var iv []byte
var err error
count := validator.Count()
if count == 0 {
readSizeRemain -= int(buffer.Len()) readSizeRemain -= int(buffer.Len())
DrainConnN(reader, readSizeRemain) DrainConnN(reader, readSizeRemain)
return nil, nil, newError("invalid user") return nil, nil, newError("failed to read 50 bytes").Base(err)
} else if count > 1 { }
var aead cipher.AEAD
if _, err := buffer.ReadFullFrom(reader, 50); err != nil { bs := buffer.Bytes()
readSizeRemain -= int(buffer.Len()) user, aead, _, ivLen, err := validator.Get(bs, protocol.RequestCommandTCP)
DrainConnN(reader, readSizeRemain)
return nil, nil, newError("failed to read 50 bytes").Base(err)
}
bs := buffer.Bytes() switch err {
user, aead, _, ivLen, err = validator.Get(bs, protocol.RequestCommandTCP) case ErrNotFound:
readSizeRemain -= int(buffer.Len())
DrainConnN(reader, readSizeRemain)
return nil, nil, newError("failed to match an user").Base(err)
case ErrIVNotUnique:
readSizeRemain -= int(buffer.Len())
DrainConnN(reader, readSizeRemain)
return nil, nil, newError("failed iv check").Base(err)
default:
reader = &FullReader{reader, bs[ivLen:]}
readSizeRemain -= int(ivLen)
if user != nil { if aead != nil {
if ivLen > 0 {
iv = append([]byte(nil), bs[:ivLen]...)
}
reader = &FullReader{reader, bs[ivLen:]}
auth := &crypto.AEADAuthenticator{ auth := &crypto.AEADAuthenticator{
AEAD: aead, AEAD: aead,
NonceGenerator: crypto.GenerateInitialAEADNonce(), NonceGenerator: crypto.GenerateInitialAEADNonce(),
} }
r2 = crypto.NewAuthenticationReader(auth, &crypto.AEADChunkSizeParser{ r = crypto.NewAuthenticationReader(auth, &crypto.AEADChunkSizeParser{
Auth: auth, Auth: auth,
}, reader, protocol.TransferTypeStream, nil) }, reader, protocol.TransferTypeStream, nil)
} else { } else {
readSizeRemain -= int(buffer.Len()) account := user.Account.(*MemoryAccount)
DrainConnN(reader, readSizeRemain) iv := append([]byte(nil), buffer.BytesTo(ivLen)...)
return nil, nil, newError("failed to match an user").Base(err) r, err = account.Cipher.NewDecryptionReader(account.Key, iv, reader)
} if err != nil {
} else {
user, ivLen = validator.GetOnlyUser()
account := user.Account.(*MemoryAccount)
hashkdf.Write(account.Key)
if ivLen > 0 {
if _, err := buffer.ReadFullFrom(reader, ivLen); err != nil {
readSizeRemain -= int(buffer.Len())
DrainConnN(reader, readSizeRemain) DrainConnN(reader, readSizeRemain)
return nil, nil, newError("failed to read IV").Base(err) return nil, nil, newError("failed to initialize decoding stream").Base(err).AtError()
} }
iv = append([]byte(nil), buffer.BytesTo(ivLen)...)
} }
r, err := account.Cipher.NewDecryptionReader(account.Key, iv, reader)
if err != nil {
readSizeRemain -= int(buffer.Len())
DrainConnN(reader, readSizeRemain)
return nil, nil, newError("failed to initialize decoding stream").Base(err).AtError()
}
r2 = r
} }
br := &buf.BufferedReader{Reader: r2} br := &buf.BufferedReader{Reader: r}
request := &protocol.RequestHeader{ request := &protocol.RequestHeader{
Version: Version, Version: Version,
@ -138,7 +111,6 @@ func ReadTCPSession(validator *Validator, reader io.Reader) (*protocol.RequestHe
Command: protocol.RequestCommandTCP, Command: protocol.RequestCommandTCP,
} }
readSizeRemain -= int(buffer.Len())
buffer.Clear() buffer.Clear()
addr, port, err := addrParser.ReadAddressPort(buffer, br) addr, port, err := addrParser.ReadAddressPort(buffer, br)
@ -157,13 +129,6 @@ func ReadTCPSession(validator *Validator, reader io.Reader) (*protocol.RequestHe
return nil, nil, newError("invalid remote address.") return nil, nil, newError("invalid remote address.")
} }
account := user.Account.(*MemoryAccount)
if ivError := account.CheckIV(iv); ivError != nil {
readSizeRemain -= int(buffer.Len())
DrainConnN(reader, readSizeRemain)
return nil, nil, newError("failed iv check").Base(ivError)
}
return request, br, nil return request, br, nil
} }
@ -273,34 +238,25 @@ func DecodeUDPPacket(validator *Validator, payload *buf.Buffer) (*protocol.Reque
return nil, nil, newError("len(bs) <= 32") return nil, nil, newError("len(bs) <= 32")
} }
var user *protocol.MemoryUser user, _, d, _, err := validator.Get(bs, protocol.RequestCommandUDP)
var err error switch err {
case ErrIVNotUnique:
count := validator.Count() return nil, nil, newError("failed iv check").Base(err)
if count == 0 { case ErrNotFound:
return nil, nil, newError("invalid user") return nil, nil, newError("failed to match an user").Base(err)
} else if count > 1 { default:
var d []byte account := user.Account.(*MemoryAccount)
user, _, d, _, err = validator.Get(bs, protocol.RequestCommandUDP) if account.Cipher.IsAEAD() {
if user != nil {
payload.Clear() payload.Clear()
payload.Write(d) payload.Write(d)
} else { } else {
return nil, nil, newError("failed to decrypt UDP payload").Base(err) if account.Cipher.IVSize() > 0 {
} iv := make([]byte, account.Cipher.IVSize())
} else { copy(iv, payload.BytesTo(account.Cipher.IVSize()))
user, _ = validator.GetOnlyUser() }
account := user.Account.(*MemoryAccount) if err = account.Cipher.DecodePacket(account.Key, payload); err != nil {
return nil, nil, newError("failed to decrypt UDP payload").Base(err)
var iv []byte }
if !account.Cipher.IsAEAD() && account.Cipher.IVSize() > 0 {
// Keep track of IV as it gets removed from payload in DecodePacket.
iv = make([]byte, account.Cipher.IVSize())
copy(iv, payload.BytesTo(account.Cipher.IVSize()))
}
if err = account.Cipher.DecodePacket(account.Key, payload); err != nil {
return nil, nil, newError("failed to decrypt UDP payload").Base(err)
} }
} }

View file

@ -115,10 +115,6 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
panic("no inbound metadata") panic("no inbound metadata")
} }
if s.validator.Count() == 1 {
inbound.User, _ = s.validator.GetOnlyUser()
}
var dest *net.Destination var dest *net.Destination
reader := buf.NewPacketReader(conn) reader := buf.NewPacketReader(conn)

View file

@ -2,112 +2,127 @@ package shadowsocks
import ( import (
"crypto/cipher" "crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"hash/crc64"
"strings" "strings"
"sync" "sync"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
) )
// Validator stores valid Shadowsocks users. // Validator stores valid Shadowsocks users.
type Validator struct { type Validator struct {
// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance. sync.RWMutex
email sync.Map users []*protocol.MemoryUser
users sync.Map
behaviorSeed uint64
behaviorFused bool
} }
// Add a Shadowsocks user, Email must be empty or unique. var (
ErrNotFound = newError("Not Found")
)
// Add a Shadowsocks user.
func (v *Validator) Add(u *protocol.MemoryUser) error { func (v *Validator) Add(u *protocol.MemoryUser) error {
v.Lock()
defer v.Unlock()
account := u.Account.(*MemoryAccount) account := u.Account.(*MemoryAccount)
if !account.Cipher.IsAEAD() && len(v.users) > 0 {
return newError("The cipher is not support Single-port Multi-user")
}
v.users = append(v.users, u)
if !account.Cipher.IsAEAD() && v.Count() > 0 { if !v.behaviorFused {
return newError("The cipher do not support Single-port Multi-user") hashkdf := hmac.New(sha256.New, []byte("SSBSKDF"))
hashkdf.Write(account.Key)
v.behaviorSeed = crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), hashkdf.Sum(nil))
} }
if u.Email != "" {
_, loaded := v.email.LoadOrStore(strings.ToLower(u.Email), u)
if loaded {
return newError("User ", u.Email, " already exists.")
}
}
v.users.Store(string(account.Key)+"&"+account.GetCipherName(), u)
return nil return nil
} }
// Del a Shadowsocks user with a non-empty Email. // Del a Shadowsocks user with a non-empty Email.
func (v *Validator) Del(e string) error { func (v *Validator) Del(email string) error {
if e == "" { if email == "" {
return newError("Email must not be empty.") return newError("Email must not be empty.")
} }
le := strings.ToLower(e)
u, _ := v.email.Load(le) v.Lock()
if u == nil { defer v.Unlock()
return newError("User ", e, " not found.")
email = strings.ToLower(email)
idx := -1
for i, u := range v.users {
if strings.EqualFold(u.Email, email) {
idx = i
break
}
} }
account := u.(*protocol.MemoryUser).Account.(*MemoryAccount)
v.email.Delete(le) if idx == -1 {
v.users.Delete(string(account.Key) + "&" + account.GetCipherName()) return newError("User ", email, " not found.")
}
ulen := len(v.users)
v.users[idx] = v.users[ulen-1]
v.users[ulen-1] = nil
v.users = v.users[:ulen-1]
return nil return nil
} }
// Count the number of Shadowsocks users // Get a Shadowsocks user.
func (v *Validator) Count() int {
length := 0
v.users.Range(func(_, _ interface{}) bool {
length++
return true
})
return length
}
// Get a Shadowsocks user and the user's cipher.
func (v *Validator) Get(bs []byte, command protocol.RequestCommand) (u *protocol.MemoryUser, aead cipher.AEAD, ret []byte, ivLen int32, err error) { func (v *Validator) Get(bs []byte, command protocol.RequestCommand) (u *protocol.MemoryUser, aead cipher.AEAD, ret []byte, ivLen int32, err error) {
var dataSize int v.RLock()
defer v.RUnlock()
switch command { for _, user := range v.users {
case protocol.RequestCommandTCP: if account := user.Account.(*MemoryAccount); account.Cipher.IsAEAD() {
dataSize = 16 aeadCipher := account.Cipher.(*AEADCipher)
case protocol.RequestCommandUDP: ivLen = aeadCipher.IVSize()
dataSize = 8192 iv := bs[:ivLen]
subkey := make([]byte, 32)
subkey = subkey[:aeadCipher.KeyBytes]
hkdfSHA1(account.Key, iv, subkey)
aead = aeadCipher.AEADAuthCreator(subkey)
var matchErr error
switch command {
case protocol.RequestCommandTCP:
data := make([]byte, 16)
ret, matchErr = aead.Open(data[:0], data[4:16], bs[ivLen:ivLen+18], nil)
case protocol.RequestCommandUDP:
data := make([]byte, 8192)
ret, matchErr = aead.Open(data[:0], data[8180:8192], bs[ivLen:], nil)
}
if matchErr == nil {
u = user
err = account.CheckIV(iv)
return
}
} else {
u = user
ivLen = user.Account.(*MemoryAccount).Cipher.IVSize()
// err = user.Account.(*MemoryAccount).CheckIV(bs[:ivLen]) // The IV size of None Cipher is 0.
return
}
} }
var aeadCipher *AEADCipher return nil, nil, nil, 0, ErrNotFound
subkey := make([]byte, 32)
data := make([]byte, dataSize)
v.users.Range(func(key, user interface{}) bool {
account := user.(*protocol.MemoryUser).Account.(*MemoryAccount)
aeadCipher = account.Cipher.(*AEADCipher)
ivLen = aeadCipher.IVSize()
subkey = subkey[:aeadCipher.KeyBytes]
hkdfSHA1(account.Key, bs[:ivLen], subkey)
aead = aeadCipher.AEADAuthCreator(subkey)
switch command {
case protocol.RequestCommandTCP:
ret, err = aead.Open(data[:0], data[4:16], bs[ivLen:ivLen+18], nil)
case protocol.RequestCommandUDP:
ret, err = aead.Open(data[:0], data[8180:8192], bs[ivLen:], nil)
}
if err == nil {
u = user.(*protocol.MemoryUser)
return false
}
return true
})
return
} }
// Get the only user without authentication func (v *Validator) GetBehaviorSeed() uint64 {
func (v *Validator) GetOnlyUser() (u *protocol.MemoryUser, ivLen int32) { v.Lock()
v.users.Range(func(_, user interface{}) bool { defer v.Unlock()
u = user.(*protocol.MemoryUser)
return false
})
ivLen = u.Account.(*MemoryAccount).Cipher.IVSize()
return v.behaviorFused = true
if v.behaviorSeed == 0 {
v.behaviorSeed = dice.RollUint64()
}
return v.behaviorSeed
} }