Skip to content

Commit be8bb8d

Browse files
authored
VMess inbound: Optimize replay filter (XTLS#5562)
And XTLS#5562 (comment)
1 parent 03c0b19 commit be8bb8d

12 files changed

Lines changed: 84 additions & 160 deletions

File tree

common/antireplay/antireplay.go

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package antireplay
2+
3+
import (
4+
"bufio"
5+
"crypto/rand"
6+
"testing"
7+
)
8+
9+
func BenchmarkMapFilter(b *testing.B) {
10+
filter := NewMapFilter[[16]byte](120)
11+
var sample [16]byte
12+
reader := bufio.NewReader(rand.Reader)
13+
reader.Read(sample[:])
14+
b.ResetTimer()
15+
for range b.N {
16+
reader.Read(sample[:])
17+
filter.Check(sample)
18+
}
19+
}
20+
21+
func TestMapFilter(t *testing.T) {
22+
filter := NewMapFilter[[16]byte](120)
23+
var sample [16]byte
24+
rand.Read(sample[:])
25+
filter.Check(sample)
26+
if filter.Check(sample) {
27+
t.Error("Unexpected true negative")
28+
}
29+
sample[0]++
30+
if !filter.Check(sample) {
31+
t.Error("Unexpected false positive")
32+
}
33+
}

common/antireplay/bloomring.go

Lines changed: 0 additions & 36 deletions
This file was deleted.

common/antireplay/mapfilter.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package antireplay
2+
3+
import (
4+
"sync"
5+
"time"
6+
)
7+
8+
// ReplayFilter checks for replay attacks.
9+
type ReplayFilter[T comparable] struct {
10+
lock sync.Mutex
11+
poolA map[T]struct{}
12+
poolB map[T]struct{}
13+
interval time.Duration
14+
lastClean time.Time
15+
}
16+
17+
// NewMapFilter create a new filter with specifying the expiration time interval in seconds.
18+
func NewMapFilter[T comparable](interval int64) *ReplayFilter[T] {
19+
filter := &ReplayFilter[T]{
20+
poolA: make(map[T]struct{}),
21+
poolB: make(map[T]struct{}),
22+
interval: time.Duration(interval) * time.Second,
23+
lastClean: time.Now(),
24+
}
25+
return filter
26+
}
27+
28+
// Check determines if there are duplicate records.
29+
func (filter *ReplayFilter[T]) Check(sum T) bool {
30+
filter.lock.Lock()
31+
defer filter.lock.Unlock()
32+
33+
now := time.Now()
34+
if now.Sub(filter.lastClean) >= filter.interval {
35+
filter.poolB = filter.poolA
36+
filter.poolA = make(map[T]struct{})
37+
filter.lastClean = now
38+
}
39+
40+
_, existsA := filter.poolA[sum]
41+
_, existsB := filter.poolB[sum]
42+
if !existsA && !existsB {
43+
filter.poolA[sum] = struct{}{}
44+
}
45+
return !(existsA || existsB)
46+
}

common/antireplay/replayfilter.go

Lines changed: 0 additions & 58 deletions
This file was deleted.

go.mod

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ require (
1515
github.com/refraction-networking/utls v1.8.2
1616
github.com/sagernet/sing v0.5.1
1717
github.com/sagernet/sing-shadowsocks v0.2.7
18-
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
1918
github.com/stretchr/testify v1.11.1
20-
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
2119
github.com/vishvananda/netlink v1.3.1
2220
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535
2321
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
@@ -38,15 +36,13 @@ require (
3836
require (
3937
github.com/andybalholm/brotli v1.0.6 // indirect
4038
github.com/davecgh/go-spew v1.1.1 // indirect
41-
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 // indirect
4239
github.com/google/btree v1.1.2 // indirect
4340
github.com/juju/ratelimit v1.0.2 // indirect
4441
github.com/klauspost/compress v1.17.4 // indirect
4542
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
4643
github.com/kr/text v0.2.0 // indirect
4744
github.com/pmezard/go-difflib v1.0.0 // indirect
4845
github.com/quic-go/qpack v0.6.0 // indirect
49-
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
5046
github.com/vishvananda/netns v0.0.5 // indirect
5147
golang.org/x/mod v0.31.0 // indirect
5248
golang.org/x/text v0.33.0 // indirect

go.sum

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@ github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIj
55
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
66
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
77
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
8-
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
98
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
109
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11-
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E=
12-
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
1310
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
1411
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
1512
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=
5451
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
5552
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
5653
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
57-
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
58-
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
5954
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
6055
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
6156
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
6257
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
6358
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
6459
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
65-
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
66-
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
67-
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
68-
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
6960
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
7061
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
71-
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
72-
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
7362
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
7463
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
7564
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
154143
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
155144
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
156145
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
157-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
158-
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
159146
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
160147
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
161148
gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2 h1:fr6L00yGG2RP5NMea6njWpdC+bm+cMdFClrSpaicp1c=

infra/conf/shadowsocks.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ type ShadowsocksServerConfig struct {
4646
Email string `json:"email"`
4747
Users []*ShadowsocksUserConfig `json:"clients"`
4848
NetworkList *NetworkList `json:"network"`
49-
IVCheck bool `json:"ivCheck"`
5049
}
5150

5251
func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
@@ -64,7 +63,6 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
6463
account := &shadowsocks.Account{
6564
Password: user.Password,
6665
CipherType: cipherFromString(user.Cipher),
67-
IvCheck: v.IVCheck,
6866
}
6967
if account.Password == "" {
7068
return nil, errors.New("Shadowsocks password is not specified.")
@@ -83,7 +81,6 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
8381
account := &shadowsocks.Account{
8482
Password: v.Password,
8583
CipherType: cipherFromString(v.Cipher),
86-
IvCheck: v.IVCheck,
8784
}
8885
if account.Password == "" {
8986
return nil, errors.New("Shadowsocks password is not specified.")
@@ -168,7 +165,6 @@ type ShadowsocksServerTarget struct {
168165
Email string `json:"email"`
169166
Cipher string `json:"method"`
170167
Password string `json:"password"`
171-
IVCheck bool `json:"ivCheck"`
172168
UoT bool `json:"uot"`
173169
UoTVersion int `json:"uotVersion"`
174170
}
@@ -180,7 +176,6 @@ type ShadowsocksClientConfig struct {
180176
Email string `json:"email"`
181177
Cipher string `json:"method"`
182178
Password string `json:"password"`
183-
IVCheck bool `json:"ivCheck"`
184179
UoT bool `json:"uot"`
185180
UoTVersion int `json:"uotVersion"`
186181
Servers []*ShadowsocksServerTarget `json:"servers"`
@@ -198,7 +193,6 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
198193
Email: v.Email,
199194
Cipher: v.Cipher,
200195
Password: v.Password,
201-
IVCheck: v.IVCheck,
202196
UoT: v.UoT,
203197
UoTVersion: v.UoTVersion,
204198
},
@@ -254,8 +248,6 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
254248
return nil, errors.New("unknown cipher method: ", server.Cipher)
255249
}
256250

257-
account.IvCheck = server.IVCheck
258-
259251
ss := &protocol.ServerEndpoint{
260252
Address: server.Address.Build(),
261253
Port: uint32(server.Port),

proxy/shadowsocks/config.go

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import (
55
"crypto/cipher"
66
"crypto/md5"
77
"crypto/sha1"
8-
"google.golang.org/protobuf/proto"
98
"io"
109

10+
"google.golang.org/protobuf/proto"
11+
1112
"github.com/xtls/xray-core/common"
12-
"github.com/xtls/xray-core/common/antireplay"
1313
"github.com/xtls/xray-core/common/buf"
1414
"github.com/xtls/xray-core/common/crypto"
1515
"github.com/xtls/xray-core/common/errors"
@@ -24,8 +24,6 @@ type MemoryAccount struct {
2424
CipherType CipherType
2525
Key []byte
2626
Password string
27-
28-
replayFilter antireplay.GeneralizedReplayFilter
2927
}
3028

3129
var ErrIVNotUnique = errors.New("IV is not unique")
@@ -42,18 +40,7 @@ func (a *MemoryAccount) ToProto() proto.Message {
4240
return &Account{
4341
CipherType: a.CipherType,
4442
Password: a.Password,
45-
IvCheck: a.replayFilter != nil,
46-
}
47-
}
48-
49-
func (a *MemoryAccount) CheckIV(iv []byte) error {
50-
if a.replayFilter == nil {
51-
return nil
52-
}
53-
if a.replayFilter.Check(iv) {
54-
return nil
5543
}
56-
return ErrIVNotUnique
5744
}
5845

5946
func createAesGcm(key []byte) cipher.AEAD {
@@ -116,12 +103,6 @@ func (a *Account) AsAccount() (protocol.Account, error) {
116103
CipherType: a.CipherType,
117104
Key: passwordToCipherKey([]byte(a.Password), Cipher.KeySize()),
118105
Password: a.Password,
119-
replayFilter: func() antireplay.GeneralizedReplayFilter {
120-
if a.IvCheck {
121-
return antireplay.NewBloomRing()
122-
}
123-
return nil
124-
}(),
125106
}, nil
126107
}
127108

proxy/shadowsocks/protocol.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,6 @@ func WriteTCPRequest(request *protocol.RequestHeader, writer io.Writer) (buf.Wri
139139
if account.Cipher.IVSize() > 0 {
140140
iv = make([]byte, account.Cipher.IVSize())
141141
common.Must2(rand.Read(iv))
142-
if ivError := account.CheckIV(iv); ivError != nil {
143-
return nil, errors.New("failed to mark outgoing iv").Base(ivError)
144-
}
145142
if err := buf.WriteAllBytes(writer, iv, nil); err != nil {
146143
return nil, errors.New("failed to write IV")
147144
}
@@ -188,10 +185,6 @@ func ReadTCPResponse(user *protocol.MemoryUser, reader io.Reader) (buf.Reader, e
188185
}
189186
}
190187

191-
if ivError := account.CheckIV(iv); ivError != nil {
192-
return nil, drain.WithError(drainer, reader, errors.New("failed iv check").Base(ivError))
193-
}
194-
195188
return account.Cipher.NewDecryptionReader(account.Key, iv, reader)
196189
}
197190

@@ -203,9 +196,6 @@ func WriteTCPResponse(request *protocol.RequestHeader, writer io.Writer) (buf.Wr
203196
if account.Cipher.IVSize() > 0 {
204197
iv = make([]byte, account.Cipher.IVSize())
205198
common.Must2(rand.Read(iv))
206-
if ivError := account.CheckIV(iv); ivError != nil {
207-
return nil, errors.New("failed to mark outgoing iv").Base(ivError)
208-
}
209199
if err := buf.WriteAllBytes(writer, iv, nil); err != nil {
210200
return nil, errors.New("failed to write IV.").Base(err)
211201
}

0 commit comments

Comments
 (0)