Skip to content

Commit cadcb47

Browse files
RPRXFangliding
andcommitted
XTLS Vision: Add testpre (outbound pre-connect) and testseed (outbound & inbound) (#5270)
https://t.me/projectXtls/1034 --------- Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
1 parent c6afcd5 commit cadcb47

File tree

8 files changed

+116
-29
lines changed

8 files changed

+116
-29
lines changed

app/proxyman/outbound/handler.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,12 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
317317
conn, err := internet.Dial(ctx, dest, h.streamSettings)
318318
conn = h.getStatCouterConnection(conn)
319319
outbounds := session.OutboundsFromContext(ctx)
320-
ob := outbounds[len(outbounds)-1]
321-
ob.Conn = conn
320+
if outbounds != nil {
321+
ob := outbounds[len(outbounds)-1]
322+
ob.Conn = conn
323+
} else {
324+
// for Vision's pre-connect
325+
}
322326
return conn, err
323327
}
324328

infra/conf/vless.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type VLessInboundConfig struct {
3434
Decryption string `json:"decryption"`
3535
Fallbacks []*VLessInboundFallback `json:"fallbacks"`
3636
Flow string `json:"flow"`
37+
Testseed []uint32 `json:"testseed"`
3738
}
3839

3940
// Build implements Buildable
@@ -73,6 +74,10 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
7374
return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`)
7475
}
7576

77+
if len(account.Testseed) < 4 {
78+
account.Testseed = c.Testseed
79+
}
80+
7681
if account.Encryption != "" {
7782
return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`)
7883
}
@@ -212,6 +217,8 @@ type VLessOutboundConfig struct {
212217
Seed string `json:"seed"`
213218
Encryption string `json:"encryption"`
214219
Reverse *vless.Reverse `json:"reverse"`
220+
Testpre uint32 `json:"testpre"`
221+
Testseed []uint32 `json:"testseed"`
215222
Vnext []*VLessOutboundVnext `json:"vnext"`
216223
}
217224

@@ -258,6 +265,8 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
258265
//account.Seed = c.Seed
259266
account.Encryption = c.Encryption
260267
account.Reverse = c.Reverse
268+
account.Testpre = c.Testpre
269+
account.Testseed = c.Testseed
261270
} else {
262271
if err := json.Unmarshal(rawUser, account); err != nil {
263272
return nil, errors.New(`VLESS users: invalid user`).Base(err)

proxy/proxy.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -296,11 +296,16 @@ type VisionWriter struct {
296296
// internal
297297
writeOnceUserUUID []byte
298298
directWriteCounter stats.Counter
299+
300+
testseed []uint32
299301
}
300302

301-
func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, ob *session.Outbound) *VisionWriter {
303+
func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, ob *session.Outbound, testseed []uint32) *VisionWriter {
302304
w := make([]byte, len(trafficState.UserUUID))
303305
copy(w, trafficState.UserUUID)
306+
if len(testseed) < 4 {
307+
testseed = []uint32{900, 500, 900, 256}
308+
}
304309
return &VisionWriter{
305310
Writer: writer,
306311
trafficState: trafficState,
@@ -309,6 +314,7 @@ func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink boo
309314
isUplink: isUplink,
310315
conn: conn,
311316
ob: ob,
317+
testseed: testseed,
312318
}
313319
}
314320

@@ -347,7 +353,7 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
347353

348354
if *isPadding {
349355
if len(mb) == 1 && mb[0] == nil {
350-
mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx) // we do a long padding to hide vless header
356+
mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx, w.testseed) // we do a long padding to hide vless header
351357
return w.Writer.WriteMultiBuffer(mb)
352358
}
353359
isComplete := IsCompleteRecord(mb)
@@ -365,13 +371,13 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
365371
command = CommandPaddingDirect
366372
}
367373
}
368-
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx)
374+
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx, w.testseed)
369375
*isPadding = false // padding going to end
370376
longPadding = false
371377
continue
372378
} else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early
373379
*isPadding = false
374-
mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx)
380+
mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)
375381
break
376382
}
377383
var command byte = CommandPaddingContinue
@@ -381,7 +387,7 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
381387
command = CommandPaddingDirect
382388
}
383389
}
384-
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx)
390+
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)
385391
}
386392
}
387393
return w.Writer.WriteMultiBuffer(mb)
@@ -488,20 +494,20 @@ func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBu
488494
}
489495

