diff --git a/common/crypto/crypto.go b/common/crypto/crypto.go index 464b4fa7..efc33a11 100644 --- a/common/crypto/crypto.go +++ b/common/crypto/crypto.go @@ -10,6 +10,9 @@ func RandBetween(from int64, to int64) int64 { if from == to { return from } + if from > to { + from, to = to, from + } bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from)) return from + bigInt.Int64() } diff --git a/infra/conf/vless.go b/infra/conf/vless.go index 5d10cc97..1dd9bad6 100644 --- a/infra/conf/vless.go +++ b/infra/conf/vless.go @@ -74,7 +74,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { } if account.Encryption != "" { - return nil, errors.New(`VLESS clients: "encryption" should not in inbound settings`) + return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`) } user.Account = serial.ToTypedMessage(account) @@ -107,12 +107,21 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { } config.Seconds = uint32(i) } - for i := 3; i < len(s); i++ { - if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 64 { + padding := 0 + for _, r := range s[3:] { + if len(r) < 20 { + padding += len(r) + 1 + continue + } + if b, _ := base64.RawURLEncoding.DecodeString(r); len(b) != 32 && len(b) != 64 { return false } } config.Decryption = config.Decryption[27+len(s[2]):] + if padding > 0 { + config.Padding = config.Decryption[:padding-1] + config.Decryption = config.Decryption[padding:] + } return true }() && config.Decryption != "none" { if config.Decryption == "" { @@ -121,6 +130,10 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { return nil, errors.New(`VLESS settings: unsupported "decryption": ` + config.Decryption) } + if config.Decryption != "none" && c.Fallbacks != nil { + return nil, errors.New(`VLESS settings: "fallbacks" can not be used together with "decryption"`) + } + for _, fb := range c.Fallbacks { var i uint16 var s string @@ -250,12 +263,21 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) { default: return false } - for i := 3; i < len(s); i++ { - if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 1184 { + padding := 0 + for _, r := range s[3:] { + if len(r) < 20 { + padding += len(r) + 1 + continue + } + if b, _ := base64.RawURLEncoding.DecodeString(r); len(b) != 32 && len(b) != 1184 { return false } } account.Encryption = account.Encryption[27+len(s[2]):] + if padding > 0 { + account.Padding = account.Encryption[:padding-1] + account.Encryption = account.Encryption[padding:] + } return true }() && account.Encryption != "none" { if account.Encryption == "" { diff --git a/proxy/vless/account.go b/proxy/vless/account.go index b1e09619..ce3eca27 100644 --- a/proxy/vless/account.go +++ b/proxy/vless/account.go @@ -20,6 +20,7 @@ func (a *Account) AsAccount() (protocol.Account, error) { Encryption: a.Encryption, // needs parser here? XorMode: a.XorMode, Seconds: a.Seconds, + Padding: a.Padding, }, nil } @@ -33,6 +34,7 @@ type MemoryAccount struct { Encryption string XorMode uint32 Seconds uint32 + Padding string } // Equals implements protocol.Account.Equals(). @@ -51,5 +53,6 @@ func (a *MemoryAccount) ToProto() proto.Message { Encryption: a.Encryption, XorMode: a.XorMode, Seconds: a.Seconds, + Padding: a.Padding, } } diff --git a/proxy/vless/account.pb.go b/proxy/vless/account.pb.go index 6048dc4e..b3027c47 100644 --- a/proxy/vless/account.pb.go +++ b/proxy/vless/account.pb.go @@ -32,6 +32,7 @@ type Account struct { Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"` XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"` Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"` + Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"` } func (x *Account) Reset() { @@ -99,12 +100,19 @@ func (x *Account) GetSeconds() uint32 { return 0 } +func (x *Account) GetPadding() string { + if x != nil { + return x.Padding + } + return "" +} + var File_proxy_vless_account_proto protoreflect.FileDescriptor var file_proxy_vless_account_proto_rawDesc = []byte{ 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63, 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, 0x81, 0x01, + 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x9b, 0x01, 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, @@ -113,12 +121,14 @@ var file_proxy_vless_account_proto_rawDesc = []byte{ 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, - 0x73, 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, + 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 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 ebb1feff..dab861bc 100644 --- a/proxy/vless/account.proto +++ b/proxy/vless/account.proto @@ -15,4 +15,5 @@ message Account { string encryption = 3; uint32 xorMode = 4; uint32 seconds = 5; + string padding = 6; } diff --git a/proxy/vless/encoding/encoding.go b/proxy/vless/encoding/encoding.go index 1176a960..da3a2517 100644 --- a/proxy/vless/encoding/encoding.go +++ b/proxy/vless/encoding/encoding.go @@ -172,7 +172,7 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A } // XtlsRead filter and read xtls protocol -func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, peerCache *[]byte, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error { +func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error { err := func() error { for { if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { @@ -194,23 +194,17 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, if !buffer.IsEmpty() { timer.Update() if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { - // XTLS Vision processes struct Encryption Conn's peerCache or TLS Conn's input and rawInput - if peerCache != nil { - if len(*peerCache) != 0 { - buffer = buf.MergeBytes(buffer, *peerCache) - } - } else { - if inputBuffer, err := buf.ReadFrom(input); err == nil { - if !inputBuffer.IsEmpty() { - buffer, _ = buf.MergeMulti(buffer, inputBuffer) - } - } - if rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil { - if !rawInputBuffer.IsEmpty() { - buffer, _ = buf.MergeMulti(buffer, rawInputBuffer) - } - } + // XTLS Vision processes TLS-like conn's input and rawInput + if inputBuffer, err := buf.ReadFrom(input); err == nil && !inputBuffer.IsEmpty() { + buffer, _ = buf.MergeMulti(buffer, inputBuffer) } + if rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil && !rawInputBuffer.IsEmpty() { + buffer, _ = buf.MergeMulti(buffer, rawInputBuffer) + } + *input = bytes.Reader{} // release memory + input = nil + *rawInput = bytes.Buffer{} // release memory + rawInput = nil } if werr := writer.WriteMultiBuffer(buffer); werr != nil { return werr diff --git a/proxy/vless/encryption/client.go b/proxy/vless/encryption/client.go index 77c0b334..1770a3ea 100644 --- a/proxy/vless/encryption/client.go +++ b/proxy/vless/encryption/client.go @@ -10,7 +10,6 @@ import ( "sync" "time" - "github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/protocol" "lukechampine.com/blake3" @@ -23,6 +22,8 @@ type ClientInstance struct { RelaysLength int XorMode uint32 Seconds uint32 + PaddingLens [][2]int + PaddingGaps [][2]int RWLock sync.RWMutex Expire time.Time @@ -30,15 +31,13 @@ type ClientInstance struct { Ticket []byte } -func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (err error) { +func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) { if i.NfsPKeys != nil { - err = errors.New("already initialized") - return + return errors.New("already initialized") } l := len(nfsPKeysBytes) if l == 0 { - err = errors.New("empty nfsPKeysBytes") - return + return errors.New("empty nfsPKeysBytes") } i.NfsPKeys = make([]any, l) i.NfsPKeysBytes = nfsPKeysBytes @@ -60,7 +59,7 @@ func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) ( i.RelaysLength -= 32 i.XorMode = xorMode i.Seconds = seconds - return + return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps) } func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { @@ -71,7 +70,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { ivAndRealysLength := 16 + i.RelaysLength pfsKeyExchangeLength := 18 + 1184 + 32 + 16 - paddingLength := int(crypto.RandBetween(100, 1000)) + paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps) clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength) iv := clientHello[:16] @@ -140,10 +139,18 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) - if _, err := conn.Write(clientHello); err != nil { - return nil, err + paddingLens[0] = ivAndRealysLength + pfsKeyExchangeLength + paddingLens[0] + for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + if l > 0 { + if _, err := conn.Write(clientHello[:l]); err != nil { + return nil, err + } + clientHello = clientHello[l:] + } + if len(paddingGaps) > i { + time.Sleep(paddingGaps[i]) + } } - // padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control encryptedPfsPublicKey := make([]byte, 1088+32+16) if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { diff --git a/proxy/vless/encryption/common.go b/proxy/vless/encryption/common.go index 00528acc..c6e8ce06 100644 --- a/proxy/vless/encryption/common.go +++ b/proxy/vless/encryption/common.go @@ -7,10 +7,12 @@ import ( "fmt" "io" "net" + "strconv" "strings" "sync" "time" + "github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/errors" "golang.org/x/crypto/chacha20poly1305" "lukechampine.com/blake3" @@ -31,15 +33,14 @@ type CommonConn struct { AEAD *AEAD PeerAEAD *AEAD PeerPadding []byte - PeerInBytes []byte - PeerCache []byte + rawInput bytes.Buffer + input bytes.Reader } func NewCommonConn(conn net.Conn, useAES bool) *CommonConn { return &CommonConn{ - Conn: conn, - UseAES: useAES, - PeerInBytes: make([]byte, 5+17000), // no need to use sync.Pool, because we are always reading + Conn: conn, + UseAES: useAES, } } @@ -99,16 +100,14 @@ func (c *CommonConn) Read(b []byte) (int, error) { } c.PeerPadding = nil } - if len(c.PeerCache) > 0 { - n := copy(b, c.PeerCache) - c.PeerCache = c.PeerCache[n:] - return n, nil + if c.input.Len() > 0 { + return c.input.Read(b) } - peerHeader := c.PeerInBytes[:5] - if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { + peerHeader := [5]byte{} + if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil { return 0, err } - l, err := DecodeHeader(c.PeerInBytes[:5]) // l: 17~17000 + l, err := DecodeHeader(peerHeader[:]) // l: 17~17000 if err != nil { if c.Client != nil && strings.Contains(err.Error(), "invalid header: ") { // client's 0-RTT c.Client.RWLock.Lock() @@ -121,7 +120,10 @@ func (c *CommonConn) Read(b []byte) (int, error) { return 0, err } c.Client = nil - peerData := c.PeerInBytes[5 : 5+l] + if c.rawInput.Cap() < l { + c.rawInput.Grow(l) // no need to use sync.Pool, because we are always reading + } + peerData := c.rawInput.Bytes()[:l] if _, err := io.ReadFull(c.Conn, peerData); err != nil { return 0, err } @@ -131,9 +133,9 @@ func (c *CommonConn) Read(b []byte) (int, error) { } var newAEAD *AEAD if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) { - newAEAD = NewAEAD(c.PeerInBytes[:5+l], c.UnitedKey, c.UseAES) + newAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES) } - _, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader) + _, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:]) if newAEAD != nil { c.PeerAEAD = newAEAD } @@ -141,7 +143,7 @@ func (c *CommonConn) Read(b []byte) (int, error) { return 0, err } if len(dst) > len(b) { - c.PeerCache = dst[copy(b, dst):] + c.input.Reset(dst[copy(b, dst):]) dst = b // for len(dst) } return len(dst), nil @@ -213,7 +215,55 @@ func DecodeHeader(h []byte) (l int, err error) { l = 0 } if l < 17 || l > 17000 { // TODO: TLSv1.3 max length - err = errors.New("invalid header: ", fmt.Sprintf("%v", h[:5])) // DO NOT CHANGE: relied by client's Read() + err = errors.New("invalid header: " + fmt.Sprintf("%v", h[:5])) // DO NOT CHANGE: relied by client's Read() + } + return +} + +func ParsePadding(padding string, paddingLens, paddingGaps *[][2]int) (err error) { + if padding == "" { + return + } + maxLen := 0 + for i, s := range strings.Split(padding, ".") { + x := strings.SplitN(s, "-", 2) + if len(x) != 2 || x[0] == "" || x[1] == "" { + return errors.New("invalid padding lenth/gap parameter: " + s) + } + y := [2]int{} + if y[0], err = strconv.Atoi(x[0]); err != nil { + return + } + if y[1], err = strconv.Atoi(x[1]); err != nil { + return + } + if i == 0 && (y[0] < 17 || y[1] < 17) { + return errors.New("first padding length must be larger than 16") + } + if i%2 == 0 { + *paddingLens = append(*paddingLens, y) + maxLen += max(y[0], y[1]) + } else { + *paddingGaps = append(*paddingGaps, y) + } + } + if maxLen > 65535 { + return errors.New("total padding length must be smaller than 65536") + } + return +} + +func CreatPadding(paddingLens, paddingGaps [][2]int) (length int, lens []int, gaps []time.Duration) { + if len(paddingLens) == 0 { + paddingLens = [][2]int{{111, 1111}, {3333, -1234}} + paddingGaps = [][2]int{{111, -66}} + } + for _, l := range paddingLens { + lens = append(lens, int(max(0, crypto.RandBetween(int64(l[0]), int64(l[1]))))) + length += lens[len(lens)-1] + } + for _, g := range paddingGaps { + gaps = append(gaps, time.Duration(max(0, crypto.RandBetween(int64(g[0]), int64(g[1]))))*time.Millisecond) } return } diff --git a/proxy/vless/encryption/server.go b/proxy/vless/encryption/server.go index ec0532bb..b9433ba3 100644 --- a/proxy/vless/encryption/server.go +++ b/proxy/vless/encryption/server.go @@ -30,21 +30,21 @@ type ServerInstance struct { RelaysLength int XorMode uint32 Seconds uint32 + PaddingLens [][2]int + PaddingGaps [][2]int RWLock sync.RWMutex Sessions map[[16]byte]*ServerSession Closed bool } -func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) { +func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) { if i.NfsSKeys != nil { - err = errors.New("already initialized") - return + return errors.New("already initialized") } l := len(nfsSKeysBytes) if l == 0 { - err = errors.New("empty nfsSKeysBytes") - return + return errors.New("empty nfsSKeysBytes") } i.NfsSKeys = make([]any, l) i.NfsPKeysBytes = make([][]byte, l) @@ -88,7 +88,7 @@ func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) ( } }() } - return + return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps) } func (i *ServerInstance) Close() (err error) { @@ -98,7 +98,7 @@ func (i *ServerInstance) Close() (err error) { return } -func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { +func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn, error) { if i.NfsSKeys == nil { return nil, errors.New("uninitialized") } @@ -108,6 +108,9 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { if _, err := io.ReadFull(conn, ivAndRelays); err != nil { return nil, err } + if fallback != nil { + *fallback = append(*fallback, ivAndRelays...) + } iv := ivAndRelays[:16] relays := ivAndRelays[16:] var nfsKey []byte @@ -157,6 +160,9 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { if _, err := io.ReadFull(conn, encryptedLength); err != nil { return nil, err } + if fallback != nil { + *fallback = append(*fallback, encryptedLength...) + } decryptedLength := make([]byte, 2) if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil { c.UseAES = !c.UseAES @@ -165,6 +171,9 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { return nil, err } } + if fallback != nil { + *fallback = nil + } length := DecodeLength(decryptedLength) if length == 32 { @@ -183,7 +192,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { s := i.Sessions[[16]byte(ticket)] i.RWLock.RUnlock() if s == nil { - noises := make([]byte, crypto.RandBetween(1268, 2268)) // matches 1-RTT's server hello length for "random", though it is not important, just for example + noises := make([]byte, crypto.RandBetween(1279, 2279)) // matches 1-RTT's server hello length for "random", though it is not important, just for example var err error for err == nil { rand.Read(noises) @@ -237,25 +246,10 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { c.UnitedKey = append(pfsKey, nfsKey...) c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES) c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES) + ticket := make([]byte, 16) rand.Read(ticket) copy(ticket, EncodeLength(int(i.Seconds*4/5))) - - pfsKeyExchangeLength := 1088 + 32 + 16 - encryptedTicketLength := 32 - paddingLength := int(crypto.RandBetween(100, 1000)) - serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength) - nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil) - c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil) - padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:] - c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) - c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) - - if _, err := conn.Write(serverHello); err != nil { - return nil, err - } - // padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control - if i.Seconds > 0 { i.RWLock.Lock() i.Sessions[[16]byte(ticket)] = &ServerSession{ @@ -265,6 +259,29 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { i.RWLock.Unlock() } + pfsKeyExchangeLength := 1088 + 32 + 16 + encryptedTicketLength := 32 + paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps) + serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength) + nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil) + c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil) + padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:] + c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) + c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) + + paddingLens[0] = pfsKeyExchangeLength + encryptedTicketLength + paddingLens[0] + for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + if l > 0 { + if _, err := conn.Write(serverHello[:l]); err != nil { + return nil, err + } + serverHello = serverHello[l:] + } + if len(paddingGaps) > i { + time.Sleep(paddingGaps[i]) + } + } + // important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern if _, err := io.ReadFull(conn, encryptedLength); err != nil { return nil, err diff --git a/proxy/vless/inbound/config.pb.go b/proxy/vless/inbound/config.pb.go index e3192cf8..ec28db8d 100644 --- a/proxy/vless/inbound/config.pb.go +++ b/proxy/vless/inbound/config.pb.go @@ -116,6 +116,7 @@ type Config struct { Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"` XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"` Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"` + Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"` } func (x *Config) Reset() { @@ -183,6 +184,13 @@ func (x *Config) GetSeconds() uint32 { return 0 } +func (x *Config) GetPadding() string { + if x != nil { + return x.Padding + } + return "" +} + var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor var file_proxy_vless_inbound_config_proto_rawDesc = []byte{ @@ -199,7 +207,7 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{ 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, - 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xd4, 0x01, + 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xee, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, @@ -213,14 +221,16 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{ 0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, - 0x6f, 0x6e, 0x64, 0x73, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, - 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, - 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d, 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, 0x2f, 0x69, 0x6e, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, - 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x6a, + 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, + 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, + 0x5a, 0x2d, 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, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, + 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, + 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( diff --git a/proxy/vless/inbound/config.proto b/proxy/vless/inbound/config.proto index e1ebc8d3..2a7ad833 100644 --- a/proxy/vless/inbound/config.proto +++ b/proxy/vless/inbound/config.proto @@ -24,4 +24,5 @@ message Config { string decryption = 3; uint32 xorMode = 4; uint32 seconds = 5; + string padding = 6; } diff --git a/proxy/vless/inbound/inbound.go b/proxy/vless/inbound/inbound.go index ca5176b9..617313bd 100644 --- a/proxy/vless/inbound/inbound.go +++ b/proxy/vless/inbound/inbound.go @@ -92,7 +92,7 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val nfsSKeysBytes = append(nfsSKeysBytes, b) } handler.decryption = &encryption.ServerInstance{} - if err := handler.decryption.Init(nfsSKeysBytes, config.XorMode, config.Seconds); err != nil { + if err := handler.decryption.Init(nfsSKeysBytes, config.XorMode, config.Seconds, config.Padding); err != nil { return nil, errors.New("failed to use decryption").Base(err).AtError() } } @@ -220,8 +220,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s if h.decryption != nil { var err error - connection, err = h.decryption.Handshake(connection) - if err != nil { + if connection, err = h.decryption.Handshake(connection, nil); err != nil { return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() } } @@ -491,7 +490,6 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s // Flow: requestAddons.Flow, } - var peerCache *[]byte var input *bytes.Reader var rawInput *bytes.Buffer switch requestAddons.Flow { @@ -504,16 +502,15 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s case protocol.RequestCommandMux: fallthrough // we will break Mux connections that contain TCP requests case protocol.RequestCommandTCP: - if serverConn, ok := connection.(*encryption.CommonConn); ok { - peerCache = &serverConn.PeerCache - if _, ok := serverConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) { - inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice - } - break - } var t reflect.Type var p uintptr - if tlsConn, ok := iConn.(*tls.Conn); ok { + if commonConn, ok := connection.(*encryption.CommonConn); ok { + if _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) { + inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice + } + t = reflect.TypeOf(commonConn).Elem() + p = uintptr(unsafe.Pointer(commonConn)) + } else if tlsConn, ok := iConn.(*tls.Conn); ok { if tlsConn.ConnectionState().Version != gotls.VersionTLS13 { return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning() } @@ -579,7 +576,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s if requestAddons.Flow == vless.XRV { ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx1) - err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, peerCache, input, rawInput, trafficState, nil, true, ctx1) + err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, input, rawInput, trafficState, nil, true, ctx1) } else { // from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)) diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go index ee1b6dfb..76352a0a 100644 --- a/proxy/vless/outbound/outbound.go +++ b/proxy/vless/outbound/outbound.go @@ -77,7 +77,7 @@ func New(ctx context.Context, config *Config) (*Handler, error) { nfsPKeysBytes = append(nfsPKeysBytes, b) } handler.encryption = &encryption.ClientInstance{} - if err := handler.encryption.Init(nfsPKeysBytes, a.XorMode, a.Seconds); err != nil { + if err := handler.encryption.Init(nfsPKeysBytes, a.XorMode, a.Seconds, a.Padding); err != nil { return nil, errors.New("failed to use encryption").Base(err).AtError() } } @@ -118,8 +118,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte if h.encryption != nil { var err error - conn, err = h.encryption.Handshake(conn) - if err != nil { + if conn, err = h.encryption.Handshake(conn); err != nil { return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() } } @@ -146,7 +145,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte Flow: account.Flow, } - var peerCache *[]byte var input *bytes.Reader var rawInput *bytes.Buffer allowUDP443 := false @@ -165,16 +163,15 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte case protocol.RequestCommandMux: fallthrough // let server break Mux connections that contain TCP requests case protocol.RequestCommandTCP: - if clientConn, ok := conn.(*encryption.CommonConn); ok { - peerCache = &clientConn.PeerCache - if _, ok := clientConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) { - ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice - } - break - } var t reflect.Type var p uintptr - if tlsConn, ok := iConn.(*tls.Conn); ok { + if commonConn, ok := conn.(*encryption.CommonConn); ok { + if _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) { + ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice + } + t = reflect.TypeOf(commonConn).Elem() + p = uintptr(unsafe.Pointer(commonConn)) + } else if tlsConn, ok := iConn.(*tls.Conn); ok { t = reflect.TypeOf(tlsConn.Conn).Elem() p = uintptr(unsafe.Pointer(tlsConn.Conn)) } else if utlsConn, ok := iConn.(*tls.UConn); ok { @@ -306,7 +303,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } if requestAddons.Flow == vless.XRV { - err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, peerCache, input, rawInput, trafficState, ob, false, ctx) + err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, input, rawInput, trafficState, ob, false, ctx) } else { // from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))