Răsfoiți Sursa

VMess inbound: Optimize replay filter (#5562)

And https://github.com/XTLS/Xray-core/pull/5562#issuecomment-3765387903
风扇滑翔翼 3 săptămâni în urmă
părinte
comite
f6a7e93923

+ 0 - 6
common/antireplay/antireplay.go

@@ -1,6 +0,0 @@
-package antireplay
-
-type GeneralizedReplayFilter interface {
-	Interval() int64
-	Check(sum []byte) bool
-}

+ 33 - 0
common/antireplay/antireplay_test.go

@@ -0,0 +1,33 @@
+package antireplay
+
+import (
+	"bufio"
+	"crypto/rand"
+	"testing"
+)
+
+func BenchmarkMapFilter(b *testing.B) {
+	filter := NewMapFilter[[16]byte](120)
+	var sample [16]byte
+	reader := bufio.NewReader(rand.Reader)
+	reader.Read(sample[:])
+	b.ResetTimer()
+	for range b.N {
+		reader.Read(sample[:])
+		filter.Check(sample)
+	}
+}
+
+func TestMapFilter(t *testing.T) {
+	filter := NewMapFilter[[16]byte](120)
+	var sample [16]byte
+	rand.Read(sample[:])
+	filter.Check(sample)
+	if filter.Check(sample) {
+		t.Error("Unexpected true negative")
+	}
+	sample[0]++
+	if !filter.Check(sample) {
+		t.Error("Unexpected false positive")
+	}
+}

+ 0 - 36
common/antireplay/bloomring.go

@@ -1,36 +0,0 @@
-package antireplay
-
-import (
-	"sync"
-
-	ss_bloomring "github.com/v2fly/ss-bloomring"
-)
-
-type BloomRing struct {
-	*ss_bloomring.BloomRing
-	lock *sync.Mutex
-}
-
-func (b BloomRing) Interval() int64 {
-	return 9999999
-}
-
-func (b BloomRing) Check(sum []byte) bool {
-	b.lock.Lock()
-	defer b.lock.Unlock()
-	if b.Test(sum) {
-		return false
-	}
-	b.Add(sum)
-	return true
-}
-
-func NewBloomRing() BloomRing {
-	const (
-		DefaultSFCapacity = 1e6
-		// FalsePositiveRate
-		DefaultSFFPR  = 1e-6
-		DefaultSFSlot = 10
-	)
-	return BloomRing{ss_bloomring.NewBloomRing(DefaultSFSlot, DefaultSFCapacity, DefaultSFFPR), &sync.Mutex{}}
-}

+ 46 - 0
common/antireplay/mapfilter.go

@@ -0,0 +1,46 @@
+package antireplay
+
+import (
+	"sync"
+	"time"
+)
+
+// ReplayFilter checks for replay attacks.
+type ReplayFilter[T comparable] struct {
+	lock      sync.Mutex
+	poolA     map[T]struct{}
+	poolB     map[T]struct{}
+	interval  time.Duration
+	lastClean time.Time
+}
+
+// NewMapFilter create a new filter with specifying the expiration time interval in seconds.
+func NewMapFilter[T comparable](interval int64) *ReplayFilter[T] {
+	filter := &ReplayFilter[T]{
+		poolA:     make(map[T]struct{}),
+		poolB:     make(map[T]struct{}),
+		interval:  time.Duration(interval) * time.Second,
+		lastClean: time.Now(),
+	}
+	return filter
+}
+
+// Check determines if there are duplicate records.
+func (filter *ReplayFilter[T]) Check(sum T) bool {
+	filter.lock.Lock()
+	defer filter.lock.Unlock()
+
+	now := time.Now()
+	if now.Sub(filter.lastClean) >= filter.interval {
+		filter.poolB = filter.poolA
+		filter.poolA = make(map[T]struct{})
+		filter.lastClean = now
+	}
+
+	_, existsA := filter.poolA[sum]
+	_, existsB := filter.poolB[sum]
+	if !existsA && !existsB {
+		filter.poolA[sum] = struct{}{}
+	}
+	return !(existsA || existsB)
+}

+ 0 - 58
common/antireplay/replayfilter.go

@@ -1,58 +0,0 @@
-package antireplay
-
-import (
-	"sync"
-	"time"
-
-	cuckoo "github.com/seiflotfy/cuckoofilter"
-)
-
-const replayFilterCapacity = 100000
-
-// ReplayFilter checks for replay attacks.
-type ReplayFilter struct {
-	lock     sync.Mutex
-	poolA    *cuckoo.Filter
-	poolB    *cuckoo.Filter
-	poolSwap bool
-	lastSwap int64
-	interval int64
-}
-
-// NewReplayFilter create a new filter with specifying the expiration time interval in seconds.
-func NewReplayFilter(interval int64) *ReplayFilter {
-	filter := &ReplayFilter{}
-	filter.interval = interval
-	return filter
-}
-
-// Interval in second for expiration time for duplicate records.
-func (filter *ReplayFilter) Interval() int64 {
-	return filter.interval
-}
-
-// Check determines if there are duplicate records.
-func (filter *ReplayFilter) Check(sum []byte) bool {
-	filter.lock.Lock()
-	defer filter.lock.Unlock()
-
-	now := time.Now().Unix()
-	if filter.lastSwap == 0 {
-		filter.lastSwap = now
-		filter.poolA = cuckoo.NewFilter(replayFilterCapacity)
-		filter.poolB = cuckoo.NewFilter(replayFilterCapacity)
-	}
-
-	elapsed := now - filter.lastSwap
-	if elapsed >= filter.Interval() {
-		if filter.poolSwap {
-			filter.poolA.Reset()
-		} else {
-			filter.poolB.Reset()
-		}
-		filter.poolSwap = !filter.poolSwap
-		filter.lastSwap = now
-	}
-
-	return filter.poolA.InsertUnique(sum) && filter.poolB.InsertUnique(sum)
-}

+ 0 - 4
go.mod

@@ -15,9 +15,7 @@ require (
 	github.com/refraction-networking/utls v1.8.2
 	github.com/sagernet/sing v0.5.1
 	github.com/sagernet/sing-shadowsocks v0.2.7
-	github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
 	github.com/stretchr/testify v1.11.1
-	github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
 	github.com/vishvananda/netlink v1.3.1
 	github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535
 	go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
@@ -38,7 +36,6 @@ require (
 require (
 	github.com/andybalholm/brotli v1.0.6 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 // indirect
 	github.com/google/btree v1.1.2 // indirect
 	github.com/juju/ratelimit v1.0.2 // indirect
 	github.com/klauspost/compress v1.17.4 // indirect
@@ -46,7 +43,6 @@ require (
 	github.com/kr/text v0.2.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/quic-go/qpack v0.6.0 // indirect
-	github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
 	github.com/vishvananda/netns v0.0.5 // indirect
 	golang.org/x/mod v0.31.0 // indirect
 	golang.org/x/text v0.33.0 // indirect

+ 0 - 13
go.sum

@@ -5,11 +5,8 @@ github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIj
 github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
 github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E=
-github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
 github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
@@ -54,22 +51,14 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
 github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
 github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
 github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
-github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
-github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
 github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
 github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
 github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
-github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
-github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
-github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
 github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
 github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
 github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
@@ -154,8 +143,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2 h1:fr6L00yGG2RP5NMea6njWpdC+bm+cMdFClrSpaicp1c=

+ 0 - 8
infra/conf/shadowsocks.go

@@ -46,7 +46,6 @@ type ShadowsocksServerConfig struct {
 	Email       string                   `json:"email"`
 	Users       []*ShadowsocksUserConfig `json:"clients"`
 	NetworkList *NetworkList             `json:"network"`
-	IVCheck     bool                     `json:"ivCheck"`
 }
 
 func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
@@ -64,7 +63,6 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
 			account := &shadowsocks.Account{
 				Password:   user.Password,
 				CipherType: cipherFromString(user.Cipher),
-				IvCheck:    v.IVCheck,
 			}
 			if account.Password == "" {
 				return nil, errors.New("Shadowsocks password is not specified.")
@@ -83,7 +81,6 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
 		account := &shadowsocks.Account{
 			Password:   v.Password,
 			CipherType: cipherFromString(v.Cipher),
-			IvCheck:    v.IVCheck,
 		}
 		if account.Password == "" {
 			return nil, errors.New("Shadowsocks password is not specified.")
@@ -168,7 +165,6 @@ type ShadowsocksServerTarget struct {
 	Email      string   `json:"email"`
 	Cipher     string   `json:"method"`
 	Password   string   `json:"password"`
-	IVCheck    bool     `json:"ivCheck"`
 	UoT        bool     `json:"uot"`
 	UoTVersion int      `json:"uotVersion"`
 }
@@ -180,7 +176,6 @@ type ShadowsocksClientConfig struct {
 	Email      string                     `json:"email"`
 	Cipher     string                     `json:"method"`
 	Password   string                     `json:"password"`
-	IVCheck    bool                       `json:"ivCheck"`
 	UoT        bool                       `json:"uot"`
 	UoTVersion int                        `json:"uotVersion"`
 	Servers    []*ShadowsocksServerTarget `json:"servers"`
@@ -198,7 +193,6 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
 				Email:      v.Email,
 				Cipher:     v.Cipher,
 				Password:   v.Password,
-				IVCheck:    v.IVCheck,
 				UoT:        v.UoT,
 				UoTVersion: v.UoTVersion,
 			},
@@ -254,8 +248,6 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
 			return nil, errors.New("unknown cipher method: ", server.Cipher)
 		}
 
-		account.IvCheck = server.IVCheck
-
 		ss := &protocol.ServerEndpoint{
 			Address: server.Address.Build(),
 			Port:    uint32(server.Port),

+ 2 - 21
proxy/shadowsocks/config.go

@@ -5,11 +5,11 @@ import (
 	"crypto/cipher"
 	"crypto/md5"
 	"crypto/sha1"
-	"google.golang.org/protobuf/proto"
 	"io"
 
+	"google.golang.org/protobuf/proto"
+
 	"github.com/xtls/xray-core/common"
-	"github.com/xtls/xray-core/common/antireplay"
 	"github.com/xtls/xray-core/common/buf"
 	"github.com/xtls/xray-core/common/crypto"
 	"github.com/xtls/xray-core/common/errors"
@@ -24,8 +24,6 @@ type MemoryAccount struct {
 	CipherType CipherType
 	Key        []byte
 	Password   string
-
-	replayFilter antireplay.GeneralizedReplayFilter
 }
 
 var ErrIVNotUnique = errors.New("IV is not unique")
@@ -42,18 +40,7 @@ func (a *MemoryAccount) ToProto() proto.Message {
 	return &Account{
 		CipherType: a.CipherType,
 		Password:   a.Password,
-		IvCheck:    a.replayFilter != nil,
-	}
-}
-
-func (a *MemoryAccount) CheckIV(iv []byte) error {
-	if a.replayFilter == nil {
-		return nil
-	}
-	if a.replayFilter.Check(iv) {
-		return nil
 	}
-	return ErrIVNotUnique
 }
 
 func createAesGcm(key []byte) cipher.AEAD {
@@ -116,12 +103,6 @@ func (a *Account) AsAccount() (protocol.Account, error) {
 		CipherType: a.CipherType,
 		Key:        passwordToCipherKey([]byte(a.Password), Cipher.KeySize()),
 		Password:   a.Password,
-		replayFilter: func() antireplay.GeneralizedReplayFilter {
-			if a.IvCheck {
-				return antireplay.NewBloomRing()
-			}
-			return nil
-		}(),
 	}, nil
 }
 

+ 0 - 10
proxy/shadowsocks/protocol.go

@@ -139,9 +139,6 @@ func WriteTCPRequest(request *protocol.RequestHeader, writer io.Writer) (buf.Wri
 	if account.Cipher.IVSize() > 0 {
 		iv = make([]byte, account.Cipher.IVSize())
 		common.Must2(rand.Read(iv))
-		if ivError := account.CheckIV(iv); ivError != nil {
-			return nil, errors.New("failed to mark outgoing iv").Base(ivError)
-		}
 		if err := buf.WriteAllBytes(writer, iv, nil); err != nil {
 			return nil, errors.New("failed to write IV")
 		}
@@ -188,10 +185,6 @@ func ReadTCPResponse(user *protocol.MemoryUser, reader io.Reader) (buf.Reader, e
 		}
 	}
 
-	if ivError := account.CheckIV(iv); ivError != nil {
-		return nil, drain.WithError(drainer, reader, errors.New("failed iv check").Base(ivError))
-	}
-
 	return account.Cipher.NewDecryptionReader(account.Key, iv, reader)
 }
 
@@ -203,9 +196,6 @@ func WriteTCPResponse(request *protocol.RequestHeader, writer io.Writer) (buf.Wr
 	if account.Cipher.IVSize() > 0 {
 		iv = make([]byte, account.Cipher.IVSize())
 		common.Must2(rand.Read(iv))
-		if ivError := account.CheckIV(iv); ivError != nil {
-			return nil, errors.New("failed to mark outgoing iv").Base(ivError)
-		}
 		if err := buf.WriteAllBytes(writer, iv, nil); err != nil {
 			return nil, errors.New("failed to write IV.").Base(err)
 		}

+ 0 - 1
proxy/shadowsocks/validator.go

@@ -140,7 +140,6 @@ func (v *Validator) Get(bs []byte, command protocol.RequestCommand) (u *protocol
 
 			if matchErr == nil {
 				u = user
-				err = account.CheckIV(iv)
 				return
 			}
 		} else {

+ 3 - 3
proxy/vmess/aead/authid.go

@@ -68,12 +68,12 @@ func (aidd *AuthIDDecoder) Decode(data [16]byte) (int64, uint32, int32, []byte)
 }
 
 func NewAuthIDDecoderHolder() *AuthIDDecoderHolder {
-	return &AuthIDDecoderHolder{make(map[string]*AuthIDDecoderItem), antireplay.NewReplayFilter(120)}
+	return &AuthIDDecoderHolder{make(map[string]*AuthIDDecoderItem), antireplay.NewMapFilter[[16]byte](120)}
 }
 
 type AuthIDDecoderHolder struct {
 	decoders map[string]*AuthIDDecoderItem
-	filter   *antireplay.ReplayFilter
+	filter   *antireplay.ReplayFilter[[16]byte]
 }
 
 type AuthIDDecoderItem struct {
@@ -111,7 +111,7 @@ func (a *AuthIDDecoderHolder) Match(authID [16]byte) (interface{}, error) {
 			return nil, ErrInvalidTime
 		}
 
-		if !a.filter.Check(authID[:]) {
+		if !a.filter.Check(authID) {
 			return nil, ErrReplay
 		}