Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c98f5f4

Browse files
committed
chore: sync vless encryption code
1 parent d094075 commit c98f5f4

File tree

8 files changed

+182
-52
lines changed

8 files changed

+182
-52
lines changed

docs/config.yaml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -641,8 +641,14 @@ proxies: # socks5
641641
# vless encryption客户端配置:
642642
# (native/xorpub 的 XTLS Vision 可以 Splice。只使用 1-RTT 模式 / 若服务端发的 ticket 中秒数不为零则 0-RTT 复用)
643643
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
644+
#
645+
# Padding 是可选的参数,仅作用于 1-RTT 以消除握手的长度特征,双端默认值均为 "111-1111.111--66.3333--1234",它的含义是:
646+
# 在 1-RTT client/server hello 后粘上随机 111 到 1111 字节的 padding
647+
# 等待随机 111 到负 66 毫秒,若随机到了负值则不等待
648+
# 再次发送随机 3333 到负 1234 字节的 padding,若随机到了负值则不发送
649+
# 服务端、客户端可以设置不同的 padding 参数,正数写在左边,按 len、gap 的顺序无限串联,第一个 padding 需大于 16 字节
644650
# -------------------------
645-
encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(X25519 Password).(ML-KEM-768 Client)..."
651+
encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(padding len).(padding gap).(X25519 Password).(ML-KEM-768 Client)..."
646652
tls: false #可以不开启tls
647653
udp: true
648654

@@ -1366,8 +1372,14 @@ listeners:
13661372
# vless encryption服务端配置:
13671373
# (原生外观 / 只 XOR 公钥 / 全随机数。只允许 1-RTT 模式 / 同时允许 1-RTT 模式与 600 秒复用的 0-RTT 模式)
13681374
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
1375+
#
1376+
# Padding 是可选的参数,仅作用于 1-RTT 以消除握手的长度特征,双端默认值均为 "111-1111.111--66.3333--1234",它的含义是:
1377+
# 在 1-RTT client/server hello 后粘上随机 111 到 1111 字节的 padding
1378+
# 等待随机 111 到负 66 毫秒,若随机到了负值则不等待
1379+
# 再次发送随机 3333 到负 1234 字节的 padding,若随机到了负值则不发送
1380+
# 服务端、客户端可以设置不同的 padding 参数,正数写在左边,按 len、gap 的顺序无限串联,第一个 padding 需大于 16 字节
13691381
# -------------------------
1370-
# decryption: "mlkem768x25519plus.native/xorpub/random.1rtt/600s.(X25519 PrivateKey).(ML-KEM-768 Seed)..."
1382+
# decryption: "mlkem768x25519plus.native/xorpub/random.1rtt/600s.(padding len).(padding gap).(X25519 PrivateKey).(ML-KEM-768 Seed)..."
13711383
# 下面两项如果填写则开启 tls(需要同时填写)
13721384
# certificate: ./server.crt
13731385
# private-key: ./server.key