490496
// XtlsPadding add padding to eliminate length signature during tls handshake
491-
func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context) *buf.Buffer {
497+
func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context, testseed []uint32) *buf.Buffer {
492498
var contentLen int32 = 0
493499
var paddingLen int32 = 0
494500
if b != nil {
495501
contentLen = b.Len()
496502
}
497-
if contentLen < 900 && longPadding {
498-
l, err := rand.Int(rand.Reader, big.NewInt(500))
503+
if contentLen < int32(testseed[0]) && longPadding {
504+
l, err := rand.Int(rand.Reader, big.NewInt(int64(testseed[1])))
499505
if err != nil {
500506
errors.LogDebugInner(ctx, err, "failed to generate padding")
501507
}
502-
paddingLen = int32(l.Int64()) + 900 - contentLen
508+
paddingLen = int32(l.Int64()) + int32(testseed[2]) - contentLen
503509
} else {
504-
l, err := rand.Int(rand.Reader, big.NewInt(256))
510+
l, err := rand.Int(rand.Reader, big.NewInt(int64(testseed[3])))
505511
if err != nil {
506512
errors.LogDebugInner(ctx, err, "failed to generate padding")
507513
}

proxy/vless/account.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ func (a *Account) AsAccount() (protocol.Account, error) {
2222
Seconds: a.Seconds,
2323
Padding: a.Padding,
2424
Reverse: a.Reverse,
25+
Testpre: a.Testpre,
26+
Testseed: a.Testseed,
2527
}, nil
2628
}
2729

@@ -38,6 +40,9 @@ type MemoryAccount struct {
3840
Padding string
3941

4042
Reverse *Reverse
43+
44+
Testpre uint32
45+
Testseed []uint32
4146
}
4247

4348
// Equals implements protocol.Account.Equals().
@@ -58,5 +63,7 @@ func (a *MemoryAccount) ToProto() proto.Message {
5863
Seconds: a.Seconds,
5964
Padding: a.Padding,
6065
Reverse: a.Reverse,
66+
Testpre: a.Testpre,
67+
Testseed: a.Testseed,
6168
}
6269
}

proxy/vless/account.pb.go

Lines changed: 27 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proxy/vless/account.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ message Account {
2222
string padding = 6;
2323

2424
Reverse reverse = 7;
25+
26+
uint32 testpre = 8;
27+
repeated uint32 testseed = 9;
2528
}

proxy/vless/encoding/addons.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func EncodeBodyAddons(writer buf.Writer, request *protocol.RequestHeader, reques
6868
return NewMultiLengthPacketWriter(writer)
6969
}
7070
if requestAddons.Flow == vless.XRV {
71-
return proxy.NewVisionWriter(writer, state, isUplink, context, conn, ob)
71+
return proxy.NewVisionWriter(writer, state, isUplink, context, conn, ob, request.User.Account.(*vless.MemoryAccount).Testseed)
7272
}
7373
return writer
7474
}

proxy/vless/outbound/outbound.go

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/base64"
88
"reflect"
99
"strings"
10+
"sync"
1011
"time"
1112
"unsafe"
1213

@@ -15,6 +16,7 @@ import (
1516
"github.com/xtls/xray-core/app/reverse"
1617
"github.com/xtls/xray-core/common"
1718
"github.com/xtls/xray-core/common/buf"
19+
xctx "github.com/xtls/xray-core/common/ctx"
1820
"github.com/xtls/xray-core/common/errors"
1921
"github.com/xtls/xray-core/common/mux"
2022
"github.com/xtls/xray-core/common/net"
@@ -52,6 +54,10 @@ type Handler struct {
5254
cone bool
5355
encryption *encryption.ClientInstance
5456
reverse *Reverse
57+
58+
testpre uint32
59+
initpre sync.Once
60+
preConns chan stat.Connection
5561
}
5662

5763
// New creates a new VLess outbound handler.
@@ -105,11 +111,16 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
105111
}()
106112
}
107113

114+
handler.testpre = a.Testpre
115+
108116
return handler, nil
109117
}
110118

111119
// Close implements common.Closable.Close().
112120
func (h *Handler) Close() error {
121+
if h.preConns != nil {
122+
close(h.preConns)
123+
}
113124
if h.reverse != nil {
114125
return h.reverse.Close()
115126
}
@@ -128,18 +139,46 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
128139
rec := h.server
129140
var conn stat.Connection
130141

131-
if err := retry.ExponentialBackoff(5, 200).On(func() error {
132-
var err error
133-
conn, err = dialer.Dial(ctx, rec.Destination)
134-
if err != nil {
135-
return err
142+
if h.testpre > 0 && h.reverse == nil {
143+
h.initpre.Do(func() {
144+
h.preConns = make(chan stat.Connection)
145+
for range h.testpre { // TODO: randomize
146+
go func() {
147+
defer func() { recover() }()
148+
ctx := xctx.ContextWithID(context.Background(), session.NewID())
149+
for {
150+
time.Sleep(time.Millisecond * 200) // TODO: randomize
151+
conn, err := dialer.Dial(ctx, rec.Destination)
152+
if err != nil {
153+
errors.LogWarningInner(ctx, err, "pre-connect failed")
154+
continue
155+
}
156+
h.preConns <- conn
157+
}
158+
}()
159+
}
160+
})
161+
if conn = <-h.preConns; conn == nil {
162+
return errors.New("closed handler").AtWarning()
163+
}
164+
}
165+
166+
if conn == nil {
167+
if err := retry.ExponentialBackoff(5, 200).On(func() error {
168+
var err error
169+
conn, err = dialer.Dial(ctx, rec.Destination)
170+
if err != nil {
171+
return err
172+
}
173+
return nil
174+
}); err != nil {
175+
return errors.New("failed to find an available destination").Base(err).AtWarning()
136176
}
137-
return nil
138-
}); err != nil {
139-
return errors.New("failed to find an available destination").Base(err).AtWarning()
140177
}
141178
defer conn.Close()
142179

180+
ob.Conn = conn // for Vision's pre-connect
181+
143182
iConn := conn
144183
if statConn, ok := iConn.(*stat.CounterConnection); ok {
145184
iConn = statConn.Connection

0 commit comments

Comments
 (0)