diff --git a/app/proxyman/outbound/handler.go b/app/proxyman/outbound/handler.go index 3c3c6918..eaa1b0b2 100644 --- a/app/proxyman/outbound/handler.go +++ b/app/proxyman/outbound/handler.go @@ -317,8 +317,12 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti conn, err := internet.Dial(ctx, dest, h.streamSettings) conn = h.getStatCouterConnection(conn) outbounds := session.OutboundsFromContext(ctx) - ob := outbounds[len(outbounds)-1] - ob.Conn = conn + if outbounds != nil { + ob := outbounds[len(outbounds)-1] + ob.Conn = conn + } else { + // for Vision's pre-connect + } return conn, err } diff --git a/infra/conf/vless.go b/infra/conf/vless.go index efb480a0..f482c4d8 100644 --- a/infra/conf/vless.go +++ b/infra/conf/vless.go @@ -34,6 +34,7 @@ type VLessInboundConfig struct { Decryption string `json:"decryption"` Fallbacks []*VLessInboundFallback `json:"fallbacks"` Flow string `json:"flow"` + Testseed []uint32 `json:"testseed"` } // Build implements Buildable @@ -73,6 +74,10 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`) } + if len(account.Testseed) < 4 { + account.Testseed = c.Testseed + } + if account.Encryption != "" { return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`) } @@ -212,6 +217,8 @@ type VLessOutboundConfig struct { Seed string `json:"seed"` Encryption string `json:"encryption"` Reverse *vless.Reverse `json:"reverse"` + Testpre uint32 `json:"testpre"` + Testseed []uint32 `json:"testseed"` Vnext []*VLessOutboundVnext `json:"vnext"` } @@ -258,6 +265,8 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) { //account.Seed = c.Seed account.Encryption = c.Encryption account.Reverse = c.Reverse + account.Testpre = c.Testpre + account.Testseed = c.Testseed } else { if err := json.Unmarshal(rawUser, account); err != nil { return nil, errors.New(`VLESS users: invalid user`).Base(err) diff --git a/proxy/proxy.go b/proxy/proxy.go index 9f965e3e..aa57adae 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -296,11 +296,16 @@ type VisionWriter struct { // internal writeOnceUserUUID []byte directWriteCounter stats.Counter + + testseed []uint32 } -func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, ob *session.Outbound) *VisionWriter { +func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, ob *session.Outbound, testseed []uint32) *VisionWriter { w := make([]byte, len(trafficState.UserUUID)) copy(w, trafficState.UserUUID) + if len(testseed) < 4 { + testseed = []uint32{900, 500, 900, 256} + } return &VisionWriter{ Writer: writer, trafficState: trafficState, @@ -309,6 +314,7 @@ func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink boo isUplink: isUplink, conn: conn, ob: ob, + testseed: testseed, } } @@ -347,7 +353,7 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { if *isPadding { if len(mb) == 1 && mb[0] == nil { - mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx) // we do a long padding to hide vless header + mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx, w.testseed) // we do a long padding to hide vless header return w.Writer.WriteMultiBuffer(mb) } isComplete := IsCompleteRecord(mb) @@ -365,13 +371,13 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { command = CommandPaddingDirect } } - mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx) + mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx, w.testseed) *isPadding = false // padding going to end longPadding = false continue } else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early *isPadding = false - mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx) + mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed) break } var command byte = CommandPaddingContinue @@ -381,7 +387,7 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { command = CommandPaddingDirect } } - mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx) + mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed) } } return w.Writer.WriteMultiBuffer(mb) @@ -488,20 +494,20 @@ func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBu } // XtlsPadding add padding to eliminate length signature during tls handshake -func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context) *buf.Buffer { +func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context, testseed []uint32) *buf.Buffer { var contentLen int32 = 0 var paddingLen int32 = 0 if b != nil { contentLen = b.Len() } - if contentLen < 900 && longPadding { - l, err := rand.Int(rand.Reader, big.NewInt(500)) + if contentLen < int32(testseed[0]) && longPadding { + l, err := rand.Int(rand.Reader, big.NewInt(int64(testseed[1]))) if err != nil { errors.LogDebugInner(ctx, err, "failed to generate padding") } - paddingLen = int32(l.Int64()) + 900 - contentLen + paddingLen = int32(l.Int64()) + int32(testseed[2]) - contentLen } else { - l, err := rand.Int(rand.Reader, big.NewInt(256)) + l, err := rand.Int(rand.Reader, big.NewInt(int64(testseed[3]))) if err != nil { errors.LogDebugInner(ctx, err, "failed to generate padding") } diff --git a/proxy/vless/account.go b/proxy/vless/account.go index ac00ea53..2f617149 100644 --- a/proxy/vless/account.go +++ b/proxy/vless/account.go @@ -22,6 +22,8 @@ func (a *Account) AsAccount() (protocol.Account, error) { Seconds: a.Seconds, Padding: a.Padding, Reverse: a.Reverse, + Testpre: a.Testpre, + Testseed: a.Testseed, }, nil } @@ -38,6 +40,9 @@ type MemoryAccount struct { Padding string Reverse *Reverse + + Testpre uint32 + Testseed []uint32 } // Equals implements protocol.Account.Equals(). @@ -58,5 +63,7 @@ func (a *MemoryAccount) ToProto() proto.Message { Seconds: a.Seconds, Padding: a.Padding, Reverse: a.Reverse, + Testpre: a.Testpre, + Testseed: a.Testseed, } } diff --git a/proxy/vless/account.pb.go b/proxy/vless/account.pb.go index 5822f512..ce01ecae 100644 --- a/proxy/vless/account.pb.go +++ b/proxy/vless/account.pb.go @@ -79,6 +79,8 @@ type Account struct { Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"` Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"` Reverse *Reverse `protobuf:"bytes,7,opt,name=reverse,proto3" json:"reverse,omitempty"` + Testpre uint32 `protobuf:"varint,8,opt,name=testpre,proto3" json:"testpre,omitempty"` + Testseed []uint32 `protobuf:"varint,9,rep,packed,name=testseed,proto3" json:"testseed,omitempty"` } func (x *Account) Reset() { @@ -160,6 +162,20 @@ func (x *Account) GetReverse() *Reverse { return nil } +func (x *Account) GetTestpre() uint32 { + if x != nil { + return x.Testpre + } + return 0 +} + +func (x *Account) GetTestseed() []uint32 { + if x != nil { + return x.Testseed + } + return nil +} + var File_proxy_vless_account_proto protoreflect.FileDescriptor var file_proxy_vless_account_proto_rawDesc = []byte{ @@ -167,7 +183,7 @@ var file_proxy_vless_account_proto_rawDesc = []byte{ 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x1b, 0x0a, 0x07, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0xd0, 0x01, 0x0a, 0x07, 0x41, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x86, 0x02, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x6e, @@ -180,13 +196,16 @@ var file_proxy_vless_account_proto_rawDesc = []byte{ 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x33, 0x0a, 0x07, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x52, 0x65, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x52, 0x07, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x42, 0x52, 0x0a, - 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, - 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, - 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, - 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, - 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x72, 0x73, 0x65, 0x52, 0x07, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x73, 0x74, 0x73, + 0x65, 0x65, 0x64, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x73, + 0x65, 0x65, 0x64, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, + 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, + 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, + 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proxy/vless/account.proto b/proxy/vless/account.proto index 047311dd..d0f1ee22 100644 --- a/proxy/vless/account.proto +++ b/proxy/vless/account.proto @@ -22,4 +22,7 @@ message Account { string padding = 6; Reverse reverse = 7; + + uint32 testpre = 8; + repeated uint32 testseed = 9; } diff --git a/proxy/vless/encoding/addons.go b/proxy/vless/encoding/addons.go index 77b09861..724de777 100644 --- a/proxy/vless/encoding/addons.go +++ b/proxy/vless/encoding/addons.go @@ -68,7 +68,7 @@ func EncodeBodyAddons(writer buf.Writer, request *protocol.RequestHeader, reques return NewMultiLengthPacketWriter(writer) } if requestAddons.Flow == vless.XRV { - return proxy.NewVisionWriter(writer, state, isUplink, context, conn, ob) + return proxy.NewVisionWriter(writer, state, isUplink, context, conn, ob, request.User.Account.(*vless.MemoryAccount).Testseed) } return writer } diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go index c425151f..504b2126 100644 --- a/proxy/vless/outbound/outbound.go +++ b/proxy/vless/outbound/outbound.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "reflect" "strings" + "sync" "time" "unsafe" @@ -15,6 +16,7 @@ import ( "github.com/xtls/xray-core/app/reverse" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" + xctx "github.com/xtls/xray-core/common/ctx" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/mux" "github.com/xtls/xray-core/common/net" @@ -52,6 +54,10 @@ type Handler struct { cone bool encryption *encryption.ClientInstance reverse *Reverse + + testpre uint32 + initpre sync.Once + preConns chan stat.Connection } // New creates a new VLess outbound handler. @@ -105,11 +111,16 @@ func New(ctx context.Context, config *Config) (*Handler, error) { }() } + handler.testpre = a.Testpre + return handler, nil } // Close implements common.Closable.Close(). func (h *Handler) Close() error { + if h.preConns != nil { + close(h.preConns) + } if h.reverse != nil { return h.reverse.Close() } @@ -128,18 +139,46 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte rec := h.server var conn stat.Connection - if err := retry.ExponentialBackoff(5, 200).On(func() error { - var err error - conn, err = dialer.Dial(ctx, rec.Destination) - if err != nil { - return err + if h.testpre > 0 && h.reverse == nil { + h.initpre.Do(func() { + h.preConns = make(chan stat.Connection) + for range h.testpre { // TODO: randomize + go func() { + defer func() { recover() }() + ctx := xctx.ContextWithID(context.Background(), session.NewID()) + for { + time.Sleep(time.Millisecond * 200) // TODO: randomize + conn, err := dialer.Dial(ctx, rec.Destination) + if err != nil { + errors.LogWarningInner(ctx, err, "pre-connect failed") + continue + } + h.preConns <- conn + } + }() + } + }) + if conn = <-h.preConns; conn == nil { + return errors.New("closed handler").AtWarning() + } + } + + if conn == nil { + if err := retry.ExponentialBackoff(5, 200).On(func() error { + var err error + conn, err = dialer.Dial(ctx, rec.Destination) + if err != nil { + return err + } + return nil + }); err != nil { + return errors.New("failed to find an available destination").Base(err).AtWarning() } - return nil - }); err != nil { - return errors.New("failed to find an available destination").Base(err).AtWarning() } defer conn.Close() + ob.Conn = conn // for Vision's pre-connect + iConn := conn if statConn, ok := iConn.(*stat.CounterConnection); ok { iConn = statConn.Connection