listener/inbound/vless_test.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ func TestInboundVless_Encryption(t *testing.T) {
9999
t.Fatal(err)
100100
return
101101
}
102+
paddings := []struct {
103+
name string
104+
data string
105+
}{
106+
{"unconfigured-padding", ""},
107+
{"default-padding", "111-1111.111--66.3333--1234."},
108+
{"old-padding", "100-1000."}, // Xray-core v25.8.29
109+
{"custom-padding", "7890-1234.1111--999.6666--3333.777--777."},
110+
}
102111
var modes = []string{
103112
"native",
104113
"xorpub",
@@ -107,19 +116,26 @@ func TestInboundVless_Encryption(t *testing.T) {
107116
for i := range modes {
108117
mode := modes[i]
109118
t.Run(mode, func(t *testing.T) {
110-
inboundOptions := inbound.VlessOption{
111-
Decryption: "mlkem768x25519plus." + mode + ".600s." + privateKeyBase64 + "." + seedBase64,
112-
}
113-
outboundOptions := outbound.VlessOption{
114-
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + passwordBase64 + "." + clientBase64,
119+
t.Parallel()
120+
for i := range paddings {
121+
padding := paddings[i].data
122+
t.Run(paddings[i].name, func(t *testing.T) {
123+
inboundOptions := inbound.VlessOption{
124+
Decryption: "mlkem768x25519plus." + mode + ".600s." + padding + privateKeyBase64 + "." + seedBase64,
125+
}
126+
outboundOptions := outbound.VlessOption{
127+
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + padding + passwordBase64 + "." + clientBase64,
128+
}
129+
testInboundVless(t, inboundOptions, outboundOptions)
130+
t.Run("xtls-rprx-vision", func(t *testing.T) {
131+
outboundOptions := outboundOptions
132+
outboundOptions.Flow = "xtls-rprx-vision"
133+
testInboundVless(t, inboundOptions, outboundOptions)
134+
})
135+
})
115136
}
116-
testInboundVless(t, inboundOptions, outboundOptions)
117-
t.Run("xtls-rprx-vision", func(t *testing.T) {
118-
outboundOptions := outboundOptions
119-
outboundOptions.Flow = "xtls-rprx-vision"
120-
testInboundVless(t, inboundOptions, outboundOptions)
121-
})
122137
})
138+
123139
}
124140
}
125141

listener/sing_vless/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
230230
ctx := sing.WithAdditions(context.TODO(), additions...)
231231
if l.decryption != nil {
232232
var err error
233-
conn, err = l.decryption.Handshake(conn)
233+
conn, err = l.decryption.Handshake(conn, nil)
234234
if err != nil {
235235
return
236236
}

transport/vless/encryption/client.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,22 @@ type ClientInstance struct {
3333
RelaysLength int
3434
XorMode uint32
3535
Seconds uint32
36+
PaddingLens [][2]int
37+
PaddingGaps [][2]int
3638

3739
RWLock sync.RWMutex
3840
Expire time.Time
3941
PfsKey []byte
4042
Ticket []byte
4143
}
4244

43-
func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (err error) {
45+
func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) {
4446
if i.NfsPKeys != nil {
45-
err = errors.New("already initialized")
46-
return
47+
return errors.New("already initialized")
4748
}
4849
l := len(nfsPKeysBytes)
4950
if l == 0 {
50-
err = errors.New("empty nfsPKeysBytes")
51-
return
51+
return errors.New("empty nfsPKeysBytes")
5252
}
5353
i.NfsPKeys = make([]any, l)
5454
i.NfsPKeysBytes = nfsPKeysBytes
@@ -70,7 +70,7 @@ func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (
7070
i.RelaysLength -= 32
7171
i.XorMode = xorMode
7272
i.Seconds = seconds
73-
return
73+
return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
7474
}
7575

7676
func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
@@ -81,7 +81,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
8181

8282
ivAndRealysLength := 16 + i.RelaysLength
8383
pfsKeyExchangeLength := 18 + 1184 + 32 + 16
84-
paddingLength := int(randBetween(100, 1000))
84+
paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)
8585
clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength)
8686

8787
iv := clientHello[:16]
@@ -150,10 +150,18 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
150150
nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
151151
nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
152152

153-
if _, err := conn.Write(clientHello); err != nil {
154-
return nil, err
153+
paddingLens[0] = ivAndRealysLength + pfsKeyExchangeLength + paddingLens[0]
154+
for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
155+
if l > 0 {
156+
if _, err := conn.Write(clientHello[:l]); err != nil {
157+
return nil, err
158+
}
159+
clientHello = clientHello[l:]
160+
}
161+
if len(paddingGaps) > i {
162+
time.Sleep(paddingGaps[i])
163+
}
155164
}
156-
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
157165

158166
encryptedPfsPublicKey := make([]byte, 1088+32+16)
159167
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {

transport/vless/encryption/common.go

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"fmt"
99
"io"
1010
"net"
11+
"strconv"
12+
"strings"
1113
"time"
1214

1315
"github.com/metacubex/mihomo/common/pool"
@@ -96,11 +98,11 @@ func (c *CommonConn) Read(b []byte) (int, error) {
9698
if c.input.Len() > 0 {
9799
return c.input.Read(b)
98100
}
99-
peerHeader := make([]byte, 5)
100-
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
101+
peerHeader := [5]byte{}
102+
if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil {
101103
return 0, err
102104
}
103-
l, err := DecodeHeader(peerHeader) // l: 17~17000
105+
l, err := DecodeHeader(peerHeader[:]) // l: 17~17000
104106
if err != nil {
105107
if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT
106108
c.Client.RWLock.Lock()
@@ -113,7 +115,9 @@ func (c *CommonConn) Read(b []byte) (int, error) {
113115
return 0, err
114116
}
115117
c.Client = nil
116-
c.rawInput.Grow(l)
118+
if c.rawInput.Cap() < l {
119+
c.rawInput.Grow(l) // no need to use sync.Pool, because we are always reading
120+
}
117121
peerData := c.rawInput.Bytes()[:l]
118122
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
119123
return 0, err
@@ -124,9 +128,9 @@ func (c *CommonConn) Read(b []byte) (int, error) {
124128
}
125129
var newAEAD *AEAD
126130
if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) {
127-
newAEAD = NewAEAD(append(peerHeader, peerData...), c.UnitedKey, c.UseAES)
131+
newAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES)
128132
}
129-
_, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader)
133+
_, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:])
130134
if newAEAD != nil {
131135
c.PeerAEAD = newAEAD
132136
}
@@ -213,9 +217,69 @@ func DecodeHeader(h []byte) (l int, err error) {
213217
return
214218
}
215219

220+
func ParsePadding(padding string, paddingLens, paddingGaps *[][2]int) (err error) {
221+
if padding == "" {
222+
return
223+
}
224+
maxLen := 0
225+
for i, s := range strings.Split(padding, ".") {
226+
x := strings.SplitN(s, "-", 2)
227+
if len(x) != 2 || x[0] == "" || x[1] == "" {
228+
return errors.New("invalid padding lenth/gap parameter: " + s)
229+
}
230+
y := [2]int{}
231+
if y[0], err = strconv.Atoi(x[0]); err != nil {
232+
return
233+
}
234+
if y[1], err = strconv.Atoi(x[1]); err != nil {
235+
return
236+
}
237+
if i == 0 && (y[0] < 17 || y[1] < 17) {
238+
return errors.New("first padding length must be larger than 16")
239+
}
240+
if i%2 == 0 {
241+
*paddingLens = append(*paddingLens, y)
242+
maxLen += max(y[0], y[1])
243+
} else {
244+
*paddingGaps = append(*paddingGaps, y)
245+
}
246+
}
247+
if maxLen > 65535 {
248+
return errors.New("total padding length must be smaller than 65536")
249+
}
250+
return
251+
}
252+
253+
func CreatPadding(paddingLens, paddingGaps [][2]int) (length int, lens []int, gaps []time.Duration) {
254+
if len(paddingLens) == 0 {
255+
paddingLens = [][2]int{{111, 1111}, {3333, -1234}}
256+
paddingGaps = [][2]int{{111, -66}}
257+
}
258+
for _, l := range paddingLens {
259+
lens = append(lens, int(max(0, randBetween(int64(l[0]), int64(l[1])))))
260+
length += lens[len(lens)-1]
261+
}
262+
for _, g := range paddingGaps {
263+
gaps = append(gaps, time.Duration(max(0, randBetween(int64(g[0]), int64(g[1]))))*time.Millisecond)
264+
}
265+
return
266+
}
267+
268+
func max[T ~int | ~uint | ~int64 | ~uint64 | ~int32 | ~uint32 | ~int16 | ~uint16 | ~int8 | ~uint8](a, b T) T {
269+
if a > b {
270+
return a
271+
}
272+
return b
273+
}
274+
216275
func randBetween(from int64, to int64) int64 {
217276
if from == to {
218277
return from
219278
}
279+
280+
if to < from {
281+
from, to = to, from
282+
}
283+
220284
return from + randv2.Int64N(to-from)
221285
}

transport/vless/encryption/doc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@
2222
// https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e
2323
// https://github.com/XTLS/Xray-core/commit/b0b220985c9c1bc832665458d5fd6e0c287b67ae
2424
// https://github.com/XTLS/Xray-core/commit/82ea7a3cc5ff23280b87e3052f0f83b04f0267fa
25+
// https://github.com/XTLS/Xray-core/commit/e8b02cd6649f14889841e8ab8ee6b2acca71dbe6
2526
package encryption

transport/vless/encryption/factory.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ func NewClient(encryption string) (*ClientInstance, error) {
3434
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
3535
}
3636
var nfsPKeysBytes [][]byte
37+
var paddings []string
3738
for _, r := range s[3:] {
39+
if len(r) < 20 {
40+
paddings = append(paddings, r)
41+
continue
42+
}
3843
b, err := base64.RawURLEncoding.DecodeString(r)
3944
if err != nil {
4045
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
@@ -44,8 +49,9 @@ func NewClient(encryption string) (*ClientInstance, error) {
4449
}
4550
nfsPKeysBytes = append(nfsPKeysBytes, b)
4651
}
52+
padding := strings.Join(paddings, ".")
4753
client := &ClientInstance{}
48-
if err := client.Init(nfsPKeysBytes, xorMode, seconds); err != nil {
54+
if err := client.Init(nfsPKeysBytes, xorMode, seconds, padding); err != nil {
4955
return nil, fmt.Errorf("failed to use encryption: %w", err)
5056
}
5157
return client, nil
@@ -84,7 +90,12 @@ func NewServer(decryption string) (*ServerInstance, error) {
8490
seconds = uint32(i)
8591
}
8692
var nfsSKeysBytes [][]byte
93+
var paddings []string
8794
for _, r := range s[3:] {
95+
if len(r) < 20 {
96+
paddings = append(paddings, r)
97+
continue
98+
}
8899
b, err := base64.RawURLEncoding.DecodeString(r)
89100
if err != nil {
90101
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
@@ -94,8 +105,9 @@ func NewServer(decryption string) (*ServerInstance, error) {
94105
}
95106
nfsSKeysBytes = append(nfsSKeysBytes, b)
96107
}
108+
padding := strings.Join(paddings, ".")
97109
server := &ServerInstance{}
98-
if err := server.Init(nfsSKeysBytes, xorMode, seconds); err != nil {
110+
if err := server.Init(nfsSKeysBytes, xorMode, seconds, padding); err != nil {
99111
return nil, fmt.Errorf("failed to use decryption: %w", err)
100112
}
101113
return server, nil

0 commit comments

Comments
 (0)