Compare commits

...
Author SHA1 Message Date
风扇滑翔翼 8351ccb625 Add NewClient() 2025-09-10 05:12:40 +00:00
风扇滑翔翼 f42a518bf6 Refine time usage 2025-09-10 04:56:41 +00:00
风扇滑翔翼 033d2ba2b9 Remove init 2025-09-06 19:21:29 +00:00
风扇滑翔翼 30e3fa690e Add use remote time for vmess 2025-09-06 18:23:12 +00:00
RPRX 6ec0291d4e app/reverse/bridge.go: Fix DispatchLink() returns immediately
Fixes https://github.com/XTLS/Xray-core/issues/5088
2025-09-05 15:58:49 +00:00
RPRX 118131fcaf v25.9.5
Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

VLESS Post-Quantum Encryption: https://github.com/XTLS/Xray-core/pull/5067
VLESS NFT: https://opensea.io/collection/vless

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-09-05 09:02:36 +00:00
patterniha 197b319f9a DNS outbound: Fix some issues (#5081) 2025-09-05 08:15:16 +00:00
风扇滑翔翼andRPRX 8b579bf3ec Commands: Add vlessenc (generate complete json pair directly) (#5078)
https://github.com/XTLS/Xray-core/pull/5078#issuecomment-3254161589

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-09-05 08:14:48 +00:00
RPRX cbade89ab1 VLESS Encryption: Improve server-side tickets' expiration mechanism
https://github.com/XTLS/Xray-core/pull/5067#issuecomment-3253717319
2025-09-04 14:03:55 +00:00
RPRX d20397c15d DispatchLink(): Fix user stats
Fixes https://github.com/XTLS/Xray-core/pull/5076#issuecomment-3243431593
2025-09-03 23:25:17 +00:00
RPRX 19f8907296 VLESS Encryption: Randomize seconds in ticket and simplify expiration mechanism
https://github.com/XTLS/Xray-core/pull/5067#issuecomment-3246925902
2025-09-02 23:37:14 +00:00
RPRX e943de5300 proxy/proxy.go: IsRAWTransport() -> IsRAWTransportWithoutSecurity() 2025-09-02 18:15:08 +00:00
yuhan6665 4064f8dd80 XTLS Vision: Refactor code to use DispatchLink() in VLESS inbound (#5076)
* Xtls: code refactor

- Move more logic to VisionReader/Writer
- Remove XtlsWrite()
- XtlsRead now only handle splice at the outbound
- This helps VLESS inbound to have simple buf.copy() so that we can remove pipe next

* Add bufferFlushNext; Use DispatchLink() in VLESS inbound

* Use TimeoutWrapperReader; clean up timer/buffer
2025-09-01 15:15:32 +00:00
yuhan6665 2acd206821 Direct/Freedom outbound: Use proxy.IsRAWTransport(conn) (#5074) 2025-09-01 15:03:01 +00:00
RPRX 4c6fd94d97 VLESS Encryption: Server checks one specific zero-bit in the peer-sent X25519 public key in relays
https://github.com/XTLS/Xray-core/pull/5067#issuecomment-3240198336
2025-09-01 15:01:54 +00:00
RPRX fd54b10d97 TimeoutWrapperReader: Fix latency issue
Pre-released for 2 days and no one had ever noticed this issue until today : (
2025-09-01 15:00:59 +00:00
RPRX 6830089d3c v25.8.31
Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

VLESS Post-Quantum Encryption: https://github.com/XTLS/Xray-core/pull/5067
VLESS NFT: https://opensea.io/collection/vless

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-08-31 13:30:42 +00:00
RPRX 6768a22f67 VLESS Encryption: Switch to "probability-from-to" format for customizable 1-RTT padding parameters
See https://github.com/XTLS/Xray-core/pull/5067#issue-3361308276 for details
2025-08-31 11:35:38 +00:00
RPRXandwwqgtxx e8b02cd664 VLESS Encryption: Add customizable 1-RTT padding parameters; Decrease memory using; Chores
Completes https://github.com/XTLS/Xray-core/pull/5067

---------

Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
2025-08-31 04:09:28 +00:00
36 changed files with 793 additions and 439 deletions
+42
View File
@@ -196,6 +196,47 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
return inboundLink, outboundLink return inboundLink, outboundLink
} }
func (d *DefaultDispatcher) WrapLink(ctx context.Context, link *transport.Link) *transport.Link {
sessionInbound := session.InboundFromContext(ctx)
var user *protocol.MemoryUser
if sessionInbound != nil {
user = sessionInbound.User
}
link.Reader = &buf.TimeoutWrapperReader{Reader: link.Reader}
if user != nil && len(user.Email) > 0 {
p := d.policy.ForLevel(user.Level)
if p.Stats.UserUplink {
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
link.Reader.(*buf.TimeoutWrapperReader).Counter = c
}
}
if p.Stats.UserDownlink {
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
link.Writer = &SizeStatWriter{
Counter: c,
Writer: link.Writer,
}
}
}
if p.Stats.UserOnline {
name := "user>>>" + user.Email + ">>>online"
if om, _ := stats.GetOrRegisterOnlineMap(d.stats, name); om != nil {
sessionInbounds := session.InboundFromContext(ctx)
userIP := sessionInbounds.Source.Address.String()
om.AddIP(userIP)
// log Online user with ips
// errors.LogDebug(ctx, "user>>>" + user.Email + ">>>online", om.Count(), om.List())
}
}
}
return link
}
func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool { func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
domain := result.Domain() domain := result.Domain()
if domain == "" { if domain == "" {
@@ -316,6 +357,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
content = new(session.Content) content = new(session.Content)
ctx = session.ContextWithContent(ctx, content) ctx = session.ContextWithContent(ctx, content)
} }
outbound = d.WrapLink(ctx, outbound)
sniffingRequest := content.SniffingRequest sniffingRequest := content.SniffingRequest
if !sniffingRequest.Enabled { if !sniffingRequest.Enabled {
d.routedDispatch(ctx, outbound, destination) d.routedDispatch(ctx, outbound, destination)
+3 -3
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"time" "time"
"github.com/xtls/xray-core/app/dispatcher"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/mux" "github.com/xtls/xray-core/common/mux"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
@@ -148,7 +149,6 @@ func (w *BridgeWorker) Connections() uint32 {
} }
func (w *BridgeWorker) handleInternalConn(link *transport.Link) { func (w *BridgeWorker) handleInternalConn(link *transport.Link) {
go func() {
reader := link.Reader reader := link.Reader
for { for {
mb, err := reader.ReadMultiBuffer() mb, err := reader.ReadMultiBuffer()
@@ -166,7 +166,6 @@ func (w *BridgeWorker) handleInternalConn(link *transport.Link) {
} }
} }
} }
}()
} }
func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) { func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) {
@@ -181,7 +180,7 @@ func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*tra
uplinkReader, uplinkWriter := pipe.New(opt...) uplinkReader, uplinkWriter := pipe.New(opt...)
downlinkReader, downlinkWriter := pipe.New(opt...) downlinkReader, downlinkWriter := pipe.New(opt...)
w.handleInternalConn(&transport.Link{ go w.handleInternalConn(&transport.Link{
Reader: downlinkReader, Reader: downlinkReader,
Writer: uplinkWriter, Writer: uplinkWriter,
}) })
@@ -200,6 +199,7 @@ func (w *BridgeWorker) DispatchLink(ctx context.Context, dest net.Destination, l
return w.dispatcher.DispatchLink(ctx, dest, link) return w.dispatcher.DispatchLink(ctx, dest, link)
} }
link = w.dispatcher.(*dispatcher.DefaultDispatcher).WrapLink(ctx, link)
w.handleInternalConn(link) w.handleInternalConn(link)
return nil return nil
+17 -4
View File
@@ -30,6 +30,7 @@ type TimeoutReader interface {
type TimeoutWrapperReader struct { type TimeoutWrapperReader struct {
Reader Reader
stats.Counter
mb MultiBuffer mb MultiBuffer
err error err error
done chan struct{} done chan struct{}
@@ -39,11 +40,16 @@ func (r *TimeoutWrapperReader) ReadMultiBuffer() (MultiBuffer, error) {
if r.done != nil { if r.done != nil {
<-r.done <-r.done
r.done = nil r.done = nil
if r.Counter != nil {
r.Counter.Add(int64(r.mb.Len()))
}
return r.mb, r.err return r.mb, r.err
} }
r.mb = nil r.mb, r.err = r.Reader.ReadMultiBuffer()
r.err = nil if r.Counter != nil {
return r.Reader.ReadMultiBuffer() r.Counter.Add(int64(r.mb.Len()))
}
return r.mb, r.err
} }
func (r *TimeoutWrapperReader) ReadMultiBufferTimeout(duration time.Duration) (MultiBuffer, error) { func (r *TimeoutWrapperReader) ReadMultiBufferTimeout(duration time.Duration) (MultiBuffer, error) {
@@ -54,12 +60,19 @@ func (r *TimeoutWrapperReader) ReadMultiBufferTimeout(duration time.Duration) (M
close(r.done) close(r.done)
}() }()
} }
timeout := make(chan struct{})
go func() {
time.Sleep(duration) time.Sleep(duration)
close(timeout)
}()
select { select {
case <-r.done: case <-r.done:
r.done = nil r.done = nil
if r.Counter != nil {
r.Counter.Add(int64(r.mb.Len()))
}
return r.mb, r.err return r.mb, r.err
default: case <-timeout:
return nil, nil return nil, nil
} }
} }
+14
View File
@@ -78,6 +78,7 @@ type BufferedWriter struct {
writer Writer writer Writer
buffer *Buffer buffer *Buffer
buffered bool buffered bool
flushNext bool
} }
// NewBufferedWriter creates a new BufferedWriter. // NewBufferedWriter creates a new BufferedWriter.
@@ -161,6 +162,12 @@ func (w *BufferedWriter) WriteMultiBuffer(b MultiBuffer) error {
} }
} }
if w.flushNext {
w.buffered = false
w.flushNext = false
return w.flushInternal()
}
return nil return nil
} }
@@ -201,6 +208,13 @@ func (w *BufferedWriter) SetBuffered(f bool) error {
return nil return nil
} }
// SetFlushNext will wait the next WriteMultiBuffer to flush and set buffered = false
func (w *BufferedWriter) SetFlushNext() {
w.Lock()
defer w.Unlock()
w.flushNext = true
}
// ReadFrom implements io.ReaderFrom. // ReadFrom implements io.ReaderFrom.
func (w *BufferedWriter) ReadFrom(reader io.Reader) (int64, error) { func (w *BufferedWriter) ReadFrom(reader io.Reader) (int64, error) {
if err := w.SetBuffered(false); err != nil { if err := w.SetBuffered(false); err != nil {
+3
View File
@@ -10,6 +10,9 @@ func RandBetween(from int64, to int64) int64 {
if from == to { if from == to {
return from return from
} }
if from > to {
from, to = to, from
}
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from)) bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
return from + bigInt.Int64() return from + bigInt.Int64()
} }
+2
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"io" "io"
"github.com/xtls/xray-core/app/dispatcher"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -61,6 +62,7 @@ func (s *Server) DispatchLink(ctx context.Context, dest net.Destination, link *t
if dest.Address != muxCoolAddress { if dest.Address != muxCoolAddress {
return s.dispatcher.DispatchLink(ctx, dest, link) return s.dispatcher.DispatchLink(ctx, dest, link)
} }
link = s.dispatcher.(*dispatcher.DefaultDispatcher).WrapLink(ctx, link)
_, err := NewServerWorker(ctx, s.dispatcher, link) _, err := NewServerWorker(ctx, s.dispatcher, link)
return err return err
} }
+34
View File
@@ -0,0 +1,34 @@
package http
import (
"context"
gohttp "net/http"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet"
)
// NewClient creates an HTTP client with with internal dialer and using the given sockopt.
// sockopt can only have one or empty.
func NewClient(sockopt ...*internet.SocketConfig) *gohttp.Client {
var Sockopt *internet.SocketConfig
switch len(sockopt) {
case 0:
case 1:
Sockopt = sockopt[0]
default:
panic("sockopt can only be nil or have one")
}
httpClient := &gohttp.Client{
Transport: &gohttp.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
dest, err := net.ParseDestination(network + ":" + addr)
if err != nil {
return nil, err
}
return internet.DialSystem(ctx, dest, Sockopt)
},
},
}
return httpClient
}
+2 -2
View File
@@ -18,8 +18,8 @@ import (
var ( var (
Version_x byte = 25 Version_x byte = 25
Version_y byte = 8 Version_y byte = 9
Version_z byte = 29 Version_z byte = 5
) )
var ( var (
+36 -12
View File
@@ -74,7 +74,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
} }
if account.Encryption != "" { 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) user.Account = serial.ToTypedMessage(account)
@@ -96,23 +96,34 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
default: default:
return false return false
} }
if s[2] != "1rtt" { t := strings.SplitN(strings.TrimSuffix(s[2], "s"), "-", 2)
t := strings.TrimSuffix(s[2], "s") i, err := strconv.Atoi(t[0])
if t == s[2] {
return false
}
i, err := strconv.Atoi(t)
if err != nil { if err != nil {
return false return false
} }
config.Seconds = uint32(i) config.SecondsFrom = int64(i)
if len(t) == 2 {
i, err := strconv.Atoi(t[1])
if err != nil {
return false
} }
for i := 3; i < len(s); i++ { config.SecondsTo = int64(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 return false
} }
} }
config.Decryption = config.Decryption[27+len(s[2]):] config.Decryption = config.Decryption[27+len(s[2]):]
if padding > 0 {
config.Padding = config.Decryption[:padding-1]
config.Decryption = config.Decryption[padding:]
}
return true return true
}() && config.Decryption != "none" { }() && config.Decryption != "none" {
if config.Decryption == "" { if config.Decryption == "" {
@@ -121,6 +132,10 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
return nil, errors.New(`VLESS settings: unsupported "decryption": ` + config.Decryption) 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 { for _, fb := range c.Fallbacks {
var i uint16 var i uint16
var s string var s string
@@ -250,12 +265,21 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
default: default:
return false return false
} }
for i := 3; i < len(s); i++ { padding := 0
if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 1184 { 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 return false
} }
} }
account.Encryption = account.Encryption[27+len(s[2]):] account.Encryption = account.Encryption[27+len(s[2]):]
if padding > 0 {
account.Padding = account.Encryption[:padding-1]
account.Encryption = account.Encryption[padding:]
}
return true return true
}() && account.Encryption != "none" { }() && account.Encryption != "none" {
if account.Encryption == "" { if account.Encryption == "" {
+1
View File
@@ -18,5 +18,6 @@ func init() {
cmdWG, cmdWG,
cmdMLDSA65, cmdMLDSA65,
cmdMLKEM768, cmdMLKEM768,
cmdVLESSEnc,
) )
} }
+19 -7
View File
@@ -25,6 +25,21 @@ func Curve25519Genkey(StdEncoding bool, input_base64 string) {
return return
} }
} }
privateKey, password, hash32, err := genCurve25519(privateKey)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("PrivateKey: %v\nPassword: %v\nHash32: %v\n",
encoding.EncodeToString(privateKey),
encoding.EncodeToString(password),
encoding.EncodeToString(hash32[:]))
}
func genCurve25519(inputPrivateKey []byte) (privateKey []byte, password []byte, hash32 [32]byte, returnErr error) {
if len(inputPrivateKey) > 0 {
privateKey = inputPrivateKey
}
if privateKey == nil { if privateKey == nil {
privateKey = make([]byte, 32) privateKey = make([]byte, 32)
rand.Read(privateKey) rand.Read(privateKey)
@@ -39,13 +54,10 @@ func Curve25519Genkey(StdEncoding bool, input_base64 string) {
key, err := ecdh.X25519().NewPrivateKey(privateKey) key, err := ecdh.X25519().NewPrivateKey(privateKey)
if err != nil { if err != nil {
fmt.Println(err.Error()) returnErr = err
return return
} }
password := key.PublicKey().Bytes() password = key.PublicKey().Bytes()
hash32 := blake3.Sum256(password) hash32 = blake3.Sum256(password)
fmt.Printf("PrivateKey: %v\nPassword: %v\nHash32: %v", return
encoding.EncodeToString(privateKey),
encoding.EncodeToString(password),
encoding.EncodeToString(hash32[:]))
} }
+1 -1
View File
@@ -40,7 +40,7 @@ func executeMLDSA65(cmd *base.Command, args []string) {
rand.Read(seed[:]) rand.Read(seed[:])
} }
pub, _ := mldsa65.NewKeyFromSeed(&seed) pub, _ := mldsa65.NewKeyFromSeed(&seed)
fmt.Printf("Seed: %v\nVerify: %v", fmt.Printf("Seed: %v\nVerify: %v\n",
base64.RawURLEncoding.EncodeToString(seed[:]), base64.RawURLEncoding.EncodeToString(seed[:]),
base64.RawURLEncoding.EncodeToString(pub.Bytes())) base64.RawURLEncoding.EncodeToString(pub.Bytes()))
} }
+16 -6
View File
@@ -12,9 +12,9 @@ import (
var cmdMLKEM768 = &base.Command{ var cmdMLKEM768 = &base.Command{
UsageLine: `{{.Exec}} mlkem768 [-i "seed (base64.RawURLEncoding)"]`, UsageLine: `{{.Exec}} mlkem768 [-i "seed (base64.RawURLEncoding)"]`,
Short: `Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS)`, Short: `Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS Encryption)`,
Long: ` Long: `
Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS). Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS Encryption).
Random: {{.Exec}} mlkem768 Random: {{.Exec}} mlkem768
@@ -40,11 +40,21 @@ func executeMLKEM768(cmd *base.Command, args []string) {
} else { } else {
rand.Read(seed[:]) rand.Read(seed[:])
} }
key, _ := mlkem.NewDecapsulationKey768(seed[:]) seed, client, hash32 := genMLKEM768(&seed)
client := key.EncapsulationKey().Bytes() fmt.Printf("Seed: %v\nClient: %v\nHash32: %v\n",
hash32 := blake3.Sum256(client)
fmt.Printf("Seed: %v\nClient: %v\nHash32: %v",
base64.RawURLEncoding.EncodeToString(seed[:]), base64.RawURLEncoding.EncodeToString(seed[:]),
base64.RawURLEncoding.EncodeToString(client), base64.RawURLEncoding.EncodeToString(client),
base64.RawURLEncoding.EncodeToString(hash32[:])) base64.RawURLEncoding.EncodeToString(hash32[:]))
} }
func genMLKEM768(inputSeed *[64]byte) (seed [64]byte, client []byte, hash32 [32]byte) {
if inputSeed == nil {
rand.Read(seed[:])
} else {
seed = *inputSeed
}
key, _ := mlkem.NewDecapsulationKey768(seed[:])
client = key.EncapsulationKey().Bytes()
hash32 = blake3.Sum256(client)
return
}
+41
View File
@@ -0,0 +1,41 @@
package all
import (
"encoding/base64"
"fmt"
"strings"
"github.com/xtls/xray-core/main/commands/base"
)
var cmdVLESSEnc = &base.Command{
UsageLine: `{{.Exec}} vlessenc`,
Short: `Generate decryption/encryption json pair (VLESS Encryption)`,
Long: `
Generate decryption/encryption json pair (VLESS Encryption).
`,
}
func init() {
cmdVLESSEnc.Run = executeVLESSEnc // break init loop
}
func executeVLESSEnc(cmd *base.Command, args []string) {
privateKey, password, _, _ := genCurve25519(nil)
serverKey := base64.RawURLEncoding.EncodeToString(privateKey)
clientKey := base64.RawURLEncoding.EncodeToString(password)
decryption := generateDotConfig("mlkem768x25519plus", "native", "600s", serverKey)
encryption := generateDotConfig("mlkem768x25519plus", "native", "0rtt", clientKey)
seed, client, _ := genMLKEM768(nil)
serverKeyPQ := base64.RawURLEncoding.EncodeToString(seed[:])
clientKeyPQ := base64.RawURLEncoding.EncodeToString(client)
decryptionPQ := generateDotConfig("mlkem768x25519plus", "native", "600s", serverKeyPQ)
encryptionPQ := generateDotConfig("mlkem768x25519plus", "native", "0rtt", clientKeyPQ)
fmt.Printf("Choose one Authentication to use, do not mix them. Ephemeral key exchange is Post-Quantum safe anyway.\n\n")
fmt.Printf("Authentication: X25519, not Post-Quantum\n\"decryption\": \"%v\"\n\"encryption\": \"%v\"\n\n", decryption, encryption)
fmt.Printf("Authentication: ML-KEM-768, Post-Quantum\n\"decryption\": \"%v\"\n\"encryption\": \"%v\"\n", decryptionPQ, encryptionPQ)
}
func generateDotConfig(fields ...string) string {
return strings.Join(fields, ".")
}
+2 -2
View File
@@ -6,9 +6,9 @@ import (
var cmdX25519 = &base.Command{ var cmdX25519 = &base.Command{
UsageLine: `{{.Exec}} x25519 [-i "private key (base64.RawURLEncoding)"] [--std-encoding]`, UsageLine: `{{.Exec}} x25519 [-i "private key (base64.RawURLEncoding)"] [--std-encoding]`,
Short: `Generate key pair for X25519 key exchange (VLESS, REALITY)`, Short: `Generate key pair for X25519 key exchange (REALITY, VLESS Encryption)`,
Long: ` Long: `
Generate key pair for X25519 key exchange (VLESS, REALITY). Generate key pair for X25519 key exchange (REALITY, VLESS Encryption).
Random: {{.Exec}} x25519 Random: {{.Exec}} x25519
+51 -21
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
go_errors "errors" go_errors "errors"
"io" "io"
"strings"
"sync" "sync"
"time" "time"
@@ -168,11 +169,15 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
} }
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, h.timeout) terminate := func() {
cancel()
conn.Close()
}
timer := signal.CancelAfterInactivity(ctx, terminate, h.timeout)
defer timer.SetTimeout(0)
request := func() error { request := func() error {
defer conn.Close() defer timer.SetTimeout(0)
for { for {
b, err := reader.ReadMessage() b, err := reader.ReadMessage()
if err == io.EOF { if err == io.EOF {
@@ -190,24 +195,33 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
if len(h.blockTypes) > 0 { if len(h.blockTypes) > 0 {
for _, blocktype := range h.blockTypes { for _, blocktype := range h.blockTypes {
if blocktype == int32(qType) { if blocktype == int32(qType) {
if h.nonIPQuery == "reject" { b.Release()
go h.rejectNonIPQuery(id, qType, domain, writer)
}
errors.LogInfo(ctx, "blocked type ", qType, " query for domain ", domain) errors.LogInfo(ctx, "blocked type ", qType, " query for domain ", domain)
if h.nonIPQuery == "reject" {
err := h.rejectNonIPQuery(id, qType, domain, writer)
if err != nil {
return err
}
}
return nil return nil
} }
} }
} }
if isIPQuery { if isIPQuery {
go h.handleIPQuery(id, qType, domain, writer) b.Release()
go h.handleIPQuery(id, qType, domain, writer, timer)
continue
} }
if isIPQuery || h.nonIPQuery == "drop" { if h.nonIPQuery == "drop" {
b.Release() b.Release()
continue continue
} }
if h.nonIPQuery == "reject" { if h.nonIPQuery == "reject" {
go h.rejectNonIPQuery(id, qType, domain, writer)
b.Release() b.Release()
err := h.rejectNonIPQuery(id, qType, domain, writer)
if err != nil {
return err
}
continue continue
} }
} }
@@ -219,6 +233,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
} }
response := func() error { response := func() error {
defer timer.SetTimeout(0)
for { for {
b, err := connReader.ReadMessage() b, err := connReader.ReadMessage()
if err == io.EOF { if err == io.EOF {
@@ -244,7 +259,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
return nil return nil
} }
func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) { func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter, timer *signal.ActivityTimer) {
var ips []net.IP var ips []net.IP
var err error var err error
@@ -319,16 +334,21 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
if err != nil { if err != nil {
errors.LogInfoInner(context.Background(), err, "pack message") errors.LogInfoInner(context.Background(), err, "pack message")
b.Release() b.Release()
return timer.SetTimeout(0)
} }
b.Resize(0, int32(len(msgBytes))) b.Resize(0, int32(len(msgBytes)))
if err := writer.WriteMessage(b); err != nil { if err := writer.WriteMessage(b); err != nil {
errors.LogInfoInner(context.Background(), err, "write IP answer") errors.LogInfoInner(context.Background(), err, "write IP answer")
timer.SetTimeout(0)
} }
} }
func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) { func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) error {
domainT := strings.TrimSuffix(domain, ".")
if domainT == "" {
return errors.New("empty domain name")
}
b := buf.New() b := buf.New()
rawBytes := b.Extend(buf.Size) rawBytes := b.Extend(buf.Size)
builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{ builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{
@@ -349,20 +369,22 @@ func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain stri
if err != nil { if err != nil {
errors.LogInfo(context.Background(), "unexpected domain ", domain, " when building reject message: ", err) errors.LogInfo(context.Background(), "unexpected domain ", domain, " when building reject message: ", err)
b.Release() b.Release()
return return err
} }
msgBytes, err := builder.Finish() msgBytes, err := builder.Finish()
if err != nil { if err != nil {
errors.LogInfoInner(context.Background(), err, "pack reject message") errors.LogInfoInner(context.Background(), err, "pack reject message")
b.Release() b.Release()
return return err
} }
b.Resize(0, int32(len(msgBytes))) b.Resize(0, int32(len(msgBytes)))
if err := writer.WriteMessage(b); err != nil { if err := writer.WriteMessage(b); err != nil {
errors.LogInfoInner(context.Background(), err, "write reject answer") errors.LogInfoInner(context.Background(), err, "write reject answer")
return err
} }
return nil
} }
type outboundConn struct { type outboundConn struct {
@@ -371,6 +393,7 @@ type outboundConn struct {
conn net.Conn conn net.Conn
connReady chan struct{} connReady chan struct{}
closed bool
} }
func (c *outboundConn) dial() error { func (c *outboundConn) dial() error {
@@ -385,12 +408,16 @@ func (c *outboundConn) dial() error {
func (c *outboundConn) Write(b []byte) (int, error) { func (c *outboundConn) Write(b []byte) (int, error) {
c.access.Lock() c.access.Lock()
if c.closed {
c.access.Unlock()
return 0, errors.New("outbound connection closed")
}
if c.conn == nil { if c.conn == nil {
if err := c.dial(); err != nil { if err := c.dial(); err != nil {
c.access.Unlock() c.access.Unlock()
errors.LogWarningInner(context.Background(), err, "failed to dial outbound connection") errors.LogWarningInner(context.Background(), err, "failed to dial outbound connection")
return len(b), nil return 0, err
} }
} }
@@ -400,24 +427,27 @@ func (c *outboundConn) Write(b []byte) (int, error) {
} }
func (c *outboundConn) Read(b []byte) (int, error) { func (c *outboundConn) Read(b []byte) (int, error) {
var conn net.Conn
c.access.Lock() c.access.Lock()
conn = c.conn if c.closed {
c.access.Unlock() c.access.Unlock()
return 0, io.EOF
}
if conn == nil { if c.conn == nil {
c.access.Unlock()
_, open := <-c.connReady _, open := <-c.connReady
if !open { if !open {
return 0, io.EOF return 0, io.EOF
} }
conn = c.conn return c.conn.Read(b)
} }
c.access.Unlock()
return conn.Read(b) return c.conn.Read(b)
} }
func (c *outboundConn) Close() error { func (c *outboundConn) Close() error {
c.access.Lock() c.access.Lock()
c.closed = true
close(c.connReady) close(c.connReady)
if c.conn != nil { if c.conn != nil {
c.conn.Close() c.conn.Close()
+1 -1
View File
@@ -182,7 +182,7 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
} }
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{ if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
Reader: &buf.TimeoutWrapperReader{Reader: reader}, Reader: reader,
Writer: writer}, Writer: writer},
); err != nil { ); err != nil {
return errors.New("failed to dispatch request").Base(err) return errors.New("failed to dispatch request").Base(err)
+2 -21
View File
@@ -26,7 +26,6 @@ import (
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
) )
var useSplice bool var useSplice bool
@@ -212,17 +211,15 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
responseDone := func() error { responseDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
if destination.Network == net.Network_TCP { if destination.Network == net.Network_TCP && useSplice && proxy.IsRAWTransportWithoutSecurity(conn) { // it would be tls conn in special use case of MITM, we need to let link handle traffic
var writeConn net.Conn var writeConn net.Conn
var inTimer *signal.ActivityTimer var inTimer *signal.ActivityTimer
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil && useSplice { if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil {
writeConn = inbound.Conn writeConn = inbound.Conn
inTimer = inbound.Timer inTimer = inbound.Timer
} }
if !isTLSConn(conn) { // it would be tls conn in special use case of MITM, we need to let link handle traffic
return proxy.CopyRawConnIfExist(ctx, conn, writeConn, link.Writer, timer, inTimer) return proxy.CopyRawConnIfExist(ctx, conn, writeConn, link.Writer, timer, inTimer)
} }
}
var reader buf.Reader var reader buf.Reader
if destination.Network == net.Network_TCP { if destination.Network == net.Network_TCP {
reader = buf.NewReader(conn) reader = buf.NewReader(conn)
@@ -246,22 +243,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return nil return nil
} }
func isTLSConn(conn stat.Connection) bool {
if conn != nil {
statConn, ok := conn.(*stat.CounterConnection)
if ok {
conn = statConn.Connection
}
if _, ok := conn.(*tls.Conn); ok {
return true
}
if _, ok := conn.(*tls.UConn); ok {
return true
}
}
return false
}
func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination) buf.Reader { func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination) buf.Reader {
iConn := conn iConn := conn
statConn, ok := iConn.(*stat.CounterConnection) statConn, ok := iConn.(*stat.CounterConnection)
+2 -2
View File
@@ -96,7 +96,7 @@ func (s *Server) ProcessWithFirstbyte(ctx context.Context, network net.Network,
inbound.User = &protocol.MemoryUser{ inbound.User = &protocol.MemoryUser{
Level: s.config.UserLevel, Level: s.config.UserLevel,
} }
if !proxy.IsRAWTransport(conn) { if !proxy.IsRAWTransportWithoutSecurity(conn) {
inbound.CanSpliceCopy = 3 inbound.CanSpliceCopy = 3
} }
var reader *bufio.Reader var reader *bufio.Reader
@@ -193,7 +193,7 @@ func (s *Server) handleConnect(ctx context.Context, _ *http.Request, buffer *buf
inbound.CanSpliceCopy = 1 inbound.CanSpliceCopy = 1
} }
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{ if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
Reader: &buf.TimeoutWrapperReader{Reader: reader}, Reader: reader,
Writer: buf.NewWriter(conn)}, Writer: buf.NewWriter(conn)},
); err != nil { ); err != nil {
return errors.New("failed to dispatch request").Base(err) return errors.New("failed to dispatch request").Base(err)
+87 -14
View File
@@ -177,20 +177,34 @@ type VisionReader struct {
trafficState *TrafficState trafficState *TrafficState
ctx context.Context ctx context.Context
isUplink bool isUplink bool
conn net.Conn
input *bytes.Reader
rawInput *bytes.Buffer
ob *session.Outbound
// internal
directReadCounter stats.Counter
} }
func NewVisionReader(reader buf.Reader, state *TrafficState, isUplink bool, context context.Context) *VisionReader { func NewVisionReader(reader buf.Reader, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, ob *session.Outbound) *VisionReader {
return &VisionReader{ return &VisionReader{
Reader: reader, Reader: reader,
trafficState: state, trafficState: trafficState,
ctx: context, ctx: ctx,
isUplink: isUplink, isUplink: isUplink,
conn: conn,
input: input,
rawInput: rawInput,
ob: ob,
} }
} }
func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) { func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
buffer, err := w.Reader.ReadMultiBuffer() buffer, err := w.Reader.ReadMultiBuffer()
if !buffer.IsEmpty() { if buffer.IsEmpty() {
return buffer, err
}
var withinPaddingBuffers *bool var withinPaddingBuffers *bool
var remainingContent *int32 var remainingContent *int32
var remainingPadding *int32 var remainingPadding *int32
@@ -210,6 +224,13 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
switchToDirectCopy = &w.trafficState.Outbound.DownlinkReaderDirectCopy switchToDirectCopy = &w.trafficState.Outbound.DownlinkReaderDirectCopy
} }
if *switchToDirectCopy {
if w.directReadCounter != nil {
w.directReadCounter.Add(int64(buffer.Len()))
}
return buffer, err
}
if *withinPaddingBuffers || w.trafficState.NumberOfPacketToFilter > 0 { if *withinPaddingBuffers || w.trafficState.NumberOfPacketToFilter > 0 {
mb2 := make(buf.MultiBuffer, 0, len(buffer)) mb2 := make(buf.MultiBuffer, 0, len(buffer))
for _, b := range buffer { for _, b := range buffer {
@@ -233,6 +254,31 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
if w.trafficState.NumberOfPacketToFilter > 0 { if w.trafficState.NumberOfPacketToFilter > 0 {
XtlsFilterTls(buffer, w.trafficState, w.ctx) XtlsFilterTls(buffer, w.trafficState, w.ctx)
} }
if *switchToDirectCopy {
// XTLS Vision processes TLS-like conn's input and rawInput
if inputBuffer, err := buf.ReadFrom(w.input); err == nil && !inputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, inputBuffer)
}
if rawInputBuffer, err := buf.ReadFrom(w.rawInput); err == nil && !rawInputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, rawInputBuffer)
}
*w.input = bytes.Reader{} // release memory
w.input = nil
*w.rawInput = bytes.Buffer{} // release memory
w.rawInput = nil
if inbound := session.InboundFromContext(w.ctx); inbound != nil && inbound.Conn != nil {
if w.isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
if !w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can have more than one ob
w.ob.CanSpliceCopy = 1
}
}
readerConn, readCounter, _ := UnwrapRawConn(w.conn)
w.directReadCounter = readCounter
w.Reader = buf.NewReader(readerConn)
} }
return buffer, err return buffer, err
} }
@@ -243,26 +289,30 @@ type VisionWriter struct {
buf.Writer buf.Writer
trafficState *TrafficState trafficState *TrafficState
ctx context.Context ctx context.Context
writeOnceUserUUID []byte
isUplink bool isUplink bool
conn net.Conn
ob *session.Outbound
// internal
writeOnceUserUUID []byte
directWriteCounter stats.Counter
} }
func NewVisionWriter(writer buf.Writer, state *TrafficState, isUplink bool, context context.Context) *VisionWriter { func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, ob *session.Outbound) *VisionWriter {
w := make([]byte, len(state.UserUUID)) w := make([]byte, len(trafficState.UserUUID))
copy(w, state.UserUUID) copy(w, trafficState.UserUUID)
return &VisionWriter{ return &VisionWriter{
Writer: writer, Writer: writer,
trafficState: state, trafficState: trafficState,
ctx: context, ctx: ctx,
writeOnceUserUUID: w, writeOnceUserUUID: w,
isUplink: isUplink, isUplink: isUplink,
conn: conn,
ob: ob,
} }
} }
func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if w.trafficState.NumberOfPacketToFilter > 0 {
XtlsFilterTls(mb, w.trafficState, w.ctx)
}
var isPadding *bool var isPadding *bool
var switchToDirectCopy *bool var switchToDirectCopy *bool
if w.isUplink { if w.isUplink {
@@ -272,6 +322,29 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
isPadding = &w.trafficState.Inbound.IsPadding isPadding = &w.trafficState.Inbound.IsPadding
switchToDirectCopy = &w.trafficState.Inbound.DownlinkWriterDirectCopy switchToDirectCopy = &w.trafficState.Inbound.DownlinkWriterDirectCopy
} }
if *switchToDirectCopy {
if inbound := session.InboundFromContext(w.ctx); inbound != nil {
if !w.isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 {
w.ob.CanSpliceCopy = 1
}
}
rawConn, _, writerCounter := UnwrapRawConn(w.conn)
w.Writer = buf.NewWriter(rawConn)
w.directWriteCounter = writerCounter
*switchToDirectCopy = false
}
if !mb.IsEmpty() && w.directWriteCounter != nil {
w.directWriteCounter.Add(int64(mb.Len()))
}
if w.trafficState.NumberOfPacketToFilter > 0 {
XtlsFilterTls(mb, w.trafficState, w.ctx)
}
if *isPadding { if *isPadding {
if len(mb) == 1 && mb[0] == nil { 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) // we do a long padding to hide vless header
@@ -652,7 +725,7 @@ func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer sign
return nil return nil
} }
func IsRAWTransport(conn stat.Connection) bool { func IsRAWTransportWithoutSecurity(conn stat.Connection) bool {
iConn := conn iConn := conn
if statConn, ok := iConn.(*stat.CounterConnection); ok { if statConn, ok := iConn.(*stat.CounterConnection); ok {
iConn = statConn.Connection iConn = statConn.Connection
+2 -2
View File
@@ -75,7 +75,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
inbound.User = &protocol.MemoryUser{ inbound.User = &protocol.MemoryUser{
Level: s.config.UserLevel, Level: s.config.UserLevel,
} }
if !proxy.IsRAWTransport(conn) { if !proxy.IsRAWTransportWithoutSecurity(conn) {
inbound.CanSpliceCopy = 3 inbound.CanSpliceCopy = 3
} }
@@ -161,7 +161,7 @@ func (s *Server) processTCP(ctx context.Context, conn stat.Connection, dispatche
inbound.CanSpliceCopy = 1 inbound.CanSpliceCopy = 1
} }
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{ if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
Reader: &buf.TimeoutWrapperReader{Reader: reader}, Reader: reader,
Writer: buf.NewWriter(conn)}, Writer: buf.NewWriter(conn)},
); err != nil { ); err != nil {
return errors.New("failed to dispatch request").Base(err) return errors.New("failed to dispatch request").Base(err)
+3
View File
@@ -20,6 +20,7 @@ func (a *Account) AsAccount() (protocol.Account, error) {
Encryption: a.Encryption, // needs parser here? Encryption: a.Encryption, // needs parser here?
XorMode: a.XorMode, XorMode: a.XorMode,
Seconds: a.Seconds, Seconds: a.Seconds,
Padding: a.Padding,
}, nil }, nil
} }
@@ -33,6 +34,7 @@ type MemoryAccount struct {
Encryption string Encryption string
XorMode uint32 XorMode uint32
Seconds uint32 Seconds uint32
Padding string
} }
// Equals implements protocol.Account.Equals(). // Equals implements protocol.Account.Equals().
@@ -51,5 +53,6 @@ func (a *MemoryAccount) ToProto() proto.Message {
Encryption: a.Encryption, Encryption: a.Encryption,
XorMode: a.XorMode, XorMode: a.XorMode,
Seconds: a.Seconds, Seconds: a.Seconds,
Padding: a.Padding,
} }
} }
+17 -7
View File
@@ -32,6 +32,7 @@ type Account struct {
Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"` Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"`
XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,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"` 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() { func (x *Account) Reset() {
@@ -99,12 +100,19 @@ func (x *Account) GetSeconds() uint32 {
return 0 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 protoreflect.FileDescriptor
var file_proxy_vless_account_proto_rawDesc = []byte{ var file_proxy_vless_account_proto_rawDesc = []byte{
0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63, 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, 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, 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, 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, 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, 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, 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, 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, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01,
0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x52, 0x0a, 0x14, 0x63,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c,
0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 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 ( var (
+1
View File
@@ -15,4 +15,5 @@ message Account {
string encryption = 3; string encryption = 3;
uint32 xorMode = 4; uint32 xorMode = 4;
uint32 seconds = 5; uint32 seconds = 5;
string padding = 6;
} }
+6 -5
View File
@@ -3,10 +3,12 @@ package encoding
import ( import (
"context" "context"
"io" "io"
"net"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
@@ -61,15 +63,14 @@ func DecodeHeaderAddons(buffer *buf.Buffer, reader io.Reader) (*Addons, error) {
} }
// EncodeBodyAddons returns a Writer that auto-encrypt content written by caller. // EncodeBodyAddons returns a Writer that auto-encrypt content written by caller.
func EncodeBodyAddons(writer io.Writer, request *protocol.RequestHeader, requestAddons *Addons, state *proxy.TrafficState, isUplink bool, context context.Context) buf.Writer { func EncodeBodyAddons(writer buf.Writer, request *protocol.RequestHeader, requestAddons *Addons, state *proxy.TrafficState, isUplink bool, context context.Context, conn net.Conn, ob *session.Outbound) buf.Writer {
if request.Command == protocol.RequestCommandUDP { if request.Command == protocol.RequestCommandUDP {
return NewMultiLengthPacketWriter(writer.(buf.Writer)) return NewMultiLengthPacketWriter(writer)
} }
w := buf.NewWriter(writer)
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
w = proxy.NewVisionWriter(w, state, isUplink, context) return proxy.NewVisionWriter(writer, state, isUplink, context, conn, ob)
} }
return w return writer
} }
// DecodeBodyAddons returns a Reader from which caller can fetch decrypted body. // DecodeBodyAddons returns a Reader from which caller can fetch decrypted body.
+2 -73
View File
@@ -1,7 +1,6 @@
package encoding package encoding
import ( import (
"bytes"
"context" "context"
"io" "io"
@@ -11,7 +10,6 @@ import (
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless"
) )
@@ -171,8 +169,8 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A
return responseAddons, nil return responseAddons, nil
} }
// XtlsRead filter and read xtls protocol // XtlsRead can switch to splice copy
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, trafficState *proxy.TrafficState, isUplink bool, ctx context.Context) error {
err := func() error { err := func() error {
for { for {
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
@@ -181,80 +179,11 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer,
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil { if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil {
writerConn = inbound.Conn writerConn = inbound.Conn
inTimer = inbound.Timer inTimer = inbound.Timer
if isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
if !isUplink && ob != nil && ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can change
ob.CanSpliceCopy = 1
}
} }
return proxy.CopyRawConnIfExist(ctx, conn, writerConn, writer, timer, inTimer) return proxy.CopyRawConnIfExist(ctx, conn, writerConn, writer, timer, inTimer)
} }
buffer, err := reader.ReadMultiBuffer() buffer, err := reader.ReadMultiBuffer()
if !buffer.IsEmpty() { 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)
}
}
}
}
if werr := writer.WriteMultiBuffer(buffer); werr != nil {
return werr
}
}
if err != nil {
return err
}
}
}()
if err != nil && errors.Cause(err) != io.EOF {
return err
}
return nil
}
// XtlsWrite filter and write xtls protocol
func XtlsWrite(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn net.Conn, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error {
err := func() error {
var ct stats.Counter
for {
buffer, err := reader.ReadMultiBuffer()
if isUplink && trafficState.Outbound.UplinkWriterDirectCopy || !isUplink && trafficState.Inbound.DownlinkWriterDirectCopy {
if inbound := session.InboundFromContext(ctx); inbound != nil {
if !isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
if isUplink && ob != nil && ob.CanSpliceCopy == 2 {
ob.CanSpliceCopy = 1
}
}
rawConn, _, writerCounter := proxy.UnwrapRawConn(conn)
writer = buf.NewWriter(rawConn)
ct = writerCounter
if isUplink {
trafficState.Outbound.UplinkWriterDirectCopy = false
} else {
trafficState.Inbound.DownlinkWriterDirectCopy = false
}
}
if !buffer.IsEmpty() {
if ct != nil {
ct.Add(int64(buffer.Len()))
}
timer.Update() timer.Update()
if werr := writer.WriteMultiBuffer(buffer); werr != nil { if werr := writer.WriteMultiBuffer(buffer); werr != nil {
return werr return werr
+17 -10
View File
@@ -10,7 +10,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"lukechampine.com/blake3" "lukechampine.com/blake3"
@@ -23,6 +22,8 @@ type ClientInstance struct {
RelaysLength int RelaysLength int
XorMode uint32 XorMode uint32
Seconds uint32 Seconds uint32
PaddingLens [][3]int
PaddingGaps [][3]int
RWLock sync.RWMutex RWLock sync.RWMutex
Expire time.Time Expire time.Time
@@ -30,15 +31,13 @@ type ClientInstance struct {
Ticket []byte 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 { if i.NfsPKeys != nil {
err = errors.New("already initialized") return errors.New("already initialized")
return
} }
l := len(nfsPKeysBytes) l := len(nfsPKeysBytes)
if l == 0 { if l == 0 {
err = errors.New("empty nfsPKeysBytes") return errors.New("empty nfsPKeysBytes")
return
} }
i.NfsPKeys = make([]any, l) i.NfsPKeys = make([]any, l)
i.NfsPKeysBytes = nfsPKeysBytes i.NfsPKeysBytes = nfsPKeysBytes
@@ -60,7 +59,7 @@ func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (
i.RelaysLength -= 32 i.RelaysLength -= 32
i.XorMode = xorMode i.XorMode = xorMode
i.Seconds = seconds i.Seconds = seconds
return return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
} }
func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { 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 ivAndRealysLength := 16 + i.RelaysLength
pfsKeyExchangeLength := 18 + 1184 + 32 + 16 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) clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength)
iv := clientHello[:16] 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[:0], nil, EncodeLength(paddingLength-18), nil)
nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
if _, err := conn.Write(clientHello); err != nil { 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 return nil, err
} }
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control clientHello = clientHello[l:]
}
if len(paddingGaps) > i {
time.Sleep(paddingGaps[i])
}
}
encryptedPfsPublicKey := make([]byte, 1088+32+16) encryptedPfsPublicKey := make([]byte, 1088+32+16)
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
+76 -15
View File
@@ -7,10 +7,12 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"lukechampine.com/blake3" "lukechampine.com/blake3"
@@ -31,15 +33,14 @@ type CommonConn struct {
AEAD *AEAD AEAD *AEAD
PeerAEAD *AEAD PeerAEAD *AEAD
PeerPadding []byte PeerPadding []byte
PeerInBytes []byte rawInput bytes.Buffer
PeerCache []byte input bytes.Reader
} }
func NewCommonConn(conn net.Conn, useAES bool) *CommonConn { func NewCommonConn(conn net.Conn, useAES bool) *CommonConn {
return &CommonConn{ return &CommonConn{
Conn: conn, Conn: conn,
UseAES: useAES, UseAES: useAES,
PeerInBytes: make([]byte, 5+17000), // no need to use sync.Pool, because we are always reading
} }
} }
@@ -99,16 +100,14 @@ func (c *CommonConn) Read(b []byte) (int, error) {
} }
c.PeerPadding = nil c.PeerPadding = nil
} }
if len(c.PeerCache) > 0 { if c.input.Len() > 0 {
n := copy(b, c.PeerCache) return c.input.Read(b)
c.PeerCache = c.PeerCache[n:]
return n, nil
} }
peerHeader := c.PeerInBytes[:5] peerHeader := [5]byte{}
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil {
return 0, err return 0, err
} }
l, err := DecodeHeader(c.PeerInBytes[:5]) // l: 17~17000 l, err := DecodeHeader(peerHeader[:]) // l: 17~17000
if err != nil { if err != nil {
if c.Client != nil && strings.Contains(err.Error(), "invalid header: ") { // client's 0-RTT if c.Client != nil && strings.Contains(err.Error(), "invalid header: ") { // client's 0-RTT
c.Client.RWLock.Lock() c.Client.RWLock.Lock()
@@ -121,7 +120,10 @@ func (c *CommonConn) Read(b []byte) (int, error) {
return 0, err return 0, err
} }
c.Client = nil 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 { if _, err := io.ReadFull(c.Conn, peerData); err != nil {
return 0, err return 0, err
} }
@@ -131,9 +133,9 @@ func (c *CommonConn) Read(b []byte) (int, error) {
} }
var newAEAD *AEAD var newAEAD *AEAD
if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) { 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 { if newAEAD != nil {
c.PeerAEAD = newAEAD c.PeerAEAD = newAEAD
} }
@@ -141,7 +143,7 @@ func (c *CommonConn) Read(b []byte) (int, error) {
return 0, err return 0, err
} }
if len(dst) > len(b) { if len(dst) > len(b) {
c.PeerCache = dst[copy(b, dst):] c.input.Reset(dst[copy(b, dst):])
dst = b // for len(dst) dst = b // for len(dst)
} }
return len(dst), nil return len(dst), nil
@@ -213,7 +215,66 @@ func DecodeHeader(h []byte) (l int, err error) {
l = 0 l = 0
} }
if l < 17 || l > 17000 { // TODO: TLSv1.3 max length 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 *[][3]int) (err error) {
if padding == "" {
return
}
maxLen := 0
for i, s := range strings.Split(padding, ".") {
x := strings.Split(s, "-")
if len(x) < 3 || x[0] == "" || x[1] == "" || x[2] == "" {
return errors.New("invalid padding lenth/gap parameter: " + s)
}
y := [3]int{}
if y[0], err = strconv.Atoi(x[0]); err != nil {
return
}
if y[1], err = strconv.Atoi(x[1]); err != nil {
return
}
if y[2], err = strconv.Atoi(x[2]); err != nil {
return
}
if i == 0 && (y[0] < 100 || y[1] < 18+17 || y[2] < 18+17) {
return errors.New("first padding length must not be smaller than 35")
}
if i%2 == 0 {
*paddingLens = append(*paddingLens, y)
maxLen += max(y[1], y[2])
} else {
*paddingGaps = append(*paddingGaps, y)
}
}
if maxLen > 18+65535 {
return errors.New("total padding length must not be larger than 65553")
}
return
}
func CreatPadding(paddingLens, paddingGaps [][3]int) (length int, lens []int, gaps []time.Duration) {
if len(paddingLens) == 0 {
paddingLens = [][3]int{{100, 111, 1111}, {50, 0, 3333}}
paddingGaps = [][3]int{{75, 0, 111}}
}
for _, y := range paddingLens {
l := 0
if y[0] >= int(crypto.RandBetween(0, 100)) {
l = int(crypto.RandBetween(int64(y[1]), int64(y[2])))
}
lens = append(lens, l)
length += l
}
for _, y := range paddingGaps {
g := 0
if y[0] >= int(crypto.RandBetween(0, 100)) {
g = int(crypto.RandBetween(int64(y[1]), int64(y[2])))
}
gaps = append(gaps, time.Duration(g)*time.Millisecond)
} }
return return
} }
+73 -32
View File
@@ -18,7 +18,6 @@ import (
) )
type ServerSession struct { type ServerSession struct {
Expire time.Time
PfsKey []byte PfsKey []byte
NfsKeys sync.Map NfsKeys sync.Map
} }
@@ -29,22 +28,25 @@ type ServerInstance struct {
Hash32s [][32]byte Hash32s [][32]byte
RelaysLength int RelaysLength int
XorMode uint32 XorMode uint32
Seconds uint32 SecondsFrom int64
SecondsTo int64
PaddingLens [][3]int
PaddingGaps [][3]int
RWLock sync.RWMutex RWLock sync.RWMutex
Sessions map[[16]byte]*ServerSession
Closed bool Closed bool
Lasts map[int64][16]byte
Tickets [][16]byte
Sessions map[[16]byte]*ServerSession
} }
func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) { func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode uint32, secondsFrom, secondsTo int64, padding string) (err error) {
if i.NfsSKeys != nil { if i.NfsSKeys != nil {
err = errors.New("already initialized") return errors.New("already initialized")
return
} }
l := len(nfsSKeysBytes) l := len(nfsSKeysBytes)
if l == 0 { if l == 0 {
err = errors.New("empty nfsSKeysBytes") return errors.New("empty nfsSKeysBytes")
return
} }
i.NfsSKeys = make([]any, l) i.NfsSKeys = make([]any, l)
i.NfsPKeysBytes = make([][]byte, l) i.NfsPKeysBytes = make([][]byte, l)
@@ -67,8 +69,15 @@ func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (
} }
i.RelaysLength -= 32 i.RelaysLength -= 32
i.XorMode = xorMode i.XorMode = xorMode
if seconds > 0 { i.SecondsFrom = secondsFrom
i.Seconds = seconds i.SecondsTo = secondsTo
err = ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
if err != nil {
return
}
if i.SecondsFrom > 0 || i.SecondsTo > 0 {
i.Lasts = make(map[int64][16]byte)
i.Tickets = make([][16]byte, 0, 1024)
i.Sessions = make(map[[16]byte]*ServerSession) i.Sessions = make(map[[16]byte]*ServerSession)
go func() { go func() {
for { for {
@@ -78,10 +87,17 @@ func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (
i.RWLock.Unlock() i.RWLock.Unlock()
return return
} }
now := time.Now() minute := time.Now().Unix() / 60
for ticket, session := range i.Sessions { last := i.Lasts[minute]
if now.After(session.Expire) { delete(i.Lasts, minute)
delete(i.Lasts, minute-1) // for insurance
if last != [16]byte{} {
for j, ticket := range i.Tickets {
delete(i.Sessions, ticket) delete(i.Sessions, ticket)
if ticket == last {
i.Tickets = i.Tickets[j+1:]
break
}
} }
} }
i.RWLock.Unlock() i.RWLock.Unlock()
@@ -98,7 +114,7 @@ func (i *ServerInstance) Close() (err error) {
return 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 { if i.NfsSKeys == nil {
return nil, errors.New("uninitialized") return nil, errors.New("uninitialized")
} }
@@ -108,6 +124,9 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
if _, err := io.ReadFull(conn, ivAndRelays); err != nil { if _, err := io.ReadFull(conn, ivAndRelays); err != nil {
return nil, err return nil, err
} }
if fallback != nil {
*fallback = append(*fallback, ivAndRelays...)
}
iv := ivAndRelays[:16] iv := ivAndRelays[:16]
relays := ivAndRelays[16:] relays := ivAndRelays[16:]
var nfsKey []byte var nfsKey []byte
@@ -121,13 +140,16 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
index = 1088 index = 1088
} }
if i.XorMode > 0 { if i.XorMode > 0 {
NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // we don't use buggy elligator, because we have PSK :) NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // we don't use buggy elligator2, because we have PSK :)
} }
if k, ok := k.(*ecdh.PrivateKey); ok { if k, ok := k.(*ecdh.PrivateKey); ok {
publicKey, err := ecdh.X25519().NewPublicKey(relays[:index]) publicKey, err := ecdh.X25519().NewPublicKey(relays[:index])
if err != nil { if err != nil {
return nil, err return nil, err
} }
if publicKey.Bytes()[31] > 127 { // we just don't want the observer can change even one bit without breaking the connection, though it has nothing to do with security
return nil, errors.New("the highest bit of the last byte of the peer-sent X25519 public key is not 0")
}
nfsKey, err = k.ECDH(publicKey) nfsKey, err = k.ECDH(publicKey)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -157,6 +179,9 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
if _, err := io.ReadFull(conn, encryptedLength); err != nil { if _, err := io.ReadFull(conn, encryptedLength); err != nil {
return nil, err return nil, err
} }
if fallback != nil {
*fallback = append(*fallback, encryptedLength...)
}
decryptedLength := make([]byte, 2) decryptedLength := make([]byte, 2)
if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil { if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil {
c.UseAES = !c.UseAES c.UseAES = !c.UseAES
@@ -165,10 +190,13 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
return nil, err return nil, err
} }
} }
if fallback != nil {
*fallback = nil
}
length := DecodeLength(decryptedLength) length := DecodeLength(decryptedLength)
if length == 32 { if length == 32 {
if i.Seconds == 0 { if i.SecondsFrom == 0 && i.SecondsTo == 0 {
return nil, errors.New("0-RTT is not allowed") return nil, errors.New("0-RTT is not allowed")
} }
encryptedTicket := make([]byte, 32) encryptedTicket := make([]byte, 32)
@@ -183,7 +211,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
s := i.Sessions[[16]byte(ticket)] s := i.Sessions[[16]byte(ticket)]
i.RWLock.RUnlock() i.RWLock.RUnlock()
if s == nil { 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 var err error
for err == nil { for err == nil {
rand.Read(noises) rand.Read(noises)
@@ -237,32 +265,45 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
c.UnitedKey = append(pfsKey, nfsKey...) c.UnitedKey = append(pfsKey, nfsKey...)
c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES) c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)
c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES) c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES)
ticket := make([]byte, 16)
rand.Read(ticket) ticket := [16]byte{}
copy(ticket, EncodeLength(int(i.Seconds*4/5))) rand.Read(ticket[:])
var seconds int64
if i.SecondsTo == 0 {
seconds = i.SecondsFrom * crypto.RandBetween(50, 100) / 100
} else {
seconds = crypto.RandBetween(i.SecondsFrom, i.SecondsTo)
}
copy(ticket[:], EncodeLength(int(seconds)))
if seconds > 0 {
i.RWLock.Lock()
i.Lasts[(time.Now().Unix()+max(i.SecondsFrom, i.SecondsTo))/60+2] = ticket
i.Tickets = append(i.Tickets, ticket)
i.Sessions[ticket] = &ServerSession{PfsKey: pfsKey}
i.RWLock.Unlock()
}
pfsKeyExchangeLength := 1088 + 32 + 16 pfsKeyExchangeLength := 1088 + 32 + 16
encryptedTicketLength := 32 encryptedTicketLength := 32
paddingLength := int(crypto.RandBetween(100, 1000)) paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)
serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength) serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)
nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil) nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)
c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil) c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket[:], nil)
padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:] padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]
c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
if _, err := conn.Write(serverHello); err != 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 return nil, err
} }
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control serverHello = serverHello[l:]
}
if i.Seconds > 0 { if len(paddingGaps) > i {
i.RWLock.Lock() time.Sleep(paddingGaps[i])
i.Sessions[[16]byte(ticket)] = &ServerSession{
Expire: time.Now().Add(time.Duration(i.Seconds) * time.Second),
PfsKey: pfsKey,
} }
i.RWLock.Unlock()
} }
// important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern // important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern
@@ -281,7 +322,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
} }
if i.XorMode == 2 { if i.XorMode == 2 {
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, ticket), NewCTR(c.UnitedKey, iv), 0, 0) c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, ticket[:]), NewCTR(c.UnitedKey, iv), 0, 0)
} }
return c, nil return c, nil
} }
+34 -14
View File
@@ -115,7 +115,9 @@ type Config struct {
Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"` Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"` Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"`
XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,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"` SecondsFrom int64 `protobuf:"varint,5,opt,name=seconds_from,json=secondsFrom,proto3" json:"seconds_from,omitempty"`
SecondsTo int64 `protobuf:"varint,6,opt,name=seconds_to,json=secondsTo,proto3" json:"seconds_to,omitempty"`
Padding string `protobuf:"bytes,7,opt,name=padding,proto3" json:"padding,omitempty"`
} }
func (x *Config) Reset() { func (x *Config) Reset() {
@@ -176,13 +178,27 @@ func (x *Config) GetXorMode() uint32 {
return 0 return 0
} }
func (x *Config) GetSeconds() uint32 { func (x *Config) GetSecondsFrom() int64 {
if x != nil { if x != nil {
return x.Seconds return x.SecondsFrom
} }
return 0 return 0
} }
func (x *Config) GetSecondsTo() int64 {
if x != nil {
return x.SecondsTo
}
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 protoreflect.FileDescriptor
var file_proxy_vless_inbound_config_proto_rawDesc = []byte{ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{
@@ -199,7 +215,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, 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, 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, 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, 0x96, 0x02,
0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 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, 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, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
@@ -211,16 +227,20 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{
0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 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, 0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65,
0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03,
0x6f, 0x6e, 0x64, 0x73, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x52, 0x0b, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x1d, 0x0a,
0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x0a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x74, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28,
0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x03, 0x52, 0x09, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x54, 0x6f, 0x12, 0x18, 0x0a, 0x07,
0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70,
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69,
0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 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 ( var (
+3 -1
View File
@@ -23,5 +23,7 @@ message Config {
string decryption = 3; string decryption = 3;
uint32 xorMode = 4; uint32 xorMode = 4;
uint32 seconds = 5; int64 seconds_from = 5;
int64 seconds_to = 6;
string padding = 7;
} }
+18 -85
View File
@@ -31,6 +31,7 @@ import (
"github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless"
"github.com/xtls/xray-core/proxy/vless/encoding" "github.com/xtls/xray-core/proxy/vless/encoding"
"github.com/xtls/xray-core/proxy/vless/encryption" "github.com/xtls/xray-core/proxy/vless/encryption"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/reality"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/tls"
@@ -92,7 +93,7 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val
nfsSKeysBytes = append(nfsSKeysBytes, b) nfsSKeysBytes = append(nfsSKeysBytes, b)
} }
handler.decryption = &encryption.ServerInstance{} 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.SecondsFrom, config.SecondsTo, config.Padding); err != nil {
return nil, errors.New("failed to use decryption").Base(err).AtError() return nil, errors.New("failed to use decryption").Base(err).AtError()
} }
} }
@@ -220,8 +221,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
if h.decryption != nil { if h.decryption != nil {
var err error var err error
connection, err = h.decryption.Handshake(connection) if connection, err = h.decryption.Handshake(connection, nil); err != nil {
if err != nil {
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo()
} }
} }
@@ -491,7 +491,6 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
// Flow: requestAddons.Flow, // Flow: requestAddons.Flow,
} }
var peerCache *[]byte
var input *bytes.Reader var input *bytes.Reader
var rawInput *bytes.Buffer var rawInput *bytes.Buffer
switch requestAddons.Flow { switch requestAddons.Flow {
@@ -504,16 +503,15 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
case protocol.RequestCommandMux: case protocol.RequestCommandMux:
fallthrough // we will break Mux connections that contain TCP requests fallthrough // we will break Mux connections that contain TCP requests
case protocol.RequestCommandTCP: 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 t reflect.Type
var p uintptr 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.IsRAWTransportWithoutSecurity(iConn) {
inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport / another securityConn should not be penetrated
}
t = reflect.TypeOf(commonConn).Elem()
p = uintptr(unsafe.Pointer(commonConn))
} else if tlsConn, ok := iConn.(*tls.Conn); ok {
if tlsConn.ConnectionState().Version != gotls.VersionTLS13 { if tlsConn.ConnectionState().Version != gotls.VersionTLS13 {
return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning() return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning()
} }
@@ -554,89 +552,24 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
ctx = session.ContextWithAllowedNetwork(ctx, net.Network_UDP) ctx = session.ContextWithAllowedNetwork(ctx, net.Network_UDP)
} }
sessionPolicy = h.policyManager.ForLevel(request.User.Level)
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
inbound.Timer = timer
ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
link, err := dispatcher.Dispatch(ctx, request.Destination())
if err != nil {
return errors.New("failed to dispatch request to ", request.Destination()).Base(err).AtWarning()
}
serverReader := link.Reader // .(*pipe.Reader)
serverWriter := link.Writer // .(*pipe.Writer)
trafficState := proxy.NewTrafficState(userSentID) trafficState := proxy.NewTrafficState(userSentID)
postRequest := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
// default: clientReader := reader
clientReader := encoding.DecodeBodyAddons(reader, request, requestAddons) clientReader := encoding.DecodeBodyAddons(reader, request, requestAddons)
var err error
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx, connection, input, rawInput, nil)
clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx1)
err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, peerCache, input, rawInput, trafficState, nil, true, ctx1)
} else {
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
} }
if err != nil {
return errors.New("failed to transfer request payload").Base(err).AtInfo()
}
return nil
}
getResponse := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
bufferWriter := buf.NewBufferedWriter(buf.NewWriter(connection)) bufferWriter := buf.NewBufferedWriter(buf.NewWriter(connection))
if err := encoding.EncodeResponseHeader(bufferWriter, request, responseAddons); err != nil { if err := encoding.EncodeResponseHeader(bufferWriter, request, responseAddons); err != nil {
return errors.New("failed to encode response header").Base(err).AtWarning() return errors.New("failed to encode response header").Base(err).AtWarning()
} }
clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, false, ctx, connection, nil)
bufferWriter.SetFlushNext()
// default: clientWriter := bufferWriter if err := dispatcher.DispatchLink(ctx, request.Destination(), &transport.Link{
clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, false, ctx) Reader: clientReader,
multiBuffer, err1 := serverReader.ReadMultiBuffer() Writer: clientWriter},
if err1 != nil { ); err != nil {
return err1 // ... return errors.New("failed to dispatch request").Base(err)
} }
if err := clientWriter.WriteMultiBuffer(multiBuffer); err != nil {
return err // ...
}
// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer
if err := bufferWriter.SetBuffered(false); err != nil {
return errors.New("failed to write A response payload").Base(err).AtWarning()
}
var err error
if requestAddons.Flow == vless.XRV {
err = encoding.XtlsWrite(serverReader, clientWriter, timer, connection, trafficState, nil, false, ctx)
} else {
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer
err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))
}
if err != nil {
return errors.New("failed to transfer response payload").Base(err).AtInfo()
}
// Indicates the end of response payload.
switch responseAddons.Flow {
default:
}
return nil
}
if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), getResponse); err != nil {
common.Interrupt(serverReader)
common.Interrupt(serverWriter)
return errors.New("connection ends").Base(err).AtInfo()
}
return nil return nil
} }
+13 -21
View File
@@ -77,7 +77,7 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
nfsPKeysBytes = append(nfsPKeysBytes, b) nfsPKeysBytes = append(nfsPKeysBytes, b)
} }
handler.encryption = &encryption.ClientInstance{} 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() 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 { if h.encryption != nil {
var err error var err error
conn, err = h.encryption.Handshake(conn) if conn, err = h.encryption.Handshake(conn); err != nil {
if err != nil {
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() 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, Flow: account.Flow,
} }
var peerCache *[]byte
var input *bytes.Reader var input *bytes.Reader
var rawInput *bytes.Buffer var rawInput *bytes.Buffer
allowUDP443 := false allowUDP443 := false
@@ -165,16 +163,15 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
case protocol.RequestCommandMux: case protocol.RequestCommandMux:
fallthrough // let server break Mux connections that contain TCP requests fallthrough // let server break Mux connections that contain TCP requests
case protocol.RequestCommandTCP: 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 t reflect.Type
var p uintptr 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.IsRAWTransportWithoutSecurity(iConn) {
ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport / another securityConn should not be penetrated
}
t = reflect.TypeOf(commonConn).Elem()
p = uintptr(unsafe.Pointer(commonConn))
} else if tlsConn, ok := iConn.(*tls.Conn); ok {
t = reflect.TypeOf(tlsConn.Conn).Elem() t = reflect.TypeOf(tlsConn.Conn).Elem()
p = uintptr(unsafe.Pointer(tlsConn.Conn)) p = uintptr(unsafe.Pointer(tlsConn.Conn))
} else if utlsConn, ok := iConn.(*tls.UConn); ok { } else if utlsConn, ok := iConn.(*tls.UConn); ok {
@@ -228,7 +225,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
// default: serverWriter := bufferWriter // default: serverWriter := bufferWriter
serverWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, true, ctx) serverWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, true, ctx, conn, ob)
if request.Command == protocol.RequestCommandMux && request.Port == 666 { if request.Command == protocol.RequestCommandMux && request.Port == 666 {
serverWriter = xudp.NewPacketWriter(serverWriter, target, xudp.GetGlobalID(ctx)) serverWriter = xudp.NewPacketWriter(serverWriter, target, xudp.GetGlobalID(ctx))
} }
@@ -256,7 +253,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return errors.New("failed to write A request payload").Base(err).AtWarning() return errors.New("failed to write A request payload").Base(err).AtWarning()
} }
var err error
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
if tlsConn, ok := iConn.(*tls.Conn); ok { if tlsConn, ok := iConn.(*tls.Conn); ok {
if tlsConn.ConnectionState().Version != gotls.VersionTLS13 { if tlsConn.ConnectionState().Version != gotls.VersionTLS13 {
@@ -267,12 +263,8 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, utlsConn.ConnectionState().Version).AtWarning() return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, utlsConn.ConnectionState().Version).AtWarning()
} }
} }
ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice
err = encoding.XtlsWrite(clientReader, serverWriter, timer, conn, trafficState, ob, true, ctx1)
} else {
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
} }
err := buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
if err != nil { if err != nil {
return errors.New("failed to transfer request payload").Base(err).AtInfo() return errors.New("failed to transfer request payload").Base(err).AtInfo()
} }
@@ -295,7 +287,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
// default: serverReader := buf.NewReader(conn) // default: serverReader := buf.NewReader(conn)
serverReader := encoding.DecodeBodyAddons(conn, request, responseAddons) serverReader := encoding.DecodeBodyAddons(conn, request, responseAddons)
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
serverReader = proxy.NewVisionReader(serverReader, trafficState, false, ctx) serverReader = proxy.NewVisionReader(serverReader, trafficState, false, ctx, conn, input, rawInput, ob)
} }
if request.Command == protocol.RequestCommandMux && request.Port == 666 { if request.Command == protocol.RequestCommandMux && request.Port == 666 {
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
@@ -306,7 +298,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
if requestAddons.Flow == vless.XRV { 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, trafficState, false, ctx)
} else { } else {
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer // from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer
err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)) err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))
+2 -2
View File
@@ -10,10 +10,10 @@ import (
"hash/crc32" "hash/crc32"
"io" "io"
"math" "math"
"time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/antireplay" "github.com/xtls/xray-core/common/antireplay"
"github.com/xtls/xray-core/proxy/vmess/vtime"
) )
var ( var (
@@ -105,7 +105,7 @@ func (a *AuthIDDecoderHolder) Match(authID [16]byte) (interface{}, error) {
continue continue
} }
if math.Abs(math.Abs(float64(t))-float64(time.Now().Unix())) > 120 { if math.Abs(math.Abs(float64(t))-float64(vtime.Now().Unix())) > 120 {
continue continue
} }
+2 -2
View File
@@ -5,14 +5,14 @@ import (
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"io" "io"
"time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/proxy/vmess/vtime"
) )
func SealVMessAEADHeader(key [16]byte, data []byte) []byte { func SealVMessAEADHeader(key [16]byte, data []byte) []byte {
generatedAuthID := CreateAuthID(key[:], time.Now().Unix()) generatedAuthID := CreateAuthID(key[:], vtime.Now().Unix())
connectionNonce := make([]byte, 8) connectionNonce := make([]byte, 8)
if _, err := io.ReadFull(rand.Reader, connectionNonce); err != nil { if _, err := io.ReadFull(rand.Reader, connectionNonce); err != nil {
+74
View File
@@ -0,0 +1,74 @@
package vtime
import (
"context"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform"
"github.com/xtls/xray-core/common/protocol/http"
)
var timeOffset atomic.Pointer[time.Duration]
var initOnce sync.Once
func updateTimeMonitor(ctx context.Context, domain string) {
for {
select {
case <-ctx.Done():
return
case <-time.After(10 * time.Minute):
err := updateTime(domain)
if err != nil {
errors.LogError(ctx, err)
}
}
}
}
func updateTime(domain string) error {
httpClient := http.NewClient()
resp, err := httpClient.Get(domain)
if err != nil {
return errors.New("Failed to access monitor domain").Base(err)
}
timeHeader := resp.Header.Get("Date")
remoteTime, err := time.Parse(time.RFC1123, timeHeader)
if err != nil {
return errors.New("Failed to parse time from monitor domain").Base(err)
}
localTime := time.Now()
offset := remoteTime.Sub(localTime)
if offset < 2*time.Second && offset > -2*time.Second {
errors.LogWarning(context.Background(), "Time offset too small, ignoring:", offset)
return nil
}
timeOffset.Store(&offset)
return nil
}
func Now() time.Time {
initOnce.Do(func() {
timeOffset.Store(new(time.Duration))
go func() {
domain := platform.NewEnvFlag("xray.vmess.time.domain").GetValue(func() string { return "https://apple.com" })
if domain == "" {
errors.LogError(context.Background(), "vmess time domain is empty, skip time sync")
return
}
err := updateTime(domain)
if err != nil {
errors.LogError(context.Background(), err)
}
errors.LogWarning(context.Background(), "Initial time offset for vmess:", timeOffset.Load())
// only one sync should be enough, so disable periodic update for now
//go updateTimeMonitor(context.TODO(), domain)
}()
runtime.Gosched()
})
offset := timeOffset.Load()
return time.Now().Add(*offset)
}