Update On Sun Aug 31 20:34:09 CEST 2025

This commit is contained in:
github-action[bot]
2025-08-31 20:34:09 +02:00
parent f1fd237cba
commit c82a70acdb
84 changed files with 1646 additions and 949 deletions

1
.github/update.log vendored
View File

@@ -1106,3 +1106,4 @@ Update On Wed Aug 27 20:38:26 CEST 2025
Update On Thu Aug 28 20:40:03 CEST 2025 Update On Thu Aug 28 20:40:03 CEST 2025
Update On Fri Aug 29 20:33:46 CEST 2025 Update On Fri Aug 29 20:33:46 CEST 2025
Update On Sat Aug 30 20:36:28 CEST 2025 Update On Sat Aug 30 20:36:28 CEST 2025
Update On Sun Aug 31 20:34:00 CEST 2025

View File

@@ -610,7 +610,6 @@ proxies: # socks5
uuid: uuid uuid: uuid
network: tcp network: tcp
servername: example.com # AKA SNI servername: example.com # AKA SNI
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
# skip-cert-verify: true # skip-cert-verify: true
# fingerprint: xxxx # fingerprint: xxxx
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none" # client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
@@ -640,10 +639,16 @@ proxies: # socks5
network: tcp network: tcp
# ------------------------- # -------------------------
# vless encryption客户端配置 # vless encryption客户端配置
# native/xorpub 的 XTLS 可以 Splice。只使用 1-RTT 模式 / 若服务端发的 ticket 中秒数不为零则 0-RTT 复用) # native/xorpub 的 XTLS Vision 可以 Splice。只使用 1-RTT 模式 / 若服务端发的 ticket 中秒数不为零则 0-RTT 复用)
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号 # / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
#
# Padding 是可选的参数,仅作用于 1-RTT 以消除握手的长度特征,双端默认值均为 "100-111-1111.75-0-111.50-0-3333"
# 在 1-RTT client/server hello 后以 100% 的概率粘上随机 111 到 1111 字节的 padding
# 以 75% 的概率等待随机 0 到 111 毫秒("probability-from-to"
# 再次以 50% 的概率发送随机 0 到 3333 字节的 padding若为 0 则不 Write()
# 服务端、客户端可以设置不同的 padding 参数,按 len、gap 的顺序无限串联,第一个 padding 需概率 100%、至少 35 字节
# ------------------------- # -------------------------
encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(X25519 Password).(ML-KEM-768 Client)..." encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(padding len).(padding gap).(X25519 Password).(ML-KEM-768 Client)..."
tls: false #可以不开启tls tls: false #可以不开启tls
udp: true udp: true
@@ -1367,8 +1372,14 @@ listeners:
# vless encryption服务端配置 # vless encryption服务端配置
# (原生外观 / 只 XOR 公钥 / 全随机数。只允许 1-RTT 模式 / 同时允许 1-RTT 模式与 600 秒复用的 0-RTT 模式) # (原生外观 / 只 XOR 公钥 / 全随机数。只允许 1-RTT 模式 / 同时允许 1-RTT 模式与 600 秒复用的 0-RTT 模式)
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号 # / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
#
# Padding 是可选的参数,仅作用于 1-RTT 以消除握手的长度特征,双端默认值均为 "100-111-1111.75-0-111.50-0-3333"
# 在 1-RTT client/server hello 后以 100% 的概率粘上随机 111 到 1111 字节的 padding
# 以 75% 的概率等待随机 0 到 111 毫秒("probability-from-to"
# 再次以 50% 的概率发送随机 0 到 3333 字节的 padding若为 0 则不 Write()
# 服务端、客户端可以设置不同的 padding 参数,按 len、gap 的顺序无限串联,第一个 padding 需概率 100%、至少 35 字节
# ------------------------- # -------------------------
# decryption: "mlkem768x25519plus.native/xorpub/random.1rtt/600s.(X25519 PrivateKey).(ML-KEM-768 Seed)..." # decryption: "mlkem768x25519plus.native/xorpub/random.1rtt/600s.(padding len).(padding gap).(X25519 PrivateKey).(ML-KEM-768 Seed)..."
# 下面两项如果填写则开启 tls需要同时填写 # 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt # certificate: ./server.crt
# private-key: ./server.key # private-key: ./server.key

View File

@@ -99,6 +99,15 @@ func TestInboundVless_Encryption(t *testing.T) {
t.Fatal(err) t.Fatal(err)
return return
} }
paddings := []struct {
name string
data string
}{
{"unconfigured-padding", ""},
{"default-padding", "100-111-1111.75-0-111.50-0-3333."},
{"old-padding", "100-100-1000."}, // Xray-core v25.8.29
{"custom-padding", "100-1234-7890.33-0-1111.66-0-6666.55-111-777."},
}
var modes = []string{ var modes = []string{
"native", "native",
"xorpub", "xorpub",
@@ -107,19 +116,26 @@ func TestInboundVless_Encryption(t *testing.T) {
for i := range modes { for i := range modes {
mode := modes[i] mode := modes[i]
t.Run(mode, func(t *testing.T) { t.Run(mode, func(t *testing.T) {
inboundOptions := inbound.VlessOption{ t.Parallel()
Decryption: "mlkem768x25519plus." + mode + ".600s." + privateKeyBase64 + "." + seedBase64, for i := range paddings {
padding := paddings[i].data
t.Run(paddings[i].name, func(t *testing.T) {
inboundOptions := inbound.VlessOption{
Decryption: "mlkem768x25519plus." + mode + ".600s." + padding + privateKeyBase64 + "." + seedBase64,
}
outboundOptions := outbound.VlessOption{
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + padding + passwordBase64 + "." + clientBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
})
} }
outboundOptions := outbound.VlessOption{
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + passwordBase64 + "." + clientBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
}) })
} }
} }

View File

@@ -230,7 +230,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
ctx := sing.WithAdditions(context.TODO(), additions...) ctx := sing.WithAdditions(context.TODO(), additions...)
if l.decryption != nil { if l.decryption != nil {
var err error var err error
conn, err = l.decryption.Handshake(conn) conn, err = l.decryption.Handshake(conn, nil)
if err != nil { if err != nil {
return return
} }

View File

@@ -87,7 +87,11 @@ func (s *serverHandler) handle() {
_ = s.handleMessage() _ = s.handleMessage()
}() }()
<-s.quicConn.HandshakeComplete() select {
case <-s.quicConn.HandshakeComplete(): // this chan maybe not closed if handshake never complete
case <-time.After(s.quicConn.Config().HandshakeIdleTimeout): // HandshakeIdleTimeout in real conn.Config() never be zero
}
time.AfterFunc(s.AuthenticationTimeout, func() { time.AfterFunc(s.AuthenticationTimeout, func() {
if s.v4Handler != nil { if s.v4Handler != nil {
if s.v4Handler.AuthOk() { if s.v4Handler.AuthOk() {

View File

@@ -7,11 +7,23 @@ import (
"errors" "errors"
"io" "io"
"net" "net"
"runtime"
"sync" "sync"
"time" "time"
"github.com/metacubex/blake3" "github.com/metacubex/blake3"
"github.com/metacubex/utls/mlkem" "github.com/metacubex/utls/mlkem"
"golang.org/x/sys/cpu"
)
var (
// Keep in sync with crypto/tls/cipher_suites.go.
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
) )
type ClientInstance struct { type ClientInstance struct {
@@ -21,6 +33,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
@@ -28,15 +42,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
@@ -58,18 +70,18 @@ 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) {
if i.NfsPKeys == nil { if i.NfsPKeys == nil {
return nil, errors.New("uninitialized") return nil, errors.New("uninitialized")
} }
c := NewCommonConn(conn) c := NewCommonConn(conn, HasAESGCMHardwareSupport)
ivAndRealysLength := 16 + i.RelaysLength ivAndRealysLength := 16 + i.RelaysLength
pfsKeyExchangeLength := 18 + 1184 + 32 + 16 pfsKeyExchangeLength := 18 + 1184 + 32 + 16
paddingLength := int(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]
@@ -107,18 +119,18 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:]) lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:])
relays = relays[index+32:] relays = relays[index+32:]
} }
nfsGCM := NewGCM(iv, nfsKey) nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES)
if i.Seconds > 0 { if i.Seconds > 0 {
i.RWLock.RLock() i.RWLock.RLock()
if time.Now().Before(i.Expire) { if time.Now().Before(i.Expire) {
c.Client = i c.Client = i
c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection
nfsGCM.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil) nfsAEAD.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil)
nfsGCM.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil) nfsAEAD.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil)
i.RWLock.RUnlock() i.RWLock.RUnlock()
c.PreWrite = clientHello[:ivAndRealysLength+18+32] c.PreWrite = clientHello[:ivAndRealysLength+18+32]
c.GCM = NewGCM(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey) c.AEAD = NewAEAD(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey, c.UseAES)
if i.XorMode == 2 { if i.XorMode == 2 {
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16) c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16)
} }
@@ -128,26 +140,34 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
} }
pfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength] pfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength]
nfsGCM.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil) nfsAEAD.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil)
mlkem768DKey, _ := mlkem.GenerateKey768() mlkem768DKey, _ := mlkem.GenerateKey768()
x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader) x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
pfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...) pfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...)
nfsGCM.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil) nfsAEAD.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil)
padding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:] padding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:]
nfsGCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
nfsGCM.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]
return nil, err for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
if l > 0 {
if _, err := conn.Write(clientHello[:l]); err != nil {
return nil, err
}
clientHello = clientHello[l:]
}
if len(paddingGaps) > i {
time.Sleep(paddingGaps[i])
}
} }
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
encryptedPfsPublicKey := make([]byte, 1088+32+16) encryptedPfsPublicKey := make([]byte, 1088+32+16)
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
return nil, err return nil, err
} }
nfsGCM.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil) nfsAEAD.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil)
mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088]) mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088])
if err != nil { if err != nil {
return nil, err return nil, err
@@ -164,14 +184,14 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
copy(pfsKey, mlkem768Key) copy(pfsKey, mlkem768Key)
copy(pfsKey[32:], x25519Key) copy(pfsKey[32:], x25519Key)
c.UnitedKey = append(pfsKey, nfsKey...) c.UnitedKey = append(pfsKey, nfsKey...)
c.GCM = NewGCM(pfsPublicKey, c.UnitedKey) c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)
c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1088+32], c.UnitedKey) c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1088+32], c.UnitedKey, c.UseAES)
encryptedTicket := make([]byte, 32) encryptedTicket := make([]byte, 32)
if _, err := io.ReadFull(conn, encryptedTicket); err != nil { if _, err := io.ReadFull(conn, encryptedTicket); err != nil {
return nil, err return nil, err
} }
if _, err := c.PeerGCM.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil { if _, err := c.PeerAEAD.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil {
return nil, err return nil, err
} }
seconds := DecodeLength(encryptedTicket) seconds := DecodeLength(encryptedTicket)
@@ -188,7 +208,7 @@ func (i *ClientInstance) 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 _, err := c.PeerGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { if _, err := c.PeerAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
return nil, err return nil, err
} }
length := DecodeLength(encryptedLength[:2]) length := DecodeLength(encryptedLength[:2])

View File

@@ -8,29 +8,34 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strconv"
"strings"
"time" "time"
"github.com/metacubex/mihomo/common/pool" "github.com/metacubex/mihomo/common/pool"
"github.com/metacubex/blake3" "github.com/metacubex/blake3"
"github.com/metacubex/randv2" "github.com/metacubex/randv2"
"golang.org/x/crypto/chacha20poly1305"
) )
type CommonConn struct { type CommonConn struct {
net.Conn net.Conn
UseAES bool
Client *ClientInstance Client *ClientInstance
UnitedKey []byte UnitedKey []byte
PreWrite []byte PreWrite []byte
GCM *GCM AEAD *AEAD
PeerAEAD *AEAD
PeerPadding []byte PeerPadding []byte
rawInput bytes.Buffer // PeerInBytes rawInput bytes.Buffer // PeerInBytes
PeerGCM *GCM
input bytes.Reader // PeerCache input bytes.Reader // PeerCache
} }
func NewCommonConn(conn net.Conn) *CommonConn { func NewCommonConn(conn net.Conn, useAES bool) *CommonConn {
return &CommonConn{ return &CommonConn{
Conn: conn, Conn: conn,
UseAES: useAES,
} }
} }
@@ -49,12 +54,12 @@ func (c *CommonConn) Write(b []byte) (int, error) {
headerAndData := outBytes[:5+len(b)+16] headerAndData := outBytes[:5+len(b)+16]
EncodeHeader(headerAndData, len(b)+16) EncodeHeader(headerAndData, len(b)+16)
max := false max := false
if bytes.Equal(c.GCM.Nonce[:], MaxNonce) { if bytes.Equal(c.AEAD.Nonce[:], MaxNonce) {
max = true max = true
} }
c.GCM.Seal(headerAndData[:5], nil, b, headerAndData[:5]) c.AEAD.Seal(headerAndData[:5], nil, b, headerAndData[:5])
if max { if max {
c.GCM = NewGCM(headerAndData, c.UnitedKey) c.AEAD = NewAEAD(headerAndData, c.UnitedKey, c.UseAES)
} }
if c.PreWrite != nil { if c.PreWrite != nil {
headerAndData = append(c.PreWrite, headerAndData...) headerAndData = append(c.PreWrite, headerAndData...)
@@ -71,12 +76,12 @@ func (c *CommonConn) Read(b []byte) (int, error) {
if len(b) == 0 { if len(b) == 0 {
return 0, nil return 0, nil
} }
if c.PeerGCM == nil { // client's 0-RTT if c.PeerAEAD == nil { // client's 0-RTT
serverRandom := make([]byte, 16) serverRandom := make([]byte, 16)
if _, err := io.ReadFull(c.Conn, serverRandom); err != nil { if _, err := io.ReadFull(c.Conn, serverRandom); err != nil {
return 0, err return 0, err
} }
c.PeerGCM = NewGCM(serverRandom, c.UnitedKey) c.PeerAEAD = NewAEAD(serverRandom, c.UnitedKey, c.UseAES)
if xorConn, ok := c.Conn.(*XorConn); ok { if xorConn, ok := c.Conn.(*XorConn); ok {
xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom) xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom)
} }
@@ -85,7 +90,7 @@ func (c *CommonConn) Read(b []byte) (int, error) {
if _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil { if _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil {
return 0, err return 0, err
} }
if _, err := c.PeerGCM.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil { if _, err := c.PeerAEAD.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil {
return 0, err return 0, err
} }
c.PeerPadding = nil c.PeerPadding = nil
@@ -93,11 +98,11 @@ func (c *CommonConn) Read(b []byte) (int, error) {
if c.input.Len() > 0 { if c.input.Len() > 0 {
return c.input.Read(b) return c.input.Read(b)
} }
peerHeader := make([]byte, 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(peerHeader) // l: 17~17000 l, err := DecodeHeader(peerHeader[:]) // l: 17~17000
if err != nil { if err != nil {
if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT
c.Client.RWLock.Lock() c.Client.RWLock.Lock()
@@ -110,7 +115,9 @@ func (c *CommonConn) Read(b []byte) (int, error) {
return 0, err return 0, err
} }
c.Client = nil c.Client = nil
c.rawInput.Grow(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] 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
@@ -119,13 +126,13 @@ func (c *CommonConn) Read(b []byte) (int, error) {
if len(dst) <= len(b) { if len(dst) <= len(b) {
dst = b[:len(dst)] // avoids another copy() dst = b[:len(dst)] // avoids another copy()
} }
var newGCM *GCM var newAEAD *AEAD
if bytes.Equal(c.PeerGCM.Nonce[:], MaxNonce) { if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) {
newGCM = NewGCM(append(peerHeader, peerData...), c.UnitedKey) newAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES)
} }
_, err = c.PeerGCM.Open(dst[:0], nil, peerData, peerHeader) _, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:])
if newGCM != nil { if newAEAD != nil {
c.PeerGCM = newGCM c.PeerAEAD = newAEAD
} }
if err != nil { if err != nil {
return 0, err return 0, err
@@ -137,28 +144,32 @@ func (c *CommonConn) Read(b []byte) (int, error) {
return len(dst), nil return len(dst), nil
} }
type GCM struct { type AEAD struct {
cipher.AEAD cipher.AEAD
Nonce [12]byte Nonce [12]byte
} }
func NewGCM(ctx, key []byte) *GCM { func NewAEAD(ctx, key []byte, useAES bool) *AEAD {
k := make([]byte, 32) k := make([]byte, 32)
blake3.DeriveKey(k, string(ctx), key) blake3.DeriveKey(k, string(ctx), key)
block, _ := aes.NewCipher(k) var aead cipher.AEAD
aead, _ := cipher.NewGCM(block) if useAES {
return &GCM{AEAD: aead} block, _ := aes.NewCipher(k)
//chacha20poly1305.New() aead, _ = cipher.NewGCM(block)
} else {
aead, _ = chacha20poly1305.New(k)
}
return &AEAD{AEAD: aead}
} }
func (a *GCM) Seal(dst, nonce, plaintext, additionalData []byte) []byte { func (a *AEAD) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if nonce == nil { if nonce == nil {
nonce = IncreaseNonce(a.Nonce[:]) nonce = IncreaseNonce(a.Nonce[:])
} }
return a.AEAD.Seal(dst, nonce, plaintext, additionalData) return a.AEAD.Seal(dst, nonce, plaintext, additionalData)
} }
func (a *GCM) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { func (a *AEAD) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if nonce == nil { if nonce == nil {
nonce = IncreaseNonce(a.Nonce[:]) nonce = IncreaseNonce(a.Nonce[:])
} }
@@ -206,9 +217,80 @@ func DecodeHeader(h []byte) (l int, err error) {
return 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(randBetween(0, 100)) {
l = int(randBetween(int64(y[1]), int64(y[2])))
}
lens = append(lens, l)
length += l
}
for _, y := range paddingGaps {
g := 0
if y[0] >= int(randBetween(0, 100)) {
g = int(randBetween(int64(y[1]), int64(y[2])))
}
gaps = append(gaps, time.Duration(g)*time.Millisecond)
}
return
}
func max[T ~int | ~uint | ~int64 | ~uint64 | ~int32 | ~uint32 | ~int16 | ~uint16 | ~int8 | ~uint8](a, b T) T {
if a > b {
return a
}
return b
}
func randBetween(from int64, to int64) int64 { func randBetween(from int64, to int64) int64 {
if from == to { if from == to {
return from return from
} }
if to < from {
from, to = to, from
}
return from + randv2.Int64N(to-from) return from + randv2.Int64N(to-from)
} }

View File

@@ -21,4 +21,7 @@
// https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902 // https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902
// https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e // https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e
// https://github.com/XTLS/Xray-core/commit/b0b220985c9c1bc832665458d5fd6e0c287b67ae // https://github.com/XTLS/Xray-core/commit/b0b220985c9c1bc832665458d5fd6e0c287b67ae
// https://github.com/XTLS/Xray-core/commit/82ea7a3cc5ff23280b87e3052f0f83b04f0267fa
// https://github.com/XTLS/Xray-core/commit/e8b02cd6649f14889841e8ab8ee6b2acca71dbe6
// https://github.com/XTLS/Xray-core/commit/6768a22f676c9121cfc9dc4f51181a8a07837c8d
package encryption package encryption

View File

@@ -34,7 +34,12 @@ func NewClient(encryption string) (*ClientInstance, error) {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
} }
var nfsPKeysBytes [][]byte var nfsPKeysBytes [][]byte
var paddings []string
for _, r := range s[3:] { for _, r := range s[3:] {
if len(r) < 20 {
paddings = append(paddings, r)
continue
}
b, err := base64.RawURLEncoding.DecodeString(r) b, err := base64.RawURLEncoding.DecodeString(r)
if err != nil { if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
@@ -44,8 +49,9 @@ func NewClient(encryption string) (*ClientInstance, error) {
} }
nfsPKeysBytes = append(nfsPKeysBytes, b) nfsPKeysBytes = append(nfsPKeysBytes, b)
} }
padding := strings.Join(paddings, ".")
client := &ClientInstance{} client := &ClientInstance{}
if err := client.Init(nfsPKeysBytes, xorMode, seconds); err != nil { if err := client.Init(nfsPKeysBytes, xorMode, seconds, padding); err != nil {
return nil, fmt.Errorf("failed to use encryption: %w", err) return nil, fmt.Errorf("failed to use encryption: %w", err)
} }
return client, nil return client, nil
@@ -84,7 +90,12 @@ func NewServer(decryption string) (*ServerInstance, error) {
seconds = uint32(i) seconds = uint32(i)
} }
var nfsSKeysBytes [][]byte var nfsSKeysBytes [][]byte
var paddings []string
for _, r := range s[3:] { for _, r := range s[3:] {
if len(r) < 20 {
paddings = append(paddings, r)
continue
}
b, err := base64.RawURLEncoding.DecodeString(r) b, err := base64.RawURLEncoding.DecodeString(r)
if err != nil { if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
@@ -94,8 +105,9 @@ func NewServer(decryption string) (*ServerInstance, error) {
} }
nfsSKeysBytes = append(nfsSKeysBytes, b) nfsSKeysBytes = append(nfsSKeysBytes, b)
} }
padding := strings.Join(paddings, ".")
server := &ServerInstance{} server := &ServerInstance{}
if err := server.Init(nfsSKeysBytes, xorMode, seconds); err != nil { if err := server.Init(nfsSKeysBytes, xorMode, seconds, padding); err != nil {
return nil, fmt.Errorf("failed to use decryption: %w", err) return nil, fmt.Errorf("failed to use decryption: %w", err)
} }
return server, nil return server, nil

View File

@@ -29,21 +29,21 @@ type ServerInstance 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
Sessions map[[16]byte]*ServerSession Sessions map[[16]byte]*ServerSession
Closed bool Closed bool
} }
func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) { func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) {
if i.NfsSKeys != nil { 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)
@@ -87,7 +87,7 @@ func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (
} }
}() }()
} }
return return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
} }
func (i *ServerInstance) Close() (err error) { func (i *ServerInstance) Close() (err error) {
@@ -97,16 +97,19 @@ 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")
} }
c := NewCommonConn(conn) c := NewCommonConn(conn, true)
ivAndRelays := make([]byte, 16+i.RelaysLength) ivAndRelays := make([]byte, 16+i.RelaysLength)
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
@@ -150,16 +153,27 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
} }
relays = relays[32:] relays = relays[32:]
} }
nfsGCM := NewGCM(iv, nfsKey) nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES)
encryptedLength := make([]byte, 18) encryptedLength := make([]byte, 18)
if _, err := io.ReadFull(conn, encryptedLength); err != nil { if _, err := io.ReadFull(conn, encryptedLength); err != nil {
return nil, err return nil, err
} }
if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { if fallback != nil {
return nil, err *fallback = append(*fallback, encryptedLength...)
} }
length := DecodeLength(encryptedLength[:2]) decryptedLength := make([]byte, 2)
if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil {
c.UseAES = !c.UseAES
nfsAEAD = NewAEAD(iv, nfsKey, c.UseAES)
if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil {
return nil, err
}
}
if fallback != nil {
*fallback = nil
}
length := DecodeLength(decryptedLength)
if length == 32 { if length == 32 {
if i.Seconds == 0 { if i.Seconds == 0 {
@@ -169,7 +183,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
if _, err := io.ReadFull(conn, encryptedTicket); err != nil { if _, err := io.ReadFull(conn, encryptedTicket); err != nil {
return nil, err return nil, err
} }
ticket, err := nfsGCM.Open(nil, nil, encryptedTicket, nil) ticket, err := nfsAEAD.Open(nil, nil, encryptedTicket, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -177,7 +191,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, randBetween(1268, 2268)) // matches 1-RTT's server hello length for "random", though it is not important, just for example noises := make([]byte, 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)
@@ -192,8 +206,8 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
c.UnitedKey = append(s.PfsKey, nfsKey...) // the same nfsKey links the upload & download (prevents server -> client's another request) c.UnitedKey = append(s.PfsKey, nfsKey...) // the same nfsKey links the upload & download (prevents server -> client's another request)
c.PreWrite = make([]byte, 16) c.PreWrite = make([]byte, 16)
rand.Read(c.PreWrite) // always trust yourself, not the client (also prevents being parsed as TLS thus causing false interruption for "native" and "xorpub") rand.Read(c.PreWrite) // always trust yourself, not the client (also prevents being parsed as TLS thus causing false interruption for "native" and "xorpub")
c.GCM = NewGCM(c.PreWrite, c.UnitedKey) c.AEAD = NewAEAD(c.PreWrite, c.UnitedKey, c.UseAES)
c.PeerGCM = NewGCM(encryptedTicket, c.UnitedKey) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client) c.PeerAEAD = NewAEAD(encryptedTicket, c.UnitedKey, c.UseAES) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client)
if i.XorMode == 2 { if i.XorMode == 2 {
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite), NewCTR(c.UnitedKey, iv), 16, 0) // it doesn't matter if the attacker sends client's iv back to the client c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite), NewCTR(c.UnitedKey, iv), 16, 0) // it doesn't matter if the attacker sends client's iv back to the client
} }
@@ -207,7 +221,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
return nil, err return nil, err
} }
if _, err := nfsGCM.Open(encryptedPfsPublicKey[:0], nil, encryptedPfsPublicKey, nil); err != nil { if _, err := nfsAEAD.Open(encryptedPfsPublicKey[:0], nil, encryptedPfsPublicKey, nil); err != nil {
return nil, err return nil, err
} }
mlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184]) mlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184])
@@ -229,27 +243,12 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
copy(pfsKey[32:], x25519Key) copy(pfsKey[32:], x25519Key)
pfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...) pfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...)
c.UnitedKey = append(pfsKey, nfsKey...) c.UnitedKey = append(pfsKey, nfsKey...)
c.GCM = NewGCM(pfsPublicKey, c.UnitedKey) c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)
c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1184+32], c.UnitedKey) c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES)
ticket := make([]byte, 16) ticket := make([]byte, 16)
rand.Read(ticket) rand.Read(ticket)
copy(ticket, EncodeLength(int(i.Seconds*4/5))) copy(ticket, EncodeLength(int(i.Seconds*4/5)))
pfsKeyExchangeLength := 1088 + 32 + 16
encryptedTicketLength := 32
paddingLength := int(randBetween(100, 1000))
serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)
nfsGCM.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)
c.GCM.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil)
padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]
c.GCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
c.GCM.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
if _, err := conn.Write(serverHello); err != nil {
return nil, err
}
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
if i.Seconds > 0 { if i.Seconds > 0 {
i.RWLock.Lock() i.RWLock.Lock()
i.Sessions[[16]byte(ticket)] = &ServerSession{ i.Sessions[[16]byte(ticket)] = &ServerSession{
@@ -259,18 +258,41 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
i.RWLock.Unlock() i.RWLock.Unlock()
} }
pfsKeyExchangeLength := 1088 + 32 + 16
encryptedTicketLength := 32
paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)
serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)
nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)
c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil)
padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]
c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
paddingLens[0] = pfsKeyExchangeLength + encryptedTicketLength + paddingLens[0]
for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
if l > 0 {
if _, err := conn.Write(serverHello[:l]); err != nil {
return nil, err
}
serverHello = serverHello[l:]
}
if len(paddingGaps) > i {
time.Sleep(paddingGaps[i])
}
}
// important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern // important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern
if _, err := io.ReadFull(conn, encryptedLength); err != nil { if _, err := io.ReadFull(conn, encryptedLength); err != nil {
return nil, err return nil, err
} }
if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { if _, err := nfsAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
return nil, err return nil, err
} }
encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2])) encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2]))
if _, err := io.ReadFull(conn, encryptedPadding); err != nil { if _, err := io.ReadFull(conn, encryptedPadding); err != nil {
return nil, err return nil, err
} }
if _, err := nfsGCM.Open(encryptedPadding[:0], nil, encryptedPadding, nil); err != nil { if _, err := nfsAEAD.Open(encryptedPadding[:0], nil, encryptedPadding, nil); err != nil {
return nil, err return nil, err
} }

View File

@@ -49,7 +49,7 @@ jobs:
- uses: maxim-lobanov/setup-xcode@v1 - uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: 15 xcode-version: 16
- name: install Rust stable - name: install Rust stable
run: | run: |

View File

@@ -26,7 +26,7 @@ jobs:
shared-key: 'release' shared-key: 'release'
- uses: maxim-lobanov/setup-xcode@v1 - uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: '15.0' xcode-version: 16
- name: install the missing rust target - name: install the missing rust target
run: | run: |
rustup target add aarch64-apple-darwin rustup target add aarch64-apple-darwin

View File

@@ -1569,7 +1569,6 @@ dependencies = [
"once_cell", "once_cell",
"oneshot", "oneshot",
"open", "open",
"openssl",
"os_pipe", "os_pipe",
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1821,7 +1820,7 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"core-foundation 0.9.4", "core-foundation 0.9.4",
"core-graphics-types 0.1.3", "core-graphics-types 0.1.3",
"foreign-types 0.5.0", "foreign-types",
"libc", "libc",
] ]
@@ -1834,7 +1833,7 @@ dependencies = [
"bitflags 2.9.2", "bitflags 2.9.2",
"core-foundation 0.10.1", "core-foundation 0.10.1",
"core-graphics-types 0.2.0", "core-graphics-types 0.2.0",
"foreign-types 0.5.0", "foreign-types",
"libc", "libc",
] ]
@@ -3055,15 +3054,6 @@ dependencies = [
"ttf-parser", "ttf-parser",
] ]
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared 0.1.1",
]
[[package]] [[package]]
name = "foreign-types" name = "foreign-types"
version = "0.5.0" version = "0.5.0"
@@ -3071,7 +3061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [ dependencies = [
"foreign-types-macros", "foreign-types-macros",
"foreign-types-shared 0.3.1", "foreign-types-shared",
] ]
[[package]] [[package]]
@@ -3085,12 +3075,6 @@ dependencies = [
"syn 2.0.106", "syn 2.0.106",
] ]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "foreign-types-shared" name = "foreign-types-shared"
version = "0.3.1" version = "0.3.1"
@@ -4088,22 +4072,6 @@ dependencies = [
"webpki-roots 1.0.2", "webpki-roots 1.0.2",
] ]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.16" version = "0.1.16"
@@ -5326,7 +5294,7 @@ dependencies = [
"bitflags 2.9.2", "bitflags 2.9.2",
"block", "block",
"core-graphics-types 0.1.3", "core-graphics-types 0.1.3",
"foreign-types 0.5.0", "foreign-types",
"log", "log",
"objc", "objc",
"paste", "paste",
@@ -5471,23 +5439,6 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
] ]
[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]] [[package]]
name = "ndk" name = "ndk"
version = "0.9.0" version = "0.9.0"
@@ -6361,47 +6312,12 @@ dependencies = [
"pathdiff", "pathdiff",
] ]
[[package]]
name = "openssl"
version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [
"bitflags 2.9.2",
"cfg-if",
"foreign-types 0.3.2",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.1.6" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-src"
version = "300.5.2+3.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.109" version = "0.9.109"
@@ -6410,7 +6326,6 @@ checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
"openssl-src",
"pkg-config", "pkg-config",
"vcpkg", "vcpkg",
] ]
@@ -7798,12 +7713,10 @@ dependencies = [
"http-body-util", "http-body-util",
"hyper", "hyper",
"hyper-rustls", "hyper-rustls",
"hyper-tls",
"hyper-util", "hyper-util",
"js-sys", "js-sys",
"log", "log",
"mime", "mime",
"native-tls",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"quinn", "quinn",
@@ -7814,7 +7727,6 @@ dependencies = [
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-native-tls",
"tokio-rustls", "tokio-rustls",
"tokio-util", "tokio-util",
"tower", "tower",
@@ -8195,19 +8107,6 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.9.2",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]] [[package]]
name = "security-framework-sys" name = "security-framework-sys"
version = "2.14.0" version = "2.14.0"
@@ -8785,7 +8684,7 @@ dependencies = [
"bytemuck", "bytemuck",
"cfg_aliases", "cfg_aliases",
"core-graphics 0.24.0", "core-graphics 0.24.0",
"foreign-types 0.5.0", "foreign-types",
"js-sys", "js-sys",
"log", "log",
"objc2 0.5.2", "objc2 0.5.2",
@@ -9999,16 +9898,6 @@ dependencies = [
"syn 2.0.106", "syn 2.0.106",
] ]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.2" version = "0.26.2"

View File

@@ -89,7 +89,14 @@ rust-i18n = "3"
axum = "0.8" axum = "0.8"
url = "2" url = "2"
mime = "0.3" mime = "0.3"
reqwest = { version = "0.12", features = ["json", "stream"] } reqwest = { version = "0.12", default-features = false, features = [
"charset",
"http2",
"system-proxy",
"json",
"stream",
"rustls-tls",
] }
tokio-tungstenite = "0.27" tokio-tungstenite = "0.27"
urlencoding = "2.1" urlencoding = "2.1"
port_scanner = "0.1.5" port_scanner = "0.1.5"
@@ -193,7 +200,13 @@ boa_utils = { path = "../boa_utils" } # should be removed wh
boa_engine = { workspace = true, features = ["annex-b"] } boa_engine = { workspace = true, features = ["annex-b"] }
# Tauri Dependencies # Tauri Dependencies
tauri = { version = "2.4", features = ["tray-icon", "image-png", "image-ico"] } tauri = { version = "2.4", features = [
"tray-icon",
"image-png",
"image-ico",
"rustls-tls",
"specta",
] }
tauri-plugin-deep-link = { path = "../tauri-plugin-deep-link", version = "0.1.2" } # This should be migrated to official tauri plugin tauri-plugin-deep-link = { path = "../tauri-plugin-deep-link", version = "0.1.2" } # This should be migrated to official tauri plugin
tauri-plugin-os = "2.2" tauri-plugin-os = "2.2"
tauri-plugin-clipboard-manager = "2.2" tauri-plugin-clipboard-manager = "2.2"
@@ -221,9 +234,6 @@ specta = { version = "=2.0.0-rc.22", features = [
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] [target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
tauri-plugin-global-shortcut = "2.2.0" tauri-plugin-global-shortcut = "2.2.0"
[target.'cfg(target_os = "linux")'.dependencies]
openssl = { version = "0.10", features = ["vendored"] }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6.1" objc2 = "0.6.1"
objc2-app-kit = { version = "0.3.1", features = [ objc2-app-kit = { version = "0.3.1", features = [
@@ -260,4 +270,3 @@ verge-dev = []
default-meta = [] default-meta = []
devtools = ["tauri/devtools"] devtools = ["tauri/devtools"]
deadlock-detection = ["parking_lot/deadlock_detection"] deadlock-detection = ["parking_lot/deadlock_detection"]
openssl_vendored = ["openssl/vendored"]

View File

@@ -56,7 +56,7 @@
"@csstools/normalize.css": "12.1.1", "@csstools/normalize.css": "12.1.1",
"@emotion/babel-plugin": "11.13.5", "@emotion/babel-plugin": "11.13.5",
"@emotion/react": "11.14.0", "@emotion/react": "11.14.0",
"@iconify/json": "2.2.379", "@iconify/json": "2.2.380",
"@monaco-editor/react": "4.7.0", "@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.85.5", "@tanstack/react-query": "5.85.5",
"@tanstack/react-router": "1.131.28", "@tanstack/react-router": "1.131.28",
@@ -71,7 +71,7 @@
"@tauri-apps/plugin-shell": "2.3.0", "@tauri-apps/plugin-shell": "2.3.0",
"@tauri-apps/plugin-updater": "2.9.0", "@tauri-apps/plugin-updater": "2.9.0",
"@types/react": "19.1.12", "@types/react": "19.1.12",
"@types/react-dom": "19.1.8", "@types/react-dom": "19.1.9",
"@types/validator": "13.15.2", "@types/validator": "13.15.2",
"@vitejs/plugin-legacy": "7.2.1", "@vitejs/plugin-legacy": "7.2.1",
"@vitejs/plugin-react": "5.0.2", "@vitejs/plugin-react": "5.0.2",
@@ -80,7 +80,7 @@
"clsx": "2.1.1", "clsx": "2.1.1",
"core-js": "3.45.1", "core-js": "3.45.1",
"filesize": "11.0.2", "filesize": "11.0.2",
"meta-json-schema": "1.19.12", "meta-json-schema": "1.19.13",
"monaco-yaml": "5.4.0", "monaco-yaml": "5.4.0",
"nanoid": "5.1.5", "nanoid": "5.1.5",
"sass-embedded": "1.90.0", "sass-embedded": "1.90.0",

View File

@@ -5,7 +5,7 @@
"mihomo_alpha": "alpha-472cefb", "mihomo_alpha": "alpha-472cefb",
"clash_rs": "v0.9.0", "clash_rs": "v0.9.0",
"clash_premium": "2023-09-05-gdcc8d87", "clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.9.0-alpha+sha.14e9e4f" "clash_rs_alpha": "0.9.0-alpha+sha.117a718"
}, },
"arch_template": { "arch_template": {
"mihomo": { "mihomo": {
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
} }
}, },
"updated_at": "2025-08-29T22:20:50.863Z" "updated_at": "2025-08-30T22:20:39.907Z"
} }

View File

@@ -343,8 +343,8 @@ importers:
specifier: 11.14.0 specifier: 11.14.0
version: 11.14.0(@types/react@19.1.12)(react@19.1.1) version: 11.14.0(@types/react@19.1.12)(react@19.1.1)
'@iconify/json': '@iconify/json':
specifier: 2.2.379 specifier: 2.2.380
version: 2.2.379 version: 2.2.380
'@monaco-editor/react': '@monaco-editor/react':
specifier: 4.7.0 specifier: 4.7.0
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -388,8 +388,8 @@ importers:
specifier: 19.1.12 specifier: 19.1.12
version: 19.1.12 version: 19.1.12
'@types/react-dom': '@types/react-dom':
specifier: 19.1.8 specifier: 19.1.9
version: 19.1.8(@types/react@19.1.12) version: 19.1.9(@types/react@19.1.12)
'@types/validator': '@types/validator':
specifier: 13.15.2 specifier: 13.15.2
version: 13.15.2 version: 13.15.2
@@ -415,8 +415,8 @@ importers:
specifier: 11.0.2 specifier: 11.0.2
version: 11.0.2 version: 11.0.2
meta-json-schema: meta-json-schema:
specifier: 1.19.12 specifier: 1.19.13
version: 1.19.12 version: 1.19.13
monaco-yaml: monaco-yaml:
specifier: 5.4.0 specifier: 5.4.0
version: 5.4.0(monaco-editor@0.52.2) version: 5.4.0(monaco-editor@0.52.2)
@@ -473,10 +473,10 @@ importers:
version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-portal': '@radix-ui/react-portal':
specifier: 1.1.9 specifier: 1.1.9
version: 1.1.9(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) version: 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-scroll-area': '@radix-ui/react-scroll-area':
specifier: 1.2.10 specifier: 1.2.10
version: 1.2.10(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) version: 1.2.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tauri-apps/api': '@tauri-apps/api':
specifier: 2.6.0 specifier: 2.6.0
version: 2.6.0 version: 2.6.0
@@ -1799,8 +1799,8 @@ packages:
prettier-plugin-ember-template-tag: prettier-plugin-ember-template-tag:
optional: true optional: true
'@iconify/json@2.2.379': '@iconify/json@2.2.380':
resolution: {integrity: sha512-PInpWLQi2C+fDIbBdVNcKOj9QKl7TT6sXqFqYMa4e34sMx206PiUlg0puWgo1Q1C/TDNQiy/raGWUbssOb1eyg==} resolution: {integrity: sha512-+Al/Q+mMB/nLz/tawmJEOkCs6+RKKVUS/Yg9I80h2yRpu0kIzxVLQRfF0NifXz/fH92vDVXbS399wio4lMVF4Q==}
'@iconify/types@2.0.0': '@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -3438,8 +3438,8 @@ packages:
'@types/prop-types@15.7.15': '@types/prop-types@15.7.15':
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
'@types/react-dom@19.1.8': '@types/react-dom@19.1.9':
resolution: {integrity: sha512-xG7xaBMJCpcK0RpN8jDbAACQo54ycO6h4dSSmgv8+fu6ZIAdANkx/WsawASUjVXYfy+J9AbUpRMNNEsXCDfDBQ==} resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==}
peerDependencies: peerDependencies:
'@types/react': ^19.0.0 '@types/react': ^19.0.0
@@ -6401,8 +6401,8 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
meta-json-schema@1.19.12: meta-json-schema@1.19.13:
resolution: {integrity: sha512-aiI0eILS8y474h5Wmmxp7GgxaFimqEc3z3L4EfZMjqec7N7q7wbXVrZHIR5mUUDHpHagZYFoT1Qf1QNrHMukcw==} resolution: {integrity: sha512-7oYti4CQ7JPGSWmSddD8bCdNvOhCyza1U+Eb8q/KfbEMT3g3M7cKSXYGS6tN3r9B2pTR5nLzevkhEy/C0DPaxw==}
engines: {node: '>=18', pnpm: '>=9'} engines: {node: '>=18', pnpm: '>=9'}
micromark-core-commonmark@2.0.1: micromark-core-commonmark@2.0.1:
@@ -10190,7 +10190,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@iconify/json@2.2.379': '@iconify/json@2.2.380':
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
pathe: 1.1.2 pathe: 1.1.2
@@ -10865,17 +10865,17 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 19.1.12 '@types/react': 19.1.12
'@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies: dependencies:
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1)
react: 19.1.1 react: 19.1.1
react-dom: 19.1.1(react@19.1.1) react-dom: 19.1.1(react@19.1.1)
optionalDependencies: optionalDependencies:
'@types/react': 19.1.12 '@types/react': 19.1.12
'@types/react-dom': 19.1.8(@types/react@19.1.12) '@types/react-dom': 19.1.9(@types/react@19.1.12)
'@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies: dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1)
@@ -10883,33 +10883,33 @@ snapshots:
react-dom: 19.1.1(react@19.1.1) react-dom: 19.1.1(react@19.1.1)
optionalDependencies: optionalDependencies:
'@types/react': 19.1.12 '@types/react': 19.1.12
'@types/react-dom': 19.1.8(@types/react@19.1.12) '@types/react-dom': 19.1.9(@types/react@19.1.12)
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies: dependencies:
'@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1) '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1)
react: 19.1.1 react: 19.1.1
react-dom: 19.1.1(react@19.1.1) react-dom: 19.1.1(react@19.1.1)
optionalDependencies: optionalDependencies:
'@types/react': 19.1.12 '@types/react': 19.1.12
'@types/react-dom': 19.1.8(@types/react@19.1.12) '@types/react-dom': 19.1.9(@types/react@19.1.12)
'@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies: dependencies:
'@radix-ui/number': 1.1.1 '@radix-ui/number': 1.1.1
'@radix-ui/primitive': 1.1.3 '@radix-ui/primitive': 1.1.3
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1)
'@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1)
'@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1) '@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1)
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1) '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1)
react: 19.1.1 react: 19.1.1
react-dom: 19.1.1(react@19.1.1) react-dom: 19.1.1(react@19.1.1)
optionalDependencies: optionalDependencies:
'@types/react': 19.1.12 '@types/react': 19.1.12
'@types/react-dom': 19.1.8(@types/react@19.1.12) '@types/react-dom': 19.1.9(@types/react@19.1.12)
'@radix-ui/react-slot@1.2.3(@types/react@19.1.12)(react@19.1.1)': '@radix-ui/react-slot@1.2.3(@types/react@19.1.12)(react@19.1.1)':
dependencies: dependencies:
@@ -11770,7 +11770,7 @@ snapshots:
'@types/prop-types@15.7.15': {} '@types/prop-types@15.7.15': {}
'@types/react-dom@19.1.8(@types/react@19.1.12)': '@types/react-dom@19.1.9(@types/react@19.1.12)':
dependencies: dependencies:
'@types/react': 19.1.12 '@types/react': 19.1.12
@@ -15170,7 +15170,7 @@ snapshots:
merge2@1.4.1: {} merge2@1.4.1: {}
meta-json-schema@1.19.12: {} meta-json-schema@1.19.13: {}
micromark-core-commonmark@2.0.1: micromark-core-commonmark@2.0.1:
dependencies: dependencies:

View File

@@ -1,2 +1,2 @@
LINUX_VERSION-5.10 = .240 LINUX_VERSION-5.10 = .241
LINUX_KERNEL_HASH-5.10.240 = 8d88c3977226d666554b75f480d1e6c5f4e4d2acdf2a3462840c6bac88634d13 LINUX_KERNEL_HASH-5.10.241 = 08c1e982064c81f8445a8fff2293d430c716c11fd3185606b8275718d696d8d6

View File

@@ -1,2 +1,2 @@
LINUX_VERSION-5.15 = .189 LINUX_VERSION-5.15 = .190
LINUX_KERNEL_HASH-5.15.189 = e3d0025b87278e14733cb326700f17c7cceb54d920622b0d5fcd58a88c6850c3 LINUX_KERNEL_HASH-5.15.190 = 62b814f1a48e1d67764a28611f23ca1f1ce1084ad7f1d319acc05720a9a68604

View File

@@ -1,2 +1,2 @@
LINUX_VERSION-5.4 = .296 LINUX_VERSION-5.4 = .297
LINUX_KERNEL_HASH-5.4.296 = 3d63614e58bf1befaba3f5713145200d09f26e564832c8948094fdf5b11fa73a LINUX_KERNEL_HASH-5.4.297 = f10cfcea7acf7588087d7cc17ebeeb7d3ff783c040536698c544eed7c5e8a841

View File

@@ -1,2 +1,2 @@
LINUX_VERSION-6.1 = .148 LINUX_VERSION-6.1 = .149
LINUX_KERNEL_HASH-6.1.148 = d9a03d3a2771c60fc726c58d3bba61123e5fd22d45ed27f6cf7a308c171180a1 LINUX_KERNEL_HASH-6.1.149 = c4e906b8d39a5866d25e06371d4de4454fbfaaeb67979a04e4137e807877be39

View File

@@ -1,2 +1,2 @@
LINUX_VERSION-6.12 = .43 LINUX_VERSION-6.12 = .44
LINUX_KERNEL_HASH-6.12.43 = 0fcbbbbcd456e87bbbfc8bf37af541fda62ccfcce76903503424fd101ef7bdee LINUX_KERNEL_HASH-6.12.44 = b650210ed3027b224969d148aa377452a9aad3ae7f2851abedd31adfef16bdae

View File

@@ -1,2 +1,2 @@
LINUX_VERSION-6.6 = .102 LINUX_VERSION-6.6 = .103
LINUX_KERNEL_HASH-6.6.102 = 80d2feb7334c30bacbe1e7dafa9ea415efb2c0ea4f4740ecbd1467cf5d94de5c LINUX_KERNEL_HASH-6.6.103 = d288dd38c3e62ba576ba6b3ad2a84cfba65cd43b702f6c50d1f701aee942b18e

View File

@@ -1,81 +0,0 @@
From 26f732791f2bcab18f59c61915bbe35225f30136 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Sat, 12 Jul 2025 16:39:21 +0100
Subject: [PATCH] Revert "leds: trigger: netdev: Configure LED blink interval
for HW offload"
This reverts commit c629c972b310af41e9e072febb6dae9a299edde6.
While .led_blink_set() would previously put an LED into an unconditional
permanently blinking state, the offending commit now uses same operation
to (also?) set the blink timing of the netdev trigger when offloading.
This breaks many if not all of the existing PHY drivers which offer
offloading LED operations, as those drivers would just put the LED into
blinking state after .led_blink_set() has been called.
Unfortunately the change even made it into stable kernels for unknown
reasons, so it should be reverted there as well.
Fixes: c629c972b310a ("leds: trigger: netdev: Configure LED blink interval for HW offload")
Link: https://lore.kernel.org/linux-leds/c6134e26-2e45-4121-aa15-58aaef327201@lunn.ch/T/#m9d6fe81bbcb273e59f12bbedbd633edd32118387
Suggested-by: Andrew Lunn <andrew@lunn.ch>
Cc: stable@vger.kernel.org
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://lore.kernel.org/r/6dcc77ee1c9676891d6250d8994850f521426a0f.1752334655.git.daniel@makrotopia.org
Signed-off-by: Lee Jones <lee@kernel.org>
---
drivers/leds/trigger/ledtrig-netdev.c | 16 +++-------------
1 file changed, 3 insertions(+), 13 deletions(-)
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -54,7 +54,6 @@ struct led_netdev_data {
unsigned int last_activity;
unsigned long mode;
- unsigned long blink_delay;
int link_speed;
u8 duplex;
@@ -70,10 +69,6 @@ static void set_baseline_state(struct le
/* Already validated, hw control is possible with the requested mode */
if (trigger_data->hw_control) {
led_cdev->hw_control_set(led_cdev, trigger_data->mode);
- if (led_cdev->blink_set) {
- led_cdev->blink_set(led_cdev, &trigger_data->blink_delay,
- &trigger_data->blink_delay);
- }
return;
}
@@ -415,11 +410,10 @@ static ssize_t interval_store(struct dev
size_t size)
{
struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
- struct led_classdev *led_cdev = trigger_data->led_cdev;
unsigned long value;
int ret;
- if (trigger_data->hw_control && !led_cdev->blink_set)
+ if (trigger_data->hw_control)
return -EINVAL;
ret = kstrtoul(buf, 0, &value);
@@ -428,13 +422,9 @@ static ssize_t interval_store(struct dev
/* impose some basic bounds on the timer interval */
if (value >= 5 && value <= 10000) {
- if (trigger_data->hw_control) {
- trigger_data->blink_delay = value;
- } else {
- cancel_delayed_work_sync(&trigger_data->work);
+ cancel_delayed_work_sync(&trigger_data->work);
- atomic_set(&trigger_data->interval, msecs_to_jiffies(value));
- }
+ atomic_set(&trigger_data->interval, msecs_to_jiffies(value));
set_baseline_state(trigger_data); /* resets timer */
}

View File

@@ -22,19 +22,11 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581
--- a/net/sched/sch_codel.c --- a/net/sched/sch_codel.c
+++ b/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c
@@ -95,11 +95,17 @@ static struct sk_buff *codel_qdisc_deque @@ -96,7 +96,12 @@ static struct sk_buff *codel_qdisc_deque
&q->stats, qdisc_pkt_len, codel_get_enqueue_time,
drop_func, dequeue_func); drop_func, dequeue_func);
- /* We cant call qdisc_tree_reduce_backlog() if our qlen is 0, if (q->stats.drop_count) {
- * or HTB crashes. Defer it for next round.
+ /* If our qlen is 0 qdisc_tree_reduce_backlog() will deactivate
+ * parent class, dequeue in parent qdisc will do the same if we
+ * return skb. Temporary increment qlen if we have skb.
*/
- if (q->stats.drop_count && sch->q.qlen) {
- qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len); - qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len);
+ if (q->stats.drop_count) {
+ if (skb) + if (skb)
+ sch->q.qlen++; + sch->q.qlen++;
+ qdisc_tree_reduce_backlog(sch, q->stats.drop_count, + qdisc_tree_reduce_backlog(sch, q->stats.drop_count,
@@ -47,9 +39,10 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581
--- a/net/sched/sch_fq_codel.c --- a/net/sched/sch_fq_codel.c
+++ b/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c
@@ -304,6 +304,21 @@ begin: @@ -304,6 +304,21 @@ begin:
skb = codel_dequeue(sch, &sch->qstats.backlog, &q->cparams,
&flow->cvars, &q->cstats, qdisc_pkt_len, &flow->cvars, &q->cstats, qdisc_pkt_len,
codel_get_enqueue_time, drop_func, dequeue_func); codel_get_enqueue_time, drop_func, dequeue_func);
+
+ /* If our qlen is 0 qdisc_tree_reduce_backlog() will deactivate + /* If our qlen is 0 qdisc_tree_reduce_backlog() will deactivate
+ * parent class, dequeue in parent qdisc will do the same if we + * parent class, dequeue in parent qdisc will do the same if we
+ * return skb. Temporary increment qlen if we have skb. + * return skb. Temporary increment qlen if we have skb.
@@ -64,18 +57,15 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581
+ q->cstats.drop_count = 0; + q->cstats.drop_count = 0;
+ q->cstats.drop_len = 0; + q->cstats.drop_len = 0;
+ } + }
+
if (!skb) { if (!skb) {
/* force a pass through old_flows to prevent starvation */ /* force a pass through old_flows to prevent starvation */
if ((head == &q->new_flows) && !list_empty(&q->old_flows)) @@ -315,13 +330,6 @@ begin:
@@ -314,15 +329,6 @@ begin:
} }
qdisc_bstats_update(sch, skb); qdisc_bstats_update(sch, skb);
flow->deficit -= qdisc_pkt_len(skb); flow->deficit -= qdisc_pkt_len(skb);
- /* We cant call qdisc_tree_reduce_backlog() if our qlen is 0, -
- * or HTB crashes. Defer it for next round. - if (q->cstats.drop_count) {
- */
- if (q->cstats.drop_count && sch->q.qlen) {
- qdisc_tree_reduce_backlog(sch, q->cstats.drop_count, - qdisc_tree_reduce_backlog(sch, q->cstats.drop_count,
- q->cstats.drop_len); - q->cstats.drop_len);
- q->cstats.drop_count = 0; - q->cstats.drop_count = 0;

View File

@@ -22,19 +22,11 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581
--- a/net/sched/sch_codel.c --- a/net/sched/sch_codel.c
+++ b/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c
@@ -95,11 +95,17 @@ static struct sk_buff *codel_qdisc_deque @@ -96,7 +96,12 @@ static struct sk_buff *codel_qdisc_deque
&q->stats, qdisc_pkt_len, codel_get_enqueue_time,
drop_func, dequeue_func); drop_func, dequeue_func);
- /* We cant call qdisc_tree_reduce_backlog() if our qlen is 0, if (q->stats.drop_count) {
- * or HTB crashes. Defer it for next round.
+ /* If our qlen is 0 qdisc_tree_reduce_backlog() will deactivate
+ * parent class, dequeue in parent qdisc will do the same if we
+ * return skb. Temporary increment qlen if we have skb.
*/
- if (q->stats.drop_count && sch->q.qlen) {
- qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len); - qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len);
+ if (q->stats.drop_count) {
+ if (skb) + if (skb)
+ sch->q.qlen++; + sch->q.qlen++;
+ qdisc_tree_reduce_backlog(sch, q->stats.drop_count, + qdisc_tree_reduce_backlog(sch, q->stats.drop_count,
@@ -47,9 +39,10 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581
--- a/net/sched/sch_fq_codel.c --- a/net/sched/sch_fq_codel.c
+++ b/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c
@@ -304,6 +304,21 @@ begin: @@ -304,6 +304,21 @@ begin:
skb = codel_dequeue(sch, &sch->qstats.backlog, &q->cparams,
&flow->cvars, &q->cstats, qdisc_pkt_len, &flow->cvars, &q->cstats, qdisc_pkt_len,
codel_get_enqueue_time, drop_func, dequeue_func); codel_get_enqueue_time, drop_func, dequeue_func);
+
+ /* If our qlen is 0 qdisc_tree_reduce_backlog() will deactivate + /* If our qlen is 0 qdisc_tree_reduce_backlog() will deactivate
+ * parent class, dequeue in parent qdisc will do the same if we + * parent class, dequeue in parent qdisc will do the same if we
+ * return skb. Temporary increment qlen if we have skb. + * return skb. Temporary increment qlen if we have skb.
@@ -64,18 +57,15 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581
+ q->cstats.drop_count = 0; + q->cstats.drop_count = 0;
+ q->cstats.drop_len = 0; + q->cstats.drop_len = 0;
+ } + }
+
if (!skb) { if (!skb) {
/* force a pass through old_flows to prevent starvation */ /* force a pass through old_flows to prevent starvation */
if ((head == &q->new_flows) && !list_empty(&q->old_flows)) @@ -315,13 +330,6 @@ begin:
@@ -314,15 +329,6 @@ begin:
} }
qdisc_bstats_update(sch, skb); qdisc_bstats_update(sch, skb);
flow->deficit -= qdisc_pkt_len(skb); flow->deficit -= qdisc_pkt_len(skb);
- /* We cant call qdisc_tree_reduce_backlog() if our qlen is 0, -
- * or HTB crashes. Defer it for next round. - if (q->cstats.drop_count) {
- */
- if (q->cstats.drop_count && sch->q.qlen) {
- qdisc_tree_reduce_backlog(sch, q->cstats.drop_count, - qdisc_tree_reduce_backlog(sch, q->cstats.drop_count,
- q->cstats.drop_len); - q->cstats.drop_len);
- q->cstats.drop_count = 0; - q->cstats.drop_count = 0;

View File

@@ -22,19 +22,11 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581
--- a/net/sched/sch_codel.c --- a/net/sched/sch_codel.c
+++ b/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c
@@ -95,11 +95,17 @@ static struct sk_buff *codel_qdisc_deque @@ -96,7 +96,12 @@ static struct sk_buff *codel_qdisc_deque
&q->stats, qdisc_pkt_len, codel_get_enqueue_time,
drop_func, dequeue_func); drop_func, dequeue_func);
- /* We cant call qdisc_tree_reduce_backlog() if our qlen is 0, if (q->stats.drop_count) {
- * or HTB crashes. Defer it for next round.
+ /* If our qlen is 0 qdisc_tree_reduce_backlog() will deactivate
+ * parent class, dequeue in parent qdisc will do the same if we
+ * return skb. Temporary increment qlen if we have skb.
*/
- if (q->stats.drop_count && sch->q.qlen) {
- qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len); - qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len);
+ if (q->stats.drop_count) {
+ if (skb) + if (skb)
+ sch->q.qlen++; + sch->q.qlen++;
+ qdisc_tree_reduce_backlog(sch, q->stats.drop_count, + qdisc_tree_reduce_backlog(sch, q->stats.drop_count,
@@ -46,10 +38,11 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581
} }
--- a/net/sched/sch_fq_codel.c --- a/net/sched/sch_fq_codel.c
+++ b/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c
@@ -305,6 +305,21 @@ begin: @@ -304,6 +304,21 @@ begin:
skb = codel_dequeue(sch, &sch->qstats.backlog, &q->cparams,
&flow->cvars, &q->cstats, qdisc_pkt_len, &flow->cvars, &q->cstats, qdisc_pkt_len,
codel_get_enqueue_time, drop_func, dequeue_func); codel_get_enqueue_time, drop_func, dequeue_func);
+
+ /* If our qlen is 0 qdisc_tree_reduce_backlog() will deactivate + /* If our qlen is 0 qdisc_tree_reduce_backlog() will deactivate
+ * parent class, dequeue in parent qdisc will do the same if we + * parent class, dequeue in parent qdisc will do the same if we
+ * return skb. Temporary increment qlen if we have skb. + * return skb. Temporary increment qlen if we have skb.
@@ -64,18 +57,15 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581
+ q->cstats.drop_count = 0; + q->cstats.drop_count = 0;
+ q->cstats.drop_len = 0; + q->cstats.drop_len = 0;
+ } + }
+
if (!skb) { if (!skb) {
/* force a pass through old_flows to prevent starvation */ /* force a pass through old_flows to prevent starvation */
if ((head == &q->new_flows) && !list_empty(&q->old_flows)) @@ -315,13 +330,6 @@ begin:
@@ -315,15 +330,6 @@ begin:
} }
qdisc_bstats_update(sch, skb); qdisc_bstats_update(sch, skb);
flow->deficit -= qdisc_pkt_len(skb); flow->deficit -= qdisc_pkt_len(skb);
- /* We cant call qdisc_tree_reduce_backlog() if our qlen is 0, -
- * or HTB crashes. Defer it for next round. - if (q->cstats.drop_count) {
- */
- if (q->cstats.drop_count && sch->q.qlen) {
- qdisc_tree_reduce_backlog(sch, q->cstats.drop_count, - qdisc_tree_reduce_backlog(sch, q->cstats.drop_count,
- q->cstats.drop_len); - q->cstats.drop_len);
- q->cstats.drop_count = 0; - q->cstats.drop_count = 0;

View File

@@ -610,7 +610,6 @@ proxies: # socks5
uuid: uuid uuid: uuid
network: tcp network: tcp
servername: example.com # AKA SNI servername: example.com # AKA SNI
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
# skip-cert-verify: true # skip-cert-verify: true
# fingerprint: xxxx # fingerprint: xxxx
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none" # client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
@@ -640,10 +639,16 @@ proxies: # socks5
network: tcp network: tcp
# ------------------------- # -------------------------
# vless encryption客户端配置 # vless encryption客户端配置
# native/xorpub 的 XTLS 可以 Splice。只使用 1-RTT 模式 / 若服务端发的 ticket 中秒数不为零则 0-RTT 复用) # native/xorpub 的 XTLS Vision 可以 Splice。只使用 1-RTT 模式 / 若服务端发的 ticket 中秒数不为零则 0-RTT 复用)
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号 # / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
#
# Padding 是可选的参数,仅作用于 1-RTT 以消除握手的长度特征,双端默认值均为 "100-111-1111.75-0-111.50-0-3333"
# 在 1-RTT client/server hello 后以 100% 的概率粘上随机 111 到 1111 字节的 padding
# 以 75% 的概率等待随机 0 到 111 毫秒("probability-from-to"
# 再次以 50% 的概率发送随机 0 到 3333 字节的 padding若为 0 则不 Write()
# 服务端、客户端可以设置不同的 padding 参数,按 len、gap 的顺序无限串联,第一个 padding 需概率 100%、至少 35 字节
# ------------------------- # -------------------------
encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(X25519 Password).(ML-KEM-768 Client)..." encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(padding len).(padding gap).(X25519 Password).(ML-KEM-768 Client)..."
tls: false #可以不开启tls tls: false #可以不开启tls
udp: true udp: true
@@ -1367,8 +1372,14 @@ listeners:
# vless encryption服务端配置 # vless encryption服务端配置
# (原生外观 / 只 XOR 公钥 / 全随机数。只允许 1-RTT 模式 / 同时允许 1-RTT 模式与 600 秒复用的 0-RTT 模式) # (原生外观 / 只 XOR 公钥 / 全随机数。只允许 1-RTT 模式 / 同时允许 1-RTT 模式与 600 秒复用的 0-RTT 模式)
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号 # / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
#
# Padding 是可选的参数,仅作用于 1-RTT 以消除握手的长度特征,双端默认值均为 "100-111-1111.75-0-111.50-0-3333"
# 在 1-RTT client/server hello 后以 100% 的概率粘上随机 111 到 1111 字节的 padding
# 以 75% 的概率等待随机 0 到 111 毫秒("probability-from-to"
# 再次以 50% 的概率发送随机 0 到 3333 字节的 padding若为 0 则不 Write()
# 服务端、客户端可以设置不同的 padding 参数,按 len、gap 的顺序无限串联,第一个 padding 需概率 100%、至少 35 字节
# ------------------------- # -------------------------
# decryption: "mlkem768x25519plus.native/xorpub/random.1rtt/600s.(X25519 PrivateKey).(ML-KEM-768 Seed)..." # decryption: "mlkem768x25519plus.native/xorpub/random.1rtt/600s.(padding len).(padding gap).(X25519 PrivateKey).(ML-KEM-768 Seed)..."
# 下面两项如果填写则开启 tls需要同时填写 # 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt # certificate: ./server.crt
# private-key: ./server.key # private-key: ./server.key

View File

@@ -99,6 +99,15 @@ func TestInboundVless_Encryption(t *testing.T) {
t.Fatal(err) t.Fatal(err)
return return
} }
paddings := []struct {
name string
data string
}{
{"unconfigured-padding", ""},
{"default-padding", "100-111-1111.75-0-111.50-0-3333."},
{"old-padding", "100-100-1000."}, // Xray-core v25.8.29
{"custom-padding", "100-1234-7890.33-0-1111.66-0-6666.55-111-777."},
}
var modes = []string{ var modes = []string{
"native", "native",
"xorpub", "xorpub",
@@ -107,19 +116,26 @@ func TestInboundVless_Encryption(t *testing.T) {
for i := range modes { for i := range modes {
mode := modes[i] mode := modes[i]
t.Run(mode, func(t *testing.T) { t.Run(mode, func(t *testing.T) {
inboundOptions := inbound.VlessOption{ t.Parallel()
Decryption: "mlkem768x25519plus." + mode + ".600s." + privateKeyBase64 + "." + seedBase64, for i := range paddings {
padding := paddings[i].data
t.Run(paddings[i].name, func(t *testing.T) {
inboundOptions := inbound.VlessOption{
Decryption: "mlkem768x25519plus." + mode + ".600s." + padding + privateKeyBase64 + "." + seedBase64,
}
outboundOptions := outbound.VlessOption{
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + padding + passwordBase64 + "." + clientBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
})
} }
outboundOptions := outbound.VlessOption{
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + passwordBase64 + "." + clientBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
}) })
} }
} }

View File

@@ -230,7 +230,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
ctx := sing.WithAdditions(context.TODO(), additions...) ctx := sing.WithAdditions(context.TODO(), additions...)
if l.decryption != nil { if l.decryption != nil {
var err error var err error
conn, err = l.decryption.Handshake(conn) conn, err = l.decryption.Handshake(conn, nil)
if err != nil { if err != nil {
return return
} }

View File

@@ -87,7 +87,11 @@ func (s *serverHandler) handle() {
_ = s.handleMessage() _ = s.handleMessage()
}() }()
<-s.quicConn.HandshakeComplete() select {
case <-s.quicConn.HandshakeComplete(): // this chan maybe not closed if handshake never complete
case <-time.After(s.quicConn.Config().HandshakeIdleTimeout): // HandshakeIdleTimeout in real conn.Config() never be zero
}
time.AfterFunc(s.AuthenticationTimeout, func() { time.AfterFunc(s.AuthenticationTimeout, func() {
if s.v4Handler != nil { if s.v4Handler != nil {
if s.v4Handler.AuthOk() { if s.v4Handler.AuthOk() {

View File

@@ -7,11 +7,23 @@ import (
"errors" "errors"
"io" "io"
"net" "net"
"runtime"
"sync" "sync"
"time" "time"
"github.com/metacubex/blake3" "github.com/metacubex/blake3"
"github.com/metacubex/utls/mlkem" "github.com/metacubex/utls/mlkem"
"golang.org/x/sys/cpu"
)
var (
// Keep in sync with crypto/tls/cipher_suites.go.
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
) )
type ClientInstance struct { type ClientInstance struct {
@@ -21,6 +33,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
@@ -28,15 +42,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
@@ -58,18 +70,18 @@ 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) {
if i.NfsPKeys == nil { if i.NfsPKeys == nil {
return nil, errors.New("uninitialized") return nil, errors.New("uninitialized")
} }
c := NewCommonConn(conn) c := NewCommonConn(conn, HasAESGCMHardwareSupport)
ivAndRealysLength := 16 + i.RelaysLength ivAndRealysLength := 16 + i.RelaysLength
pfsKeyExchangeLength := 18 + 1184 + 32 + 16 pfsKeyExchangeLength := 18 + 1184 + 32 + 16
paddingLength := int(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]
@@ -107,18 +119,18 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:]) lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:])
relays = relays[index+32:] relays = relays[index+32:]
} }
nfsGCM := NewGCM(iv, nfsKey) nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES)
if i.Seconds > 0 { if i.Seconds > 0 {
i.RWLock.RLock() i.RWLock.RLock()
if time.Now().Before(i.Expire) { if time.Now().Before(i.Expire) {
c.Client = i c.Client = i
c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection
nfsGCM.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil) nfsAEAD.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil)
nfsGCM.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil) nfsAEAD.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil)
i.RWLock.RUnlock() i.RWLock.RUnlock()
c.PreWrite = clientHello[:ivAndRealysLength+18+32] c.PreWrite = clientHello[:ivAndRealysLength+18+32]
c.GCM = NewGCM(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey) c.AEAD = NewAEAD(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey, c.UseAES)
if i.XorMode == 2 { if i.XorMode == 2 {
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16) c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16)
} }
@@ -128,26 +140,34 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
} }
pfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength] pfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength]
nfsGCM.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil) nfsAEAD.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil)
mlkem768DKey, _ := mlkem.GenerateKey768() mlkem768DKey, _ := mlkem.GenerateKey768()
x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader) x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
pfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...) pfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...)
nfsGCM.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil) nfsAEAD.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil)
padding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:] padding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:]
nfsGCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
nfsGCM.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]
return nil, err for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
if l > 0 {
if _, err := conn.Write(clientHello[:l]); err != nil {
return nil, err
}
clientHello = clientHello[l:]
}
if len(paddingGaps) > i {
time.Sleep(paddingGaps[i])
}
} }
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
encryptedPfsPublicKey := make([]byte, 1088+32+16) encryptedPfsPublicKey := make([]byte, 1088+32+16)
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
return nil, err return nil, err
} }
nfsGCM.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil) nfsAEAD.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil)
mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088]) mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088])
if err != nil { if err != nil {
return nil, err return nil, err
@@ -164,14 +184,14 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
copy(pfsKey, mlkem768Key) copy(pfsKey, mlkem768Key)
copy(pfsKey[32:], x25519Key) copy(pfsKey[32:], x25519Key)
c.UnitedKey = append(pfsKey, nfsKey...) c.UnitedKey = append(pfsKey, nfsKey...)
c.GCM = NewGCM(pfsPublicKey, c.UnitedKey) c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)
c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1088+32], c.UnitedKey) c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1088+32], c.UnitedKey, c.UseAES)
encryptedTicket := make([]byte, 32) encryptedTicket := make([]byte, 32)
if _, err := io.ReadFull(conn, encryptedTicket); err != nil { if _, err := io.ReadFull(conn, encryptedTicket); err != nil {
return nil, err return nil, err
} }
if _, err := c.PeerGCM.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil { if _, err := c.PeerAEAD.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil {
return nil, err return nil, err
} }
seconds := DecodeLength(encryptedTicket) seconds := DecodeLength(encryptedTicket)
@@ -188,7 +208,7 @@ func (i *ClientInstance) 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 _, err := c.PeerGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { if _, err := c.PeerAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
return nil, err return nil, err
} }
length := DecodeLength(encryptedLength[:2]) length := DecodeLength(encryptedLength[:2])

View File

@@ -8,29 +8,34 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strconv"
"strings"
"time" "time"
"github.com/metacubex/mihomo/common/pool" "github.com/metacubex/mihomo/common/pool"
"github.com/metacubex/blake3" "github.com/metacubex/blake3"
"github.com/metacubex/randv2" "github.com/metacubex/randv2"
"golang.org/x/crypto/chacha20poly1305"
) )
type CommonConn struct { type CommonConn struct {
net.Conn net.Conn
UseAES bool
Client *ClientInstance Client *ClientInstance
UnitedKey []byte UnitedKey []byte
PreWrite []byte PreWrite []byte
GCM *GCM AEAD *AEAD
PeerAEAD *AEAD
PeerPadding []byte PeerPadding []byte
rawInput bytes.Buffer // PeerInBytes rawInput bytes.Buffer // PeerInBytes
PeerGCM *GCM
input bytes.Reader // PeerCache input bytes.Reader // PeerCache
} }
func NewCommonConn(conn net.Conn) *CommonConn { func NewCommonConn(conn net.Conn, useAES bool) *CommonConn {
return &CommonConn{ return &CommonConn{
Conn: conn, Conn: conn,
UseAES: useAES,
} }
} }
@@ -49,12 +54,12 @@ func (c *CommonConn) Write(b []byte) (int, error) {
headerAndData := outBytes[:5+len(b)+16] headerAndData := outBytes[:5+len(b)+16]
EncodeHeader(headerAndData, len(b)+16) EncodeHeader(headerAndData, len(b)+16)
max := false max := false
if bytes.Equal(c.GCM.Nonce[:], MaxNonce) { if bytes.Equal(c.AEAD.Nonce[:], MaxNonce) {
max = true max = true
} }
c.GCM.Seal(headerAndData[:5], nil, b, headerAndData[:5]) c.AEAD.Seal(headerAndData[:5], nil, b, headerAndData[:5])
if max { if max {
c.GCM = NewGCM(headerAndData, c.UnitedKey) c.AEAD = NewAEAD(headerAndData, c.UnitedKey, c.UseAES)
} }
if c.PreWrite != nil { if c.PreWrite != nil {
headerAndData = append(c.PreWrite, headerAndData...) headerAndData = append(c.PreWrite, headerAndData...)
@@ -71,12 +76,12 @@ func (c *CommonConn) Read(b []byte) (int, error) {
if len(b) == 0 { if len(b) == 0 {
return 0, nil return 0, nil
} }
if c.PeerGCM == nil { // client's 0-RTT if c.PeerAEAD == nil { // client's 0-RTT
serverRandom := make([]byte, 16) serverRandom := make([]byte, 16)
if _, err := io.ReadFull(c.Conn, serverRandom); err != nil { if _, err := io.ReadFull(c.Conn, serverRandom); err != nil {
return 0, err return 0, err
} }
c.PeerGCM = NewGCM(serverRandom, c.UnitedKey) c.PeerAEAD = NewAEAD(serverRandom, c.UnitedKey, c.UseAES)
if xorConn, ok := c.Conn.(*XorConn); ok { if xorConn, ok := c.Conn.(*XorConn); ok {
xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom) xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom)
} }
@@ -85,7 +90,7 @@ func (c *CommonConn) Read(b []byte) (int, error) {
if _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil { if _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil {
return 0, err return 0, err
} }
if _, err := c.PeerGCM.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil { if _, err := c.PeerAEAD.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil {
return 0, err return 0, err
} }
c.PeerPadding = nil c.PeerPadding = nil
@@ -93,11 +98,11 @@ func (c *CommonConn) Read(b []byte) (int, error) {
if c.input.Len() > 0 { if c.input.Len() > 0 {
return c.input.Read(b) return c.input.Read(b)
} }
peerHeader := make([]byte, 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(peerHeader) // l: 17~17000 l, err := DecodeHeader(peerHeader[:]) // l: 17~17000
if err != nil { if err != nil {
if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT
c.Client.RWLock.Lock() c.Client.RWLock.Lock()
@@ -110,7 +115,9 @@ func (c *CommonConn) Read(b []byte) (int, error) {
return 0, err return 0, err
} }
c.Client = nil c.Client = nil
c.rawInput.Grow(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] 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
@@ -119,13 +126,13 @@ func (c *CommonConn) Read(b []byte) (int, error) {
if len(dst) <= len(b) { if len(dst) <= len(b) {
dst = b[:len(dst)] // avoids another copy() dst = b[:len(dst)] // avoids another copy()
} }
var newGCM *GCM var newAEAD *AEAD
if bytes.Equal(c.PeerGCM.Nonce[:], MaxNonce) { if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) {
newGCM = NewGCM(append(peerHeader, peerData...), c.UnitedKey) newAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES)
} }
_, err = c.PeerGCM.Open(dst[:0], nil, peerData, peerHeader) _, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:])
if newGCM != nil { if newAEAD != nil {
c.PeerGCM = newGCM c.PeerAEAD = newAEAD
} }
if err != nil { if err != nil {
return 0, err return 0, err
@@ -137,28 +144,32 @@ func (c *CommonConn) Read(b []byte) (int, error) {
return len(dst), nil return len(dst), nil
} }
type GCM struct { type AEAD struct {
cipher.AEAD cipher.AEAD
Nonce [12]byte Nonce [12]byte
} }
func NewGCM(ctx, key []byte) *GCM { func NewAEAD(ctx, key []byte, useAES bool) *AEAD {
k := make([]byte, 32) k := make([]byte, 32)
blake3.DeriveKey(k, string(ctx), key) blake3.DeriveKey(k, string(ctx), key)
block, _ := aes.NewCipher(k) var aead cipher.AEAD
aead, _ := cipher.NewGCM(block) if useAES {
return &GCM{AEAD: aead} block, _ := aes.NewCipher(k)
//chacha20poly1305.New() aead, _ = cipher.NewGCM(block)
} else {
aead, _ = chacha20poly1305.New(k)
}
return &AEAD{AEAD: aead}
} }
func (a *GCM) Seal(dst, nonce, plaintext, additionalData []byte) []byte { func (a *AEAD) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if nonce == nil { if nonce == nil {
nonce = IncreaseNonce(a.Nonce[:]) nonce = IncreaseNonce(a.Nonce[:])
} }
return a.AEAD.Seal(dst, nonce, plaintext, additionalData) return a.AEAD.Seal(dst, nonce, plaintext, additionalData)
} }
func (a *GCM) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { func (a *AEAD) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if nonce == nil { if nonce == nil {
nonce = IncreaseNonce(a.Nonce[:]) nonce = IncreaseNonce(a.Nonce[:])
} }
@@ -206,9 +217,80 @@ func DecodeHeader(h []byte) (l int, err error) {
return 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(randBetween(0, 100)) {
l = int(randBetween(int64(y[1]), int64(y[2])))
}
lens = append(lens, l)
length += l
}
for _, y := range paddingGaps {
g := 0
if y[0] >= int(randBetween(0, 100)) {
g = int(randBetween(int64(y[1]), int64(y[2])))
}
gaps = append(gaps, time.Duration(g)*time.Millisecond)
}
return
}
func max[T ~int | ~uint | ~int64 | ~uint64 | ~int32 | ~uint32 | ~int16 | ~uint16 | ~int8 | ~uint8](a, b T) T {
if a > b {
return a
}
return b
}
func randBetween(from int64, to int64) int64 { func randBetween(from int64, to int64) int64 {
if from == to { if from == to {
return from return from
} }
if to < from {
from, to = to, from
}
return from + randv2.Int64N(to-from) return from + randv2.Int64N(to-from)
} }

View File

@@ -21,4 +21,7 @@
// https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902 // https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902
// https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e // https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e
// https://github.com/XTLS/Xray-core/commit/b0b220985c9c1bc832665458d5fd6e0c287b67ae // https://github.com/XTLS/Xray-core/commit/b0b220985c9c1bc832665458d5fd6e0c287b67ae
// https://github.com/XTLS/Xray-core/commit/82ea7a3cc5ff23280b87e3052f0f83b04f0267fa
// https://github.com/XTLS/Xray-core/commit/e8b02cd6649f14889841e8ab8ee6b2acca71dbe6
// https://github.com/XTLS/Xray-core/commit/6768a22f676c9121cfc9dc4f51181a8a07837c8d
package encryption package encryption

View File

@@ -34,7 +34,12 @@ func NewClient(encryption string) (*ClientInstance, error) {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
} }
var nfsPKeysBytes [][]byte var nfsPKeysBytes [][]byte
var paddings []string
for _, r := range s[3:] { for _, r := range s[3:] {
if len(r) < 20 {
paddings = append(paddings, r)
continue
}
b, err := base64.RawURLEncoding.DecodeString(r) b, err := base64.RawURLEncoding.DecodeString(r)
if err != nil { if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
@@ -44,8 +49,9 @@ func NewClient(encryption string) (*ClientInstance, error) {
} }
nfsPKeysBytes = append(nfsPKeysBytes, b) nfsPKeysBytes = append(nfsPKeysBytes, b)
} }
padding := strings.Join(paddings, ".")
client := &ClientInstance{} client := &ClientInstance{}
if err := client.Init(nfsPKeysBytes, xorMode, seconds); err != nil { if err := client.Init(nfsPKeysBytes, xorMode, seconds, padding); err != nil {
return nil, fmt.Errorf("failed to use encryption: %w", err) return nil, fmt.Errorf("failed to use encryption: %w", err)
} }
return client, nil return client, nil
@@ -84,7 +90,12 @@ func NewServer(decryption string) (*ServerInstance, error) {
seconds = uint32(i) seconds = uint32(i)
} }
var nfsSKeysBytes [][]byte var nfsSKeysBytes [][]byte
var paddings []string
for _, r := range s[3:] { for _, r := range s[3:] {
if len(r) < 20 {
paddings = append(paddings, r)
continue
}
b, err := base64.RawURLEncoding.DecodeString(r) b, err := base64.RawURLEncoding.DecodeString(r)
if err != nil { if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
@@ -94,8 +105,9 @@ func NewServer(decryption string) (*ServerInstance, error) {
} }
nfsSKeysBytes = append(nfsSKeysBytes, b) nfsSKeysBytes = append(nfsSKeysBytes, b)
} }
padding := strings.Join(paddings, ".")
server := &ServerInstance{} server := &ServerInstance{}
if err := server.Init(nfsSKeysBytes, xorMode, seconds); err != nil { if err := server.Init(nfsSKeysBytes, xorMode, seconds, padding); err != nil {
return nil, fmt.Errorf("failed to use decryption: %w", err) return nil, fmt.Errorf("failed to use decryption: %w", err)
} }
return server, nil return server, nil

View File

@@ -29,21 +29,21 @@ type ServerInstance 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
Sessions map[[16]byte]*ServerSession Sessions map[[16]byte]*ServerSession
Closed bool Closed bool
} }
func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) { func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) {
if i.NfsSKeys != nil { 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)
@@ -87,7 +87,7 @@ func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (
} }
}() }()
} }
return return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
} }
func (i *ServerInstance) Close() (err error) { func (i *ServerInstance) Close() (err error) {
@@ -97,16 +97,19 @@ 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")
} }
c := NewCommonConn(conn) c := NewCommonConn(conn, true)
ivAndRelays := make([]byte, 16+i.RelaysLength) ivAndRelays := make([]byte, 16+i.RelaysLength)
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
@@ -150,16 +153,27 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
} }
relays = relays[32:] relays = relays[32:]
} }
nfsGCM := NewGCM(iv, nfsKey) nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES)
encryptedLength := make([]byte, 18) encryptedLength := make([]byte, 18)
if _, err := io.ReadFull(conn, encryptedLength); err != nil { if _, err := io.ReadFull(conn, encryptedLength); err != nil {
return nil, err return nil, err
} }
if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { if fallback != nil {
return nil, err *fallback = append(*fallback, encryptedLength...)
} }
length := DecodeLength(encryptedLength[:2]) decryptedLength := make([]byte, 2)
if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil {
c.UseAES = !c.UseAES
nfsAEAD = NewAEAD(iv, nfsKey, c.UseAES)
if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil {
return nil, err
}
}
if fallback != nil {
*fallback = nil
}
length := DecodeLength(decryptedLength)
if length == 32 { if length == 32 {
if i.Seconds == 0 { if i.Seconds == 0 {
@@ -169,7 +183,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
if _, err := io.ReadFull(conn, encryptedTicket); err != nil { if _, err := io.ReadFull(conn, encryptedTicket); err != nil {
return nil, err return nil, err
} }
ticket, err := nfsGCM.Open(nil, nil, encryptedTicket, nil) ticket, err := nfsAEAD.Open(nil, nil, encryptedTicket, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -177,7 +191,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, randBetween(1268, 2268)) // matches 1-RTT's server hello length for "random", though it is not important, just for example noises := make([]byte, 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)
@@ -192,8 +206,8 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
c.UnitedKey = append(s.PfsKey, nfsKey...) // the same nfsKey links the upload & download (prevents server -> client's another request) c.UnitedKey = append(s.PfsKey, nfsKey...) // the same nfsKey links the upload & download (prevents server -> client's another request)
c.PreWrite = make([]byte, 16) c.PreWrite = make([]byte, 16)
rand.Read(c.PreWrite) // always trust yourself, not the client (also prevents being parsed as TLS thus causing false interruption for "native" and "xorpub") rand.Read(c.PreWrite) // always trust yourself, not the client (also prevents being parsed as TLS thus causing false interruption for "native" and "xorpub")
c.GCM = NewGCM(c.PreWrite, c.UnitedKey) c.AEAD = NewAEAD(c.PreWrite, c.UnitedKey, c.UseAES)
c.PeerGCM = NewGCM(encryptedTicket, c.UnitedKey) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client) c.PeerAEAD = NewAEAD(encryptedTicket, c.UnitedKey, c.UseAES) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client)
if i.XorMode == 2 { if i.XorMode == 2 {
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite), NewCTR(c.UnitedKey, iv), 16, 0) // it doesn't matter if the attacker sends client's iv back to the client c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite), NewCTR(c.UnitedKey, iv), 16, 0) // it doesn't matter if the attacker sends client's iv back to the client
} }
@@ -207,7 +221,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
return nil, err return nil, err
} }
if _, err := nfsGCM.Open(encryptedPfsPublicKey[:0], nil, encryptedPfsPublicKey, nil); err != nil { if _, err := nfsAEAD.Open(encryptedPfsPublicKey[:0], nil, encryptedPfsPublicKey, nil); err != nil {
return nil, err return nil, err
} }
mlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184]) mlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184])
@@ -229,27 +243,12 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
copy(pfsKey[32:], x25519Key) copy(pfsKey[32:], x25519Key)
pfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...) pfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...)
c.UnitedKey = append(pfsKey, nfsKey...) c.UnitedKey = append(pfsKey, nfsKey...)
c.GCM = NewGCM(pfsPublicKey, c.UnitedKey) c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)
c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1184+32], c.UnitedKey) c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES)
ticket := make([]byte, 16) ticket := make([]byte, 16)
rand.Read(ticket) rand.Read(ticket)
copy(ticket, EncodeLength(int(i.Seconds*4/5))) copy(ticket, EncodeLength(int(i.Seconds*4/5)))
pfsKeyExchangeLength := 1088 + 32 + 16
encryptedTicketLength := 32
paddingLength := int(randBetween(100, 1000))
serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)
nfsGCM.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)
c.GCM.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil)
padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]
c.GCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
c.GCM.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
if _, err := conn.Write(serverHello); err != nil {
return nil, err
}
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
if i.Seconds > 0 { if i.Seconds > 0 {
i.RWLock.Lock() i.RWLock.Lock()
i.Sessions[[16]byte(ticket)] = &ServerSession{ i.Sessions[[16]byte(ticket)] = &ServerSession{
@@ -259,18 +258,41 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
i.RWLock.Unlock() i.RWLock.Unlock()
} }
pfsKeyExchangeLength := 1088 + 32 + 16
encryptedTicketLength := 32
paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)
serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)
nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)
c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil)
padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]
c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
paddingLens[0] = pfsKeyExchangeLength + encryptedTicketLength + paddingLens[0]
for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
if l > 0 {
if _, err := conn.Write(serverHello[:l]); err != nil {
return nil, err
}
serverHello = serverHello[l:]
}
if len(paddingGaps) > i {
time.Sleep(paddingGaps[i])
}
}
// important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern // important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern
if _, err := io.ReadFull(conn, encryptedLength); err != nil { if _, err := io.ReadFull(conn, encryptedLength); err != nil {
return nil, err return nil, err
} }
if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { if _, err := nfsAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
return nil, err return nil, err
} }
encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2])) encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2]))
if _, err := io.ReadFull(conn, encryptedPadding); err != nil { if _, err := io.ReadFull(conn, encryptedPadding); err != nil {
return nil, err return nil, err
} }
if _, err := nfsGCM.Open(encryptedPadding[:0], nil, encryptedPadding, nil); err != nil { if _, err := nfsAEAD.Open(encryptedPadding[:0], nil, encryptedPadding, nil); err != nil {
return nil, err return nil, err
} }

View File

@@ -185,6 +185,10 @@ if has_xray then
o.default = "10-20" o.default = "10-20"
o:depends("fragment", true) o:depends("fragment", true)
o = s_xray:option(Value, "fragment_maxSplit", translate("Max Split"), translate("Limit the maximum number of splits."))
o.default = "100-200"
o:depends("fragment", true)
o = s_xray:option(Flag, "noise", translate("Noise"), translate("UDP noise, Under some circumstances it can bypass some UDP based protocol restrictions.")) o = s_xray:option(Flag, "noise", translate("Noise"), translate("UDP noise, Under some circumstances it can bypass some UDP based protocol restrictions."))
o.default = 0 o.default = 0
@@ -238,6 +242,11 @@ if has_xray then
o = s_xray_noise:option(Value, "delay", translate("Delay (ms)")) o = s_xray_noise:option(Value, "delay", translate("Delay (ms)"))
o.datatype = "or(uinteger,portrange)" o.datatype = "or(uinteger,portrange)"
o.rmempty = false o.rmempty = false
o = s_xray_noise:option(ListValue, "applyTo", translate("IP Type"))
o:value("ip", "ALL")
o:value("ipv4", "IPv4")
o:value("ipv6", "IPv6")
end end
if has_singbox then if has_singbox then

View File

@@ -429,6 +429,18 @@ function is_ipv6(val)
return false return false
end end
function is_local_ip(ip)
ip = tostring(ip or ""):lower()
ip = ip:gsub("^[%w%d]+://", "") -- 去掉协议头
:gsub("/.*$", "") -- 去掉路径
:gsub("^%[", ""):gsub("%]$", "") -- 去掉IPv6方括号
:gsub(":%d+$", "") -- 去掉端口
return ip:match("^127%.") or ip:match("^10%.") or
ip:match("^172%.1[6-9]%.") or ip:match("^172%.2[0-9]%.") or
ip:match("^172%.3[0-1]%.") or ip:match("^192%.168%.") or
ip == "::1" or ip:match("^f[cd]") or ip:match("^fe[89ab]")
end
function is_ipv6addrport(val) function is_ipv6addrport(val)
if is_ipv6(val) then if is_ipv6(val) then
local address, port = val:match('%[(.*)%]:([^:]+)$') local address, port = val:match('%[(.*)%]:([^:]+)$')

View File

@@ -1555,6 +1555,9 @@ function gen_config(var)
end end
if remote_server.address then if remote_server.address then
if api.is_local_ip(remote_server.address) then --dns为本地ip不走代理
remote_server.detour = "direct"
end
table.insert(dns.servers, remote_server) table.insert(dns.servers, remote_server)
end end
@@ -1610,6 +1613,9 @@ function gen_config(var)
end end
if remote_server.server then if remote_server.server then
if api.is_local_ip(remote_server.server) then --dns为本地ip不走代理
remote_server.detour = "direct"
end
table.insert(dns.servers, remote_server) table.insert(dns.servers, remote_server)
end end
@@ -1759,7 +1765,9 @@ function gen_config(var)
if value.outboundTag ~= COMMON.default_outbound_tag and (remote_server.address or remote_server.server) then if value.outboundTag ~= COMMON.default_outbound_tag and (remote_server.address or remote_server.server) then
local remote_shunt_server = api.clone(remote_server) local remote_shunt_server = api.clone(remote_server)
remote_shunt_server.tag = value.outboundTag remote_shunt_server.tag = value.outboundTag
remote_shunt_server.detour = value.outboundTag local is_local = (remote_server.address and api.is_local_ip(remote_server.address)) or
(remote_server.server and api.is_local_ip(remote_server.server)) --dns为本地ip不走代理
remote_shunt_server.detour = is_local and "direct" or value.outboundTag
table.insert(dns.servers, remote_shunt_server) table.insert(dns.servers, remote_shunt_server)
dns_rule.server = remote_shunt_server.tag dns_rule.server = remote_shunt_server.tag
end end

View File

@@ -23,7 +23,8 @@ local function get_noise_packets()
local noise = (n.enabled == "1") and { local noise = (n.enabled == "1") and {
type = n.type, type = n.type,
packet = n.packet, packet = n.packet,
delay = string.find(n.delay, "-") and n.delay or tonumber(n.delay) delay = string.find(n.delay, "-") and n.delay or tonumber(n.delay),
applyTo = n.applyTo
} or nil } or nil
table.insert(noises, noise) table.insert(noises, noise)
end) end)
@@ -1370,25 +1371,37 @@ function gen_config(var)
end end
if dns_server then if dns_server then
local outboundTag, balancerTag
if not api.is_local_ip(dns_server.address) or value.outboundTag == "blackhole" then --dns为本地ip不走代理
outboundTag = value.outboundTag
balancerTag = value.balancerTag
else
outboundTag = "direct"
balancerTag = nil
end
table.insert(dns.servers, dns_server) table.insert(dns.servers, dns_server)
table.insert(routing.rules, { table.insert(routing.rules, {
inboundTag = { inboundTag = { dns_server.tag },
dns_server.tag outboundTag = outboundTag,
}, balancerTag = balancerTag
outboundTag = value.outboundTag or nil,
balancerTag = value.balancerTag or nil
}) })
end end
end end
end end
end end
local _outboundTag, _balancerTag
if not api.is_local_ip(_remote_dns.address) or dns_outbound_tag == "blackhole" then --dns为本地ip不走代理
_outboundTag = dns_outbound_tag
_balancerTag = COMMON.default_balancer_tag
else
_outboundTag = "direct"
_balancerTag = nil
end
table.insert(routing.rules, { table.insert(routing.rules, {
inboundTag = { inboundTag = { "dns-global" },
"dns-global" balancerTag = _balancerTag,
}, outboundTag = _outboundTag
balancerTag = COMMON.default_balancer_tag,
outboundTag = dns_outbound_tag
}) })
local default_rule_index = nil local default_rule_index = nil
@@ -1460,7 +1473,8 @@ function gen_config(var)
fragment = (xray_settings.fragment == "1") and { fragment = (xray_settings.fragment == "1") and {
packets = (xray_settings.fragment_packets and xray_settings.fragment_packets ~= "") and xray_settings.fragment_packets, packets = (xray_settings.fragment_packets and xray_settings.fragment_packets ~= "") and xray_settings.fragment_packets,
length = (xray_settings.fragment_length and xray_settings.fragment_length ~= "") and xray_settings.fragment_length, length = (xray_settings.fragment_length and xray_settings.fragment_length ~= "") and xray_settings.fragment_length,
interval = (xray_settings.fragment_interval and xray_settings.fragment_interval ~= "") and xray_settings.fragment_interval interval = (xray_settings.fragment_interval and xray_settings.fragment_interval ~= "") and xray_settings.fragment_interval,
maxSplit = (xray_settings.fragment_maxSplit and xray_settings.fragment_maxSplit ~= "") and xray_settings.fragment_maxSplit
} or nil, } or nil,
noises = (xray_settings.noise == "1") and get_noise_packets() or nil noises = (xray_settings.noise == "1") and get_noise_packets() or nil
}, },

View File

@@ -1759,6 +1759,12 @@ msgstr "分片间隔"
msgid "Fragmentation interval (ms)" msgid "Fragmentation interval (ms)"
msgstr "分片间隔ms" msgstr "分片间隔ms"
msgid "Max Split"
msgstr "最大分片数"
msgid "Limit the maximum number of splits."
msgstr "限制分片的最大数量。"
msgid "Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first." msgid "Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first."
msgstr "将握手数据拆分为多个 TLS 记录,提升抗封锁能力,几乎不增加延迟,建议优先启用。" msgstr "将握手数据拆分为多个 TLS 记录,提升抗封锁能力,几乎不增加延迟,建议优先启用。"
@@ -1783,6 +1789,9 @@ msgstr "数据包"
msgid "Delay (ms)" msgid "Delay (ms)"
msgstr "延迟ms" msgstr "延迟ms"
msgid "IP Type"
msgstr "IP 类型"
msgid "If is domain name, The requested domain name will be resolved to IP before connect." msgid "If is domain name, The requested domain name will be resolved to IP before connect."
msgstr "如果是域名,域名将在请求发出之前解析为 IP。" msgstr "如果是域名,域名将在请求发出之前解析为 IP。"

View File

@@ -214,14 +214,7 @@ if DNS_MODE == "socks" then
end end
-- 判断是否为本地地址 if not api.is_local_ip(w) then
local is_local = w:match("127%.0%.0%.")
or w:match("192%.168%.")
or w:match("10%.")
or w:match("172%.1[6-9]%.")
or w:match("172%.2[0-9]%.")
or w:match("172%.3[0-1]%.")
if not is_local then
server_param = server_param .. " -proxy " .. proxy_server_name server_param = server_param .. " -proxy " .. proxy_server_name
end end

View File

@@ -121,9 +121,9 @@ dependencies = [
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.19" version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
@@ -151,29 +151,29 @@ dependencies = [
[[package]] [[package]]
name = "anstyle-query" name = "anstyle-query"
version = "1.1.3" version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.9" version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"once_cell_polyfill", "once_cell_polyfill",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.98" version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
@@ -195,9 +195,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]] [[package]]
name = "async-channel" name = "async-channel"
version = "2.4.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16c74e56284d2188cabb6ad99603d1ace887a5d7e7b695d01b728155ed9ed427" checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
dependencies = [ dependencies = [
"concurrent-queue", "concurrent-queue",
"event-listener-strategy", "event-listener-strategy",
@@ -213,13 +213,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.88" version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -273,7 +273,7 @@ version = "0.72.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f" checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools", "itertools",
@@ -282,7 +282,7 @@ dependencies = [
"regex", "regex",
"rustc-hash", "rustc-hash",
"shlex", "shlex",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -293,9 +293,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.1" version = "2.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
[[package]] [[package]]
name = "bitvec" name = "bitvec"
@@ -333,9 +333,9 @@ dependencies = [
[[package]] [[package]]
name = "blocking" name = "blocking"
version = "1.6.1" version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"async-task", "async-task",
@@ -409,7 +409,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -478,9 +478,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.28" version = "1.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ad45f4f74e4e20eaa392913b7b33a7091c87e59628f4dd27888205ad888843c" checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@@ -679,9 +679,9 @@ dependencies = [
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.2" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@@ -792,7 +792,7 @@ dependencies = [
"proc-macro-error2", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -880,7 +880,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -900,7 +900,7 @@ checksum = "0b0713d5c1d52e774c5cd7bb8b043d7c0fc4f921abfb678556140bfbe6ab2364"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -966,7 +966,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -1019,9 +1019,9 @@ dependencies = [
[[package]] [[package]]
name = "event-listener" name = "event-listener"
version = "5.4.0" version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
dependencies = [ dependencies = [
"concurrent-queue", "concurrent-queue",
"parking", "parking",
@@ -1159,9 +1159,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]] [[package]]
name = "futures-lite" name = "futures-lite"
version = "2.6.0" version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"pin-project-lite", "pin-project-lite",
@@ -1175,7 +1175,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -1210,9 +1210,9 @@ dependencies = [
[[package]] [[package]]
name = "generator" name = "generator"
version = "0.8.5" version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
@@ -1256,7 +1256,7 @@ dependencies = [
"js-sys", "js-sys",
"libc", "libc",
"r-efi", "r-efi",
"wasi 0.14.2+wasi-0.2.4", "wasi 0.14.3+wasi-0.2.4",
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -1278,9 +1278,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]] [[package]]
name = "group" name = "group"
@@ -1295,9 +1295,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.11" version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -1351,9 +1351,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.4" version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
[[package]] [[package]]
name = "heapless" name = "heapless"
@@ -1568,9 +1568,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.14" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@@ -1584,7 +1584,7 @@ dependencies = [
"libc", "libc",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2 0.5.10", "socket2 0.6.0",
"system-configuration", "system-configuration",
"tokio", "tokio",
"tower-service", "tower-service",
@@ -1725,9 +1725,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.10.0" version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
@@ -1739,7 +1739,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"inotify-sys", "inotify-sys",
"libc", "libc",
] ]
@@ -1764,11 +1764,11 @@ dependencies = [
[[package]] [[package]]
name = "io-uring" name = "io-uring"
version = "0.7.8" version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"cfg-if", "cfg-if",
"libc", "libc",
] ]
@@ -1872,14 +1872,14 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.33" version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [ dependencies = [
"getrandom 0.3.3", "getrandom 0.3.3",
"libc", "libc",
@@ -1945,7 +1945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.53.2", "windows-targets 0.53.3",
] ]
[[package]] [[package]]
@@ -1960,11 +1960,11 @@ dependencies = [
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.4" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"libc", "libc",
] ]
@@ -2209,7 +2209,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@@ -2231,7 +2231,7 @@ version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"fsevent-sys", "fsevent-sys",
"inotify", "inotify",
"kqueue", "kqueue",
@@ -2319,7 +2319,7 @@ version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
@@ -2336,7 +2336,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -2347,9 +2347,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]] [[package]]
name = "openssl-src" name = "openssl-src"
version = "300.5.1+3.5.1" version = "300.5.2+3.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "735230c832b28c000e3bc117119e6466a663ec73506bc0a9907ea4187508e42a" checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4"
dependencies = [ dependencies = [
"cc", "cc",
] ]
@@ -2469,7 +2469,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -2499,7 +2499,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -2581,9 +2581,9 @@ dependencies = [
[[package]] [[package]]
name = "potential_utf" name = "potential_utf"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
dependencies = [ dependencies = [
"zerovec", "zerovec",
] ]
@@ -2631,14 +2631,14 @@ dependencies = [
"proc-macro-error-attr2", "proc-macro-error-attr2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.95" version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -2651,9 +2651,9 @@ checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
[[package]] [[package]]
name = "quinn" name = "quinn"
version = "0.11.8" version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [ dependencies = [
"bytes", "bytes",
"cfg_aliases", "cfg_aliases",
@@ -2663,7 +2663,7 @@ dependencies = [
"quinn-udp", "quinn-udp",
"rustc-hash", "rustc-hash",
"rustls", "rustls",
"socket2 0.5.10", "socket2 0.6.0",
"thiserror 2.0.16", "thiserror 2.0.16",
"tokio", "tokio",
"tracing", "tracing",
@@ -2672,9 +2672,9 @@ dependencies = [
[[package]] [[package]]
name = "quinn-proto" name = "quinn-proto"
version = "0.11.12" version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
dependencies = [ dependencies = [
"bytes", "bytes",
"getrandom 0.3.3", "getrandom 0.3.3",
@@ -2693,16 +2693,16 @@ dependencies = [
[[package]] [[package]]
name = "quinn-udp" name = "quinn-udp"
version = "0.5.13" version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [ dependencies = [
"cfg_aliases", "cfg_aliases",
"libc", "libc",
"once_cell", "once_cell",
"socket2 0.5.10", "socket2 0.6.0",
"tracing", "tracing",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -2787,18 +2787,18 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.13" version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
] ]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.5.0" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [ dependencies = [
"getrandom 0.2.16", "getrandom 0.2.16",
"libredox", "libredox",
@@ -2819,9 +2819,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.9" version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -2830,9 +2830,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.5" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
@@ -2973,9 +2973,9 @@ dependencies = [
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.25" version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
@@ -2994,22 +2994,22 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.0.7" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.28" version = "0.23.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
dependencies = [ dependencies = [
"log", "log",
"once_cell", "once_cell",
@@ -3029,7 +3029,7 @@ dependencies = [
"openssl-probe", "openssl-probe",
"rustls-pki-types", "rustls-pki-types",
"schannel", "schannel",
"security-framework 3.2.0", "security-framework 3.3.0",
] ]
[[package]] [[package]]
@@ -3044,9 +3044,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.3" version = "0.103.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
dependencies = [ dependencies = [
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
@@ -3055,9 +3055,9 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.21" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]] [[package]]
name = "ryu" name = "ryu"
@@ -3103,7 +3103,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -3125,7 +3125,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"core-foundation 0.9.4", "core-foundation 0.9.4",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@@ -3134,11 +3134,11 @@ dependencies = [
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "3.2.0" version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"core-foundation 0.10.1", "core-foundation 0.10.1",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@@ -3207,7 +3207,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -3445,9 +3445,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.5" version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -3597,9 +3597,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.104" version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3623,7 +3623,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -3638,7 +3638,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"core-foundation 0.9.4", "core-foundation 0.9.4",
"system-configuration-sys", "system-configuration-sys",
] ]
@@ -3682,25 +3682,25 @@ checksum = "3b7ad73e635dd232c2c2106d59269f59a61de421cc6b95252d2d932094ff1f40"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.20.0" version = "3.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"getrandom 0.3.3", "getrandom 0.3.3",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "terminal_size" name = "terminal_size"
version = "0.4.2" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
dependencies = [ dependencies = [
"rustix", "rustix",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -3729,7 +3729,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -3740,7 +3740,7 @@ checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -3807,9 +3807,9 @@ dependencies = [
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.9.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
dependencies = [ dependencies = [
"tinyvec_macros", "tinyvec_macros",
] ]
@@ -3848,7 +3848,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -3890,9 +3890,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.15" version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -3922,7 +3922,7 @@ version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
@@ -3978,7 +3978,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -4029,7 +4029,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -4143,9 +4143,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.17.0" version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be"
dependencies = [ dependencies = [
"getrandom 0.3.3", "getrandom 0.3.3",
"js-sys", "js-sys",
@@ -4198,11 +4198,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.14.2+wasi-0.2.4" version = "0.14.3+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
dependencies = [ dependencies = [
"wit-bindgen-rt", "wit-bindgen",
] ]
[[package]] [[package]]
@@ -4227,7 +4227,7 @@ dependencies = [
"log", "log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -4262,7 +4262,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -4338,11 +4338,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.9" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -4405,7 +4405,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -4416,7 +4416,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -4461,7 +4461,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "193cae8e647981c35bc947fdd57ba7928b1fa0d4a79305f6dd2dc55221ac35ac" checksum = "193cae8e647981c35bc947fdd57ba7928b1fa0d4a79305f6dd2dc55221ac35ac"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.3",
"widestring", "widestring",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -4508,7 +4508,7 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [ dependencies = [
"windows-targets 0.53.2", "windows-targets 0.53.3",
] ]
[[package]] [[package]]
@@ -4544,10 +4544,11 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.53.2" version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [ dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.0", "windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0", "windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0", "windows_i686_gnu 0.53.0",
@@ -4742,13 +4743,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "wit-bindgen-rt" name = "wit-bindgen"
version = "0.39.0" version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
dependencies = [
"bitflags 2.9.1",
]
[[package]] [[package]]
name = "writeable" name = "writeable"
@@ -4791,7 +4789,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
"synstructure", "synstructure",
] ]
@@ -4812,7 +4810,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -4832,7 +4830,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
"synstructure", "synstructure",
] ]
@@ -4855,9 +4853,9 @@ dependencies = [
[[package]] [[package]]
name = "zerovec" name = "zerovec"
version = "0.11.2" version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
dependencies = [ dependencies = [
"yoke", "yoke",
"zerofrom", "zerofrom",
@@ -4872,7 +4870,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.104", "syn 2.0.106",
] ]
[[package]] [[package]]

View File

@@ -185,6 +185,10 @@ if has_xray then
o.default = "10-20" o.default = "10-20"
o:depends("fragment", true) o:depends("fragment", true)
o = s_xray:option(Value, "fragment_maxSplit", translate("Max Split"), translate("Limit the maximum number of splits."))
o.default = "100-200"
o:depends("fragment", true)
o = s_xray:option(Flag, "noise", translate("Noise"), translate("UDP noise, Under some circumstances it can bypass some UDP based protocol restrictions.")) o = s_xray:option(Flag, "noise", translate("Noise"), translate("UDP noise, Under some circumstances it can bypass some UDP based protocol restrictions."))
o.default = 0 o.default = 0
@@ -238,6 +242,11 @@ if has_xray then
o = s_xray_noise:option(Value, "delay", translate("Delay (ms)")) o = s_xray_noise:option(Value, "delay", translate("Delay (ms)"))
o.datatype = "or(uinteger,portrange)" o.datatype = "or(uinteger,portrange)"
o.rmempty = false o.rmempty = false
o = s_xray_noise:option(ListValue, "applyTo", translate("IP Type"))
o:value("ip", "ALL")
o:value("ipv4", "IPv4")
o:value("ipv6", "IPv6")
end end
if has_singbox then if has_singbox then

View File

@@ -429,6 +429,18 @@ function is_ipv6(val)
return false return false
end end
function is_local_ip(ip)
ip = tostring(ip or ""):lower()
ip = ip:gsub("^[%w%d]+://", "") -- 去掉协议头
:gsub("/.*$", "") -- 去掉路径
:gsub("^%[", ""):gsub("%]$", "") -- 去掉IPv6方括号
:gsub(":%d+$", "") -- 去掉端口
return ip:match("^127%.") or ip:match("^10%.") or
ip:match("^172%.1[6-9]%.") or ip:match("^172%.2[0-9]%.") or
ip:match("^172%.3[0-1]%.") or ip:match("^192%.168%.") or
ip == "::1" or ip:match("^f[cd]") or ip:match("^fe[89ab]")
end
function is_ipv6addrport(val) function is_ipv6addrport(val)
if is_ipv6(val) then if is_ipv6(val) then
local address, port = val:match('%[(.*)%]:([^:]+)$') local address, port = val:match('%[(.*)%]:([^:]+)$')

View File

@@ -1555,6 +1555,9 @@ function gen_config(var)
end end
if remote_server.address then if remote_server.address then
if api.is_local_ip(remote_server.address) then --dns为本地ip不走代理
remote_server.detour = "direct"
end
table.insert(dns.servers, remote_server) table.insert(dns.servers, remote_server)
end end
@@ -1610,6 +1613,9 @@ function gen_config(var)
end end
if remote_server.server then if remote_server.server then
if api.is_local_ip(remote_server.server) then --dns为本地ip不走代理
remote_server.detour = "direct"
end
table.insert(dns.servers, remote_server) table.insert(dns.servers, remote_server)
end end
@@ -1759,7 +1765,9 @@ function gen_config(var)
if value.outboundTag ~= COMMON.default_outbound_tag and (remote_server.address or remote_server.server) then if value.outboundTag ~= COMMON.default_outbound_tag and (remote_server.address or remote_server.server) then
local remote_shunt_server = api.clone(remote_server) local remote_shunt_server = api.clone(remote_server)
remote_shunt_server.tag = value.outboundTag remote_shunt_server.tag = value.outboundTag
remote_shunt_server.detour = value.outboundTag local is_local = (remote_server.address and api.is_local_ip(remote_server.address)) or
(remote_server.server and api.is_local_ip(remote_server.server)) --dns为本地ip不走代理
remote_shunt_server.detour = is_local and "direct" or value.outboundTag
table.insert(dns.servers, remote_shunt_server) table.insert(dns.servers, remote_shunt_server)
dns_rule.server = remote_shunt_server.tag dns_rule.server = remote_shunt_server.tag
end end

View File

@@ -23,7 +23,8 @@ local function get_noise_packets()
local noise = (n.enabled == "1") and { local noise = (n.enabled == "1") and {
type = n.type, type = n.type,
packet = n.packet, packet = n.packet,
delay = string.find(n.delay, "-") and n.delay or tonumber(n.delay) delay = string.find(n.delay, "-") and n.delay or tonumber(n.delay),
applyTo = n.applyTo
} or nil } or nil
table.insert(noises, noise) table.insert(noises, noise)
end) end)
@@ -1370,25 +1371,37 @@ function gen_config(var)
end end
if dns_server then if dns_server then
local outboundTag, balancerTag
if not api.is_local_ip(dns_server.address) or value.outboundTag == "blackhole" then --dns为本地ip不走代理
outboundTag = value.outboundTag
balancerTag = value.balancerTag
else
outboundTag = "direct"
balancerTag = nil
end
table.insert(dns.servers, dns_server) table.insert(dns.servers, dns_server)
table.insert(routing.rules, { table.insert(routing.rules, {
inboundTag = { inboundTag = { dns_server.tag },
dns_server.tag outboundTag = outboundTag,
}, balancerTag = balancerTag
outboundTag = value.outboundTag or nil,
balancerTag = value.balancerTag or nil
}) })
end end
end end
end end
end end
local _outboundTag, _balancerTag
if not api.is_local_ip(_remote_dns.address) or dns_outbound_tag == "blackhole" then --dns为本地ip不走代理
_outboundTag = dns_outbound_tag
_balancerTag = COMMON.default_balancer_tag
else
_outboundTag = "direct"
_balancerTag = nil
end
table.insert(routing.rules, { table.insert(routing.rules, {
inboundTag = { inboundTag = { "dns-global" },
"dns-global" balancerTag = _balancerTag,
}, outboundTag = _outboundTag
balancerTag = COMMON.default_balancer_tag,
outboundTag = dns_outbound_tag
}) })
local default_rule_index = nil local default_rule_index = nil
@@ -1460,7 +1473,8 @@ function gen_config(var)
fragment = (xray_settings.fragment == "1") and { fragment = (xray_settings.fragment == "1") and {
packets = (xray_settings.fragment_packets and xray_settings.fragment_packets ~= "") and xray_settings.fragment_packets, packets = (xray_settings.fragment_packets and xray_settings.fragment_packets ~= "") and xray_settings.fragment_packets,
length = (xray_settings.fragment_length and xray_settings.fragment_length ~= "") and xray_settings.fragment_length, length = (xray_settings.fragment_length and xray_settings.fragment_length ~= "") and xray_settings.fragment_length,
interval = (xray_settings.fragment_interval and xray_settings.fragment_interval ~= "") and xray_settings.fragment_interval interval = (xray_settings.fragment_interval and xray_settings.fragment_interval ~= "") and xray_settings.fragment_interval,
maxSplit = (xray_settings.fragment_maxSplit and xray_settings.fragment_maxSplit ~= "") and xray_settings.fragment_maxSplit
} or nil, } or nil,
noises = (xray_settings.noise == "1") and get_noise_packets() or nil noises = (xray_settings.noise == "1") and get_noise_packets() or nil
}, },

View File

@@ -1759,6 +1759,12 @@ msgstr "分片间隔"
msgid "Fragmentation interval (ms)" msgid "Fragmentation interval (ms)"
msgstr "分片间隔ms" msgstr "分片间隔ms"
msgid "Max Split"
msgstr "最大分片数"
msgid "Limit the maximum number of splits."
msgstr "限制分片的最大数量。"
msgid "Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first." msgid "Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first."
msgstr "将握手数据拆分为多个 TLS 记录,提升抗封锁能力,几乎不增加延迟,建议优先启用。" msgstr "将握手数据拆分为多个 TLS 记录,提升抗封锁能力,几乎不增加延迟,建议优先启用。"
@@ -1783,6 +1789,9 @@ msgstr "数据包"
msgid "Delay (ms)" msgid "Delay (ms)"
msgstr "延迟ms" msgstr "延迟ms"
msgid "IP Type"
msgstr "IP 类型"
msgid "If is domain name, The requested domain name will be resolved to IP before connect." msgid "If is domain name, The requested domain name will be resolved to IP before connect."
msgstr "如果是域名,域名将在请求发出之前解析为 IP。" msgstr "如果是域名,域名将在请求发出之前解析为 IP。"

View File

@@ -214,14 +214,7 @@ if DNS_MODE == "socks" then
end end
-- 判断是否为本地地址 if not api.is_local_ip(w) then
local is_local = w:match("127%.0%.0%.")
or w:match("192%.168%.")
or w:match("10%.")
or w:match("172%.1[6-9]%.")
or w:match("172%.2[0-9]%.")
or w:match("172%.3[0-1]%.")
if not is_local then
server_param = server_param .. " -proxy " .. proxy_server_name server_param = server_param .. " -proxy " .. proxy_server_name
end end

View File

@@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.14.4</Version> <Version>7.14.5</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@@ -5,12 +5,12 @@
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled> <CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="System.Reactive" Version="6.0.1" /> <PackageVersion Include="System.Reactive" Version="6.0.2" />
<PackageVersion Include="coverlet.collector" Version="6.0.0" /> <PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="NUnit" Version="3.14.0" /> <PackageVersion Include="NUnit" Version="4.4.0" />
<PackageVersion Include="NUnit.Analyzers" Version="3.9.0" /> <PackageVersion Include="NUnit.Analyzers" Version="4.10.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" /> <PackageVersion Include="NUnit3TestAdapter" Version="5.1.0" />
<PackageVersion Include="Avalonia" Version="0.10.0" /> <PackageVersion Include="Avalonia" Version="0.10.0" />
<PackageVersion Include="Avalonia.Desktop" Version="0.10.0" /> <PackageVersion Include="Avalonia.Desktop" Version="0.10.0" />
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.0" /> <PackageVersion Include="Avalonia.Diagnostics" Version="0.10.0" />

View File

@@ -4,7 +4,7 @@ using Avalonia.Markup.Xaml;
namespace AvaloniaApp namespace AvaloniaApp
{ {
public class MainWindow : Window public partial class MainWindow : Window
{ {
public MainWindow() public MainWindow()
{ {

View File

@@ -10,11 +10,17 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" /> <PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NUnit" /> <PackageReference Include="NUnit" />
<PackageReference Include="NUnit.Analyzers" /> <PackageReference Include="NUnit.Analyzers">
<PackageReference Include="NUnit3TestAdapter"/> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -13,11 +13,11 @@ namespace ServiceLib.ViewModels;
public class CheckUpdateViewModel : MyReactiveObject public class CheckUpdateViewModel : MyReactiveObject
{ {
private const string _geo = "GeoFiles"; private const string _geo = "GeoFiles";
private string _v2rayN = ECoreType.v2rayN.ToString(); private readonly string _v2rayN = ECoreType.v2rayN.ToString();
private List<CheckUpdateModel> _lstUpdated = []; private List<CheckUpdateModel> _lstUpdated = [];
private static readonly string _tag = "CheckUpdateViewModel";
private IObservableCollection<CheckUpdateModel> _checkUpdateModel = new ObservableCollectionExtended<CheckUpdateModel>(); public IObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = new ObservableCollectionExtended<CheckUpdateModel>();
public IObservableCollection<CheckUpdateModel> CheckUpdateModels => _checkUpdateModel;
public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; } public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; }
[Reactive] public bool EnableCheckPreReleaseUpdate { get; set; } [Reactive] public bool EnableCheckPreReleaseUpdate { get; set; }
@@ -26,9 +26,11 @@ public class CheckUpdateViewModel : MyReactiveObject
_config = AppManager.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
CheckUpdateCmd = ReactiveCommand.CreateFromTask(async () => CheckUpdateCmd = ReactiveCommand.CreateFromTask(CheckUpdate);
CheckUpdateCmd.ThrownExceptions.Subscribe(ex =>
{ {
await CheckUpdate(); Logging.SaveLog(_tag, ex);
_ = UpdateView(_v2rayN, ex.Message);
}); });
EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate; EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate;
@@ -43,20 +45,20 @@ public class CheckUpdateViewModel : MyReactiveObject
private void RefreshCheckUpdateItems() private void RefreshCheckUpdateItems()
{ {
_checkUpdateModel.Clear(); CheckUpdateModels.Clear();
if (RuntimeInformation.ProcessArchitecture != Architecture.X86) if (RuntimeInformation.ProcessArchitecture != Architecture.X86)
{ {
_checkUpdateModel.Add(GetCheckUpdateModel(_v2rayN)); CheckUpdateModels.Add(GetCheckUpdateModel(_v2rayN));
//Not Windows and under Win10 //Not Windows and under Win10
if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10)) if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10))
{ {
_checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.Xray.ToString())); CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.Xray.ToString()));
_checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString())); CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString()));
_checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString())); CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString()));
} }
} }
_checkUpdateModel.Add(GetCheckUpdateModel(_geo)); CheckUpdateModels.Add(GetCheckUpdateModel(_geo));
} }
private CheckUpdateModel GetCheckUpdateModel(string coreType) private CheckUpdateModel GetCheckUpdateModel(string coreType)
@@ -71,7 +73,7 @@ public class CheckUpdateViewModel : MyReactiveObject
private async Task SaveSelectedCoreTypes() private async Task SaveSelectedCoreTypes()
{ {
_config.CheckUpdateItem.SelectedCoreTypes = _checkUpdateModel.Where(t => t.IsSelected == true).Select(t => t.CoreType ?? "").ToList(); _config.CheckUpdateItem.SelectedCoreTypes = CheckUpdateModels.Where(t => t.IsSelected == true).Select(t => t.CoreType ?? "").ToList();
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
} }
@@ -83,13 +85,13 @@ public class CheckUpdateViewModel : MyReactiveObject
private async Task CheckUpdateTask() private async Task CheckUpdateTask()
{ {
_lstUpdated.Clear(); _lstUpdated.Clear();
_lstUpdated = _checkUpdateModel.Where(x => x.IsSelected == true) _lstUpdated = CheckUpdateModels.Where(x => x.IsSelected == true)
.Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList(); .Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList();
await SaveSelectedCoreTypes(); await SaveSelectedCoreTypes();
for (var k = _checkUpdateModel.Count - 1; k >= 0; k--) for (var k = CheckUpdateModels.Count - 1; k >= 0; k--)
{ {
var item = _checkUpdateModel[k]; var item = CheckUpdateModels[k];
if (item.IsSelected != true) if (item.IsSelected != true)
{ {
continue; continue;
@@ -320,7 +322,7 @@ public class CheckUpdateViewModel : MyReactiveObject
public async Task UpdateViewResult(CheckUpdateModel model) public async Task UpdateViewResult(CheckUpdateModel model)
{ {
var found = _checkUpdateModel.FirstOrDefault(t => t.CoreType == model.CoreType); var found = CheckUpdateModels.FirstOrDefault(t => t.CoreType == model.CoreType);
if (found == null) if (found == null)
{ {
return; return;
@@ -328,6 +330,6 @@ public class CheckUpdateViewModel : MyReactiveObject
var itemCopy = JsonUtils.DeepCopy(found); var itemCopy = JsonUtils.DeepCopy(found);
itemCopy.Remarks = model.Remarks; itemCopy.Remarks = model.Remarks;
_checkUpdateModel.Replace(found, itemCopy); CheckUpdateModels.Replace(found, itemCopy);
} }
} }

View File

@@ -10,8 +10,7 @@ namespace ServiceLib.ViewModels;
public class ClashConnectionsViewModel : MyReactiveObject public class ClashConnectionsViewModel : MyReactiveObject
{ {
private IObservableCollection<ClashConnectionModel> _connectionItems = new ObservableCollectionExtended<ClashConnectionModel>(); public IObservableCollection<ClashConnectionModel> ConnectionItems { get; } = new ObservableCollectionExtended<ClashConnectionModel>();
public IObservableCollection<ClashConnectionModel> ConnectionItems => _connectionItems;
[Reactive] [Reactive]
public ClashConnectionModel SelectedSource { get; set; } public ClashConnectionModel SelectedSource { get; set; }
@@ -74,7 +73,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
public async Task RefreshConnections(List<ConnectionItem>? connections) public async Task RefreshConnections(List<ConnectionItem>? connections)
{ {
_connectionItems.Clear(); ConnectionItems.Clear();
var dtNow = DateTime.Now; var dtNow = DateTime.Now;
var lstModel = new List<ClashConnectionModel>(); var lstModel = new List<ClashConnectionModel>();
@@ -104,7 +103,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
return; return;
} }
_connectionItems.AddRange(lstModel); ConnectionItems.AddRange(lstModel);
} }
public async Task ClashConnectionClose(bool all) public async Task ClashConnectionClose(bool all)
@@ -121,7 +120,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
} }
else else
{ {
_connectionItems.Clear(); ConnectionItems.Clear();
} }
await ClashApiManager.Instance.ClashConnectionClose(id); await ClashApiManager.Instance.ClashConnectionClose(id);
await GetClashConnections(); await GetClashConnections();

View File

@@ -17,11 +17,8 @@ public class ClashProxiesViewModel : MyReactiveObject
private Dictionary<string, ProvidersItem>? _providers; private Dictionary<string, ProvidersItem>? _providers;
private readonly int _delayTimeout = 99999999; private readonly int _delayTimeout = 99999999;
private IObservableCollection<ClashProxyModel> _proxyGroups = new ObservableCollectionExtended<ClashProxyModel>(); public IObservableCollection<ClashProxyModel> ProxyGroups { get; } = new ObservableCollectionExtended<ClashProxyModel>();
private IObservableCollection<ClashProxyModel> _proxyDetails = new ObservableCollectionExtended<ClashProxyModel>(); public IObservableCollection<ClashProxyModel> ProxyDetails { get; } = new ObservableCollectionExtended<ClashProxyModel>();
public IObservableCollection<ClashProxyModel> ProxyGroups => _proxyGroups;
public IObservableCollection<ClashProxyModel> ProxyDetails => _proxyDetails;
[Reactive] [Reactive]
public ClashProxyModel SelectedGroup { get; set; } public ClashProxyModel SelectedGroup { get; set; }
@@ -182,7 +179,7 @@ public class ClashProxiesViewModel : MyReactiveObject
} }
var selectedName = SelectedGroup?.Name; var selectedName = SelectedGroup?.Name;
_proxyGroups.Clear(); ProxyGroups.Clear();
var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups(); var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups();
if (proxyGroups != null && proxyGroups.Count > 0) if (proxyGroups != null && proxyGroups.Count > 0)
@@ -198,7 +195,7 @@ public class ClashProxiesViewModel : MyReactiveObject
{ {
continue; continue;
} }
_proxyGroups.Add(new ClashProxyModel() ProxyGroups.Add(new ClashProxyModel()
{ {
Now = item.now, Now = item.now,
Name = item.name, Name = item.name,
@@ -214,12 +211,12 @@ public class ClashProxiesViewModel : MyReactiveObject
{ {
continue; continue;
} }
var item = _proxyGroups.FirstOrDefault(t => t.Name == kv.Key); var item = ProxyGroups.FirstOrDefault(t => t.Name == kv.Key);
if (item != null && item.Name.IsNotEmpty()) if (item != null && item.Name.IsNotEmpty())
{ {
continue; continue;
} }
_proxyGroups.Add(new ClashProxyModel() ProxyGroups.Add(new ClashProxyModel()
{ {
Now = kv.Value.now, Now = kv.Value.now,
Name = kv.Key, Name = kv.Key,
@@ -227,15 +224,15 @@ public class ClashProxiesViewModel : MyReactiveObject
}); });
} }
if (_proxyGroups != null && _proxyGroups.Count > 0) if (ProxyGroups != null && ProxyGroups.Count > 0)
{ {
if (selectedName != null && _proxyGroups.Any(t => t.Name == selectedName)) if (selectedName != null && ProxyGroups.Any(t => t.Name == selectedName))
{ {
SelectedGroup = _proxyGroups.FirstOrDefault(t => t.Name == selectedName); SelectedGroup = ProxyGroups.FirstOrDefault(t => t.Name == selectedName);
} }
else else
{ {
SelectedGroup = _proxyGroups.First(); SelectedGroup = ProxyGroups.First();
} }
} }
else else
@@ -246,7 +243,7 @@ public class ClashProxiesViewModel : MyReactiveObject
private void RefreshProxyDetails(bool c) private void RefreshProxyDetails(bool c)
{ {
_proxyDetails.Clear(); ProxyDetails.Clear();
if (!c) if (!c)
{ {
return; return;
@@ -299,7 +296,7 @@ public class ClashProxiesViewModel : MyReactiveObject
default: default:
break; break;
} }
_proxyDetails.AddRange(lstDetails); ProxyDetails.AddRange(lstDetails);
} }
private ProxiesItem? TryGetProxy(string name) private ProxiesItem? TryGetProxy(string name)
@@ -361,12 +358,12 @@ public class ClashProxiesViewModel : MyReactiveObject
await ClashApiManager.Instance.ClashSetActiveProxy(name, nameNode); await ClashApiManager.Instance.ClashSetActiveProxy(name, nameNode);
selectedProxy.now = nameNode; selectedProxy.now = nameNode;
var group = _proxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name); var group = ProxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name);
if (group != null) if (group != null)
{ {
group.Now = nameNode; group.Now = nameNode;
var group2 = JsonUtils.DeepCopy(group); var group2 = JsonUtils.DeepCopy(group);
_proxyGroups.Replace(group, group2); ProxyGroups.Replace(group, group2);
SelectedGroup = group2; SelectedGroup = group2;
} }
@@ -375,7 +372,7 @@ public class ClashProxiesViewModel : MyReactiveObject
private async Task ProxiesDelayTest(bool blAll = true) private async Task ProxiesDelayTest(bool blAll = true)
{ {
ClashApiManager.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), async (item, result) => ClashApiManager.Instance.ClashProxiesDelayTest(blAll, ProxyDetails.ToList(), async (item, result) =>
{ {
if (item == null || result.IsNullOrEmpty()) if (item == null || result.IsNullOrEmpty())
{ {
@@ -395,7 +392,7 @@ public class ClashProxiesViewModel : MyReactiveObject
public async Task ProxiesDelayTestResult(SpeedTestResult result) public async Task ProxiesDelayTestResult(SpeedTestResult result)
{ {
//UpdateHandler(false, $"{item.name}={result}"); //UpdateHandler(false, $"{item.name}={result}");
var detail = _proxyDetails.FirstOrDefault(it => it.Name == result.IndexId); var detail = ProxyDetails.FirstOrDefault(it => it.Name == result.IndexId);
if (detail == null) if (detail == null)
{ {
return; return;
@@ -417,7 +414,7 @@ public class ClashProxiesViewModel : MyReactiveObject
detail.Delay = _delayTimeout; detail.Delay = _delayTimeout;
detail.DelayName = string.Empty; detail.DelayName = string.Empty;
} }
_proxyDetails.Replace(detail, JsonUtils.DeepCopy(detail)); ProxyDetails.Replace(detail, JsonUtils.DeepCopy(detail));
} }
#endregion proxy function #endregion proxy function

View File

@@ -8,8 +8,8 @@ namespace ServiceLib.ViewModels;
public class MsgViewModel : MyReactiveObject public class MsgViewModel : MyReactiveObject
{ {
private ConcurrentQueue<string> _queueMsg = new(); private readonly ConcurrentQueue<string> _queueMsg = new();
private int _numMaxMsg = 500; private readonly int _numMaxMsg = 500;
private bool _lastMsgFilterNotAvailable; private bool _lastMsgFilterNotAvailable;
private bool _blLockShow = false; private bool _blLockShow = false;

View File

@@ -23,13 +23,9 @@ public class ProfilesViewModel : MyReactiveObject
#region ObservableCollection #region ObservableCollection
private IObservableCollection<ProfileItemModel> _profileItems = new ObservableCollectionExtended<ProfileItemModel>(); public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
public IObservableCollection<ProfileItemModel> ProfileItems => _profileItems;
private IObservableCollection<SubItem> _subItems = new ObservableCollectionExtended<SubItem>(); public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
public IObservableCollection<SubItem> SubItems => _subItems;
private IObservableCollection<ComboItem> _servers = new ObservableCollectionExtended<ComboItem>();
[Reactive] [Reactive]
public ProfileItemModel SelectedProfile { get; set; } public ProfileItemModel SelectedProfile { get; set; }
@@ -293,7 +289,7 @@ public class ProfilesViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(result.Delay); NoticeManager.Instance.Enqueue(result.Delay);
return; return;
} }
var item = _profileItems.FirstOrDefault(it => it.IndexId == result.IndexId); var item = ProfileItems.FirstOrDefault(it => it.IndexId == result.IndexId);
if (item == null) if (item == null)
{ {
return; return;
@@ -323,7 +319,7 @@ public class ProfilesViewModel : MyReactiveObject
try try
{ {
var item = _profileItems.FirstOrDefault(it => it.IndexId == update.IndexId); var item = ProfileItems.FirstOrDefault(it => it.IndexId == update.IndexId);
if (item != null) if (item != null)
{ {
item.TodayDown = Utils.HumanFy(update.TodayDown); item.TodayDown = Utils.HumanFy(update.TodayDown);
@@ -390,8 +386,8 @@ public class ProfilesViewModel : MyReactiveObject
var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter); var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter);
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? []; _lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];
_profileItems.Clear(); ProfileItems.Clear();
_profileItems.AddRange(lstModel); ProfileItems.AddRange(lstModel);
if (lstModel.Count > 0) if (lstModel.Count > 0)
{ {
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
@@ -410,21 +406,21 @@ public class ProfilesViewModel : MyReactiveObject
public async Task RefreshSubscriptions() public async Task RefreshSubscriptions()
{ {
_subItems.Clear(); SubItems.Clear();
_subItems.Add(new SubItem { Remarks = ResUI.AllGroupServers }); SubItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
foreach (var item in await AppManager.Instance.SubItems()) foreach (var item in await AppManager.Instance.SubItems())
{ {
_subItems.Add(item); SubItems.Add(item);
} }
if (_config.SubIndexId != null && _subItems.FirstOrDefault(t => t.Id == _config.SubIndexId) != null) if (_config.SubIndexId != null && SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId) != null)
{ {
SelectedSub = _subItems.FirstOrDefault(t => t.Id == _config.SubIndexId); SelectedSub = SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId);
} }
else else
{ {
SelectedSub = _subItems.First(); SelectedSub = SubItems.First();
} }
} }
@@ -548,9 +544,9 @@ public class ProfilesViewModel : MyReactiveObject
await ConfigHandler.RemoveServers(_config, lstSelected); await ConfigHandler.RemoveServers(_config, lstSelected);
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
if (lstSelected.Count == _profileItems.Count) if (lstSelected.Count == ProfileItems.Count)
{ {
_profileItems.Clear(); ProfileItems.Clear();
} }
await RefreshServers(); await RefreshServers();
if (exists) if (exists)
@@ -740,7 +736,7 @@ public class ProfilesViewModel : MyReactiveObject
public async Task MoveServerTo(int startIndex, ProfileItemModel targetItem) public async Task MoveServerTo(int startIndex, ProfileItemModel targetItem)
{ {
var targetIndex = _profileItems.IndexOf(targetItem); var targetIndex = ProfileItems.IndexOf(targetItem);
if (startIndex >= 0 && targetIndex >= 0 && startIndex != targetIndex) if (startIndex >= 0 && targetIndex >= 0 && startIndex != targetIndex)
{ {
if (await ConfigHandler.MoveServer(_config, _lstProfile, startIndex, EMove.Position, targetIndex) == 0) if (await ConfigHandler.MoveServer(_config, _lstProfile, startIndex, EMove.Position, targetIndex) == 0)
@@ -754,7 +750,7 @@ public class ProfilesViewModel : MyReactiveObject
{ {
if (actionType == ESpeedActionType.Mixedtest) if (actionType == ESpeedActionType.Mixedtest)
{ {
SelectedProfiles = _profileItems; SelectedProfiles = ProfileItems;
} }
var lstSelected = await GetProfileItems(false); var lstSelected = await GetProfileItems(false);
if (lstSelected == null) if (lstSelected == null)

View File

@@ -13,9 +13,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
[Reactive] [Reactive]
public RoutingItem SelectedRouting { get; set; } public RoutingItem SelectedRouting { get; set; }
public IObservableCollection<RulesItemModel> RulesItems { get; } = new ObservableCollectionExtended<RulesItemModel>();
private IObservableCollection<RulesItemModel> _rulesItems = new ObservableCollectionExtended<RulesItemModel>();
public IObservableCollection<RulesItemModel> RulesItems => _rulesItems;
[Reactive] [Reactive]
public RulesItemModel SelectedSource { get; set; } public RulesItemModel SelectedSource { get; set; }
@@ -101,7 +99,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
public void RefreshRulesItems() public void RefreshRulesItems()
{ {
_rulesItems.Clear(); RulesItems.Clear();
foreach (var item in _rules) foreach (var item in _rules)
{ {
@@ -118,7 +116,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
Enabled = item.Enabled, Enabled = item.Enabled,
Remarks = item.Remarks, Remarks = item.Remarks,
}; };
_rulesItems.Add(it); RulesItems.Add(it);
} }
} }

View File

@@ -9,8 +9,7 @@ public class RoutingSettingViewModel : MyReactiveObject
{ {
#region Reactive #region Reactive
private IObservableCollection<RoutingItemModel> _routingItems = new ObservableCollectionExtended<RoutingItemModel>(); public IObservableCollection<RoutingItemModel> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItemModel>();
public IObservableCollection<RoutingItemModel> RoutingItems => _routingItems;
[Reactive] [Reactive]
public RoutingItemModel SelectedSource { get; set; } public RoutingItemModel SelectedSource { get; set; }
@@ -82,7 +81,7 @@ public class RoutingSettingViewModel : MyReactiveObject
public async Task RefreshRoutingItems() public async Task RefreshRoutingItems()
{ {
_routingItems.Clear(); RoutingItems.Clear();
var routings = await AppManager.Instance.RoutingItems(); var routings = await AppManager.Instance.RoutingItems();
foreach (var item in routings) foreach (var item in routings)
@@ -98,7 +97,7 @@ public class RoutingSettingViewModel : MyReactiveObject
CustomRulesetPath4Singbox = item.CustomRulesetPath4Singbox, CustomRulesetPath4Singbox = item.CustomRulesetPath4Singbox,
Sort = item.Sort, Sort = item.Sort,
}; };
_routingItems.Add(it); RoutingItems.Add(it);
} }
} }

View File

@@ -13,11 +13,9 @@ public class StatusBarViewModel : MyReactiveObject
{ {
#region ObservableCollection #region ObservableCollection
private IObservableCollection<RoutingItem> _routingItems = new ObservableCollectionExtended<RoutingItem>(); public IObservableCollection<RoutingItem> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItem>();
public IObservableCollection<RoutingItem> RoutingItems => _routingItems;
private IObservableCollection<ComboItem> _servers = new ObservableCollectionExtended<ComboItem>(); public IObservableCollection<ComboItem> Servers { get; } = new ObservableCollectionExtended<ComboItem>();
public IObservableCollection<ComboItem> Servers => _servers;
[Reactive] [Reactive]
public RoutingItem SelectedRouting { get; set; } public RoutingItem SelectedRouting { get; set; }
@@ -295,7 +293,7 @@ public class StatusBarViewModel : MyReactiveObject
{ {
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, ""); var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, "");
_servers.Clear(); Servers.Clear();
if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit) if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit)
{ {
BlServers = false; BlServers = false;
@@ -309,7 +307,7 @@ public class StatusBarViewModel : MyReactiveObject
string name = it.GetSummary(); string name = it.GetSummary();
var item = new ComboItem() { ID = it.IndexId, Text = name }; var item = new ComboItem() { ID = it.IndexId, Text = name };
_servers.Add(item); Servers.Add(item);
if (_config.IndexId == it.IndexId) if (_config.IndexId == it.IndexId)
{ {
SelectedServer = item; SelectedServer = item;
@@ -397,13 +395,13 @@ public class StatusBarViewModel : MyReactiveObject
public async Task RefreshRoutingsMenu() public async Task RefreshRoutingsMenu()
{ {
_routingItems.Clear(); RoutingItems.Clear();
BlRouting = true; BlRouting = true;
var routings = await AppManager.Instance.RoutingItems(); var routings = await AppManager.Instance.RoutingItems();
foreach (var item in routings) foreach (var item in routings)
{ {
_routingItems.Add(item); RoutingItems.Add(item);
if (item.IsActive) if (item.IsActive)
{ {
SelectedRouting = item; SelectedRouting = item;

View File

@@ -8,8 +8,7 @@ namespace ServiceLib.ViewModels;
public class SubSettingViewModel : MyReactiveObject public class SubSettingViewModel : MyReactiveObject
{ {
private IObservableCollection<SubItem> _subItems = new ObservableCollectionExtended<SubItem>(); public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
public IObservableCollection<SubItem> SubItems => _subItems;
[Reactive] [Reactive]
public SubItem SelectedSource { get; set; } public SubItem SelectedSource { get; set; }
@@ -60,8 +59,8 @@ public class SubSettingViewModel : MyReactiveObject
public async Task RefreshSubItems() public async Task RefreshSubItems()
{ {
_subItems.Clear(); SubItems.Clear();
_subItems.AddRange(await AppManager.Instance.SubItems()); SubItems.AddRange(await AppManager.Instance.SubItems());
} }
public async Task EditSubAsync(bool blNew) public async Task EditSubAsync(bool blNew)

View File

@@ -22,4 +22,8 @@
<Style Selector="ScrollViewer"> <Style Selector="ScrollViewer">
<Setter Property="AllowAutoHide" Value="False" /> <Setter Property="AllowAutoHide" Value="False" />
</Style> </Style>
<Style Selector="TabControl">
<Setter Property="Theme" Value="{StaticResource LineTabControl}" />
</Style>
</Styles> </Styles>

View File

@@ -59,7 +59,7 @@
x:Name="cmbDirectDNS" x:Name="cmbDirectDNS"
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
Width="200" Width="300"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Text="{Binding DirectDNS, Mode=TwoWay}" /> Text="{Binding DirectDNS, Mode=TwoWay}" />
@@ -73,7 +73,7 @@
x:Name="cmbRemoteDNS" x:Name="cmbRemoteDNS"
Grid.Row="2" Grid.Row="2"
Grid.Column="1" Grid.Column="1"
Width="200" Width="300"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Text="{Binding RemoteDNS, Mode=TwoWay}" /> Text="{Binding RemoteDNS, Mode=TwoWay}" />
@@ -87,7 +87,7 @@
x:Name="cmbSBResolverDNS" x:Name="cmbSBResolverDNS"
Grid.Row="3" Grid.Row="3"
Grid.Column="1" Grid.Column="1"
Width="200" Width="300"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Text="{Binding SingboxOutboundsResolveDNS, Mode=TwoWay}" /> Text="{Binding SingboxOutboundsResolveDNS, Mode=TwoWay}" />
<TextBlock <TextBlock
@@ -108,7 +108,7 @@
x:Name="cmbSBFinalResolverDNS" x:Name="cmbSBFinalResolverDNS"
Grid.Row="4" Grid.Row="4"
Grid.Column="1" Grid.Column="1"
Width="200" Width="300"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Text="{Binding SingboxFinalResolveDNS, Mode=TwoWay}" /> Text="{Binding SingboxFinalResolveDNS, Mode=TwoWay}" />
<TextBlock <TextBlock

View File

@@ -130,10 +130,6 @@
Width="*" Width="*"
Binding="{Binding Url}" Binding="{Binding Url}"
Header="{x:Static resx:ResUI.LvUrl}" /> Header="{x:Static resx:ResUI.LvUrl}" />
<DataGridTextColumn
Width="300"
Binding="{Binding CustomIcon}"
Header="{x:Static resx:ResUI.LvCustomIcon}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</TabItem> </TabItem>

View File

@@ -80,7 +80,7 @@
x:Name="cmbDirectDNS" x:Name="cmbDirectDNS"
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
Width="200" Width="300"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
IsEditable="True" IsEditable="True"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
@@ -96,7 +96,7 @@
x:Name="cmbRemoteDNS" x:Name="cmbRemoteDNS"
Grid.Row="2" Grid.Row="2"
Grid.Column="1" Grid.Column="1"
Width="200" Width="300"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
IsEditable="True" IsEditable="True"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
@@ -112,7 +112,7 @@
x:Name="cmbSBResolverDNS" x:Name="cmbSBResolverDNS"
Grid.Row="3" Grid.Row="3"
Grid.Column="1" Grid.Column="1"
Width="200" Width="300"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
IsEditable="True" IsEditable="True"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
@@ -136,7 +136,7 @@
x:Name="cmbSBFinalResolverDNS" x:Name="cmbSBFinalResolverDNS"
Grid.Row="4" Grid.Row="4"
Grid.Column="1" Grid.Column="1"
Width="200" Width="300"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
IsEditable="True" IsEditable="True"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />

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()
} }

View File

@@ -19,7 +19,7 @@ import (
var ( var (
Version_x byte = 25 Version_x byte = 25
Version_y byte = 8 Version_y byte = 8
Version_z byte = 29 Version_z byte = 31
) )
var ( var (

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)
@@ -107,12 +107,21 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
} }
config.Seconds = uint32(i) config.Seconds = uint32(i)
} }
for i := 3; i < len(s); i++ { padding := 0
if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 64 { 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 +130,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 +263,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 == "" {

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,
} }
} }

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 (

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;
} }

View File

@@ -172,7 +172,7 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A
} }
// XtlsRead filter and read xtls protocol // XtlsRead filter and read xtls protocol
func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, peerCache *[]byte, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error { func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error {
err := func() error { err := func() error {
for { for {
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
@@ -194,23 +194,17 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer,
if !buffer.IsEmpty() { if !buffer.IsEmpty() {
timer.Update() timer.Update()
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
// XTLS Vision processes struct Encryption Conn's peerCache or TLS Conn's input and rawInput // XTLS Vision processes TLS-like conn's input and rawInput
if peerCache != nil { if inputBuffer, err := buf.ReadFrom(input); err == nil && !inputBuffer.IsEmpty() {
if len(*peerCache) != 0 { buffer, _ = buf.MergeMulti(buffer, inputBuffer)
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 rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil && !rawInputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, rawInputBuffer)
}
*input = bytes.Reader{} // release memory
input = nil
*rawInput = bytes.Buffer{} // release memory
rawInput = nil
} }
if werr := writer.WriteMultiBuffer(buffer); werr != nil { if werr := writer.WriteMultiBuffer(buffer); werr != nil {
return werr return werr

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]
return nil, err for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
if l > 0 {
if _, err := conn.Write(clientHello[:l]); err != nil {
return nil, err
}
clientHello = clientHello[l:]
}
if len(paddingGaps) > i {
time.Sleep(paddingGaps[i])
}
} }
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
encryptedPfsPublicKey := make([]byte, 1088+32+16) encryptedPfsPublicKey := make([]byte, 1088+32+16)
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {

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
} }

View File

@@ -30,21 +30,21 @@ type ServerInstance 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
Sessions map[[16]byte]*ServerSession Sessions map[[16]byte]*ServerSession
Closed bool Closed bool
} }
func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) { func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) {
if i.NfsSKeys != nil { 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)
@@ -88,7 +88,7 @@ func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (
} }
}() }()
} }
return return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
} }
func (i *ServerInstance) Close() (err error) { func (i *ServerInstance) Close() (err error) {
@@ -98,7 +98,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 +108,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
@@ -157,6 +160,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,6 +171,9 @@ 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 {
@@ -183,7 +192,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,25 +246,10 @@ 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) ticket := make([]byte, 16)
rand.Read(ticket) rand.Read(ticket)
copy(ticket, EncodeLength(int(i.Seconds*4/5))) copy(ticket, EncodeLength(int(i.Seconds*4/5)))
pfsKeyExchangeLength := 1088 + 32 + 16
encryptedTicketLength := 32
paddingLength := int(crypto.RandBetween(100, 1000))
serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)
nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)
c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil)
padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]
c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
if _, err := conn.Write(serverHello); err != nil {
return nil, err
}
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
if i.Seconds > 0 { if i.Seconds > 0 {
i.RWLock.Lock() i.RWLock.Lock()
i.Sessions[[16]byte(ticket)] = &ServerSession{ i.Sessions[[16]byte(ticket)] = &ServerSession{
@@ -265,6 +259,29 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
i.RWLock.Unlock() i.RWLock.Unlock()
} }
pfsKeyExchangeLength := 1088 + 32 + 16
encryptedTicketLength := 32
paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)
serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)
nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)
c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil)
padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]
c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
paddingLens[0] = pfsKeyExchangeLength + encryptedTicketLength + paddingLens[0]
for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
if l > 0 {
if _, err := conn.Write(serverHello[:l]); err != nil {
return nil, err
}
serverHello = serverHello[l:]
}
if len(paddingGaps) > i {
time.Sleep(paddingGaps[i])
}
}
// important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern // important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern
if _, err := io.ReadFull(conn, encryptedLength); err != nil { if _, err := io.ReadFull(conn, encryptedLength); err != nil {
return nil, err return nil, err

View File

@@ -116,6 +116,7 @@ type Config struct {
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"` Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"`
Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"`
} }
func (x *Config) Reset() { func (x *Config) Reset() {
@@ -183,6 +184,13 @@ func (x *Config) GetSeconds() uint32 {
return 0 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 +207,7 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{
0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 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, 0xee, 0x01,
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,
@@ -213,14 +221,16 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{
0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 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, 0x18, 0x0a, 0x07, 0x73, 0x65,
0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63,
0x6f, 0x6e, 0x64, 0x73, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18,
0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x6a,
0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79,
0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01,
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c,
0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78,
0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 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 (

View File

@@ -24,4 +24,5 @@ message Config {
string decryption = 3; string decryption = 3;
uint32 xorMode = 4; uint32 xorMode = 4;
uint32 seconds = 5; uint32 seconds = 5;
string padding = 6;
} }

View File

@@ -92,7 +92,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.Seconds, 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 +220,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 +490,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 +502,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.IsRAWTransport(iConn) {
inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice
}
t = reflect.TypeOf(commonConn).Elem()
p = uintptr(unsafe.Pointer(commonConn))
} else if tlsConn, ok := iConn.(*tls.Conn); ok {
if tlsConn.ConnectionState().Version != gotls.VersionTLS13 { 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()
} }
@@ -579,7 +576,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice
clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx1) clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx1)
err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, peerCache, input, rawInput, trafficState, nil, true, ctx1) err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, input, rawInput, trafficState, nil, true, ctx1)
} else { } else {
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer // from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)) err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))

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.IsRAWTransport(iConn) {
ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice
}
t = reflect.TypeOf(commonConn).Elem()
p = uintptr(unsafe.Pointer(commonConn))
} else if tlsConn, ok := iConn.(*tls.Conn); ok {
t = reflect.TypeOf(tlsConn.Conn).Elem() 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 {
@@ -306,7 +303,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, input, rawInput, trafficState, ob, 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))

View File

@@ -2288,6 +2288,10 @@ from .varzesh3 import Varzesh3IE
from .vbox7 import Vbox7IE from .vbox7 import Vbox7IE
from .veo import VeoIE from .veo import VeoIE
from .vesti import VestiIE from .vesti import VestiIE
from .vevo import (
VevoIE,
VevoPlaylistIE,
)
from .vgtv import ( from .vgtv import (
VGTVIE, VGTVIE,
BTArticleIE, BTArticleIE,

View File

@@ -90,6 +90,10 @@ class DisneyIE(InfoExtractor):
webpage, 'embed data'), video_id) webpage, 'embed data'), video_id)
video_data = page_data['video'] video_data = page_data['video']
for external in video_data.get('externals', []):
if external.get('source') == 'vevo':
return self.url_result('vevo:' + external['data_id'], 'Vevo')
video_id = video_data['id'] video_id = video_data['id']
title = video_data['title'] title = video_data['title']

View File

@@ -111,8 +111,12 @@ class MySpaceIE(InfoExtractor):
search_data('stream-url'), search_data('hls-stream-url'), search_data('stream-url'), search_data('hls-stream-url'),
search_data('http-stream-url')) search_data('http-stream-url'))
if not formats: if not formats:
vevo_id = search_data('vevo-id')
youtube_id = search_data('youtube-id') youtube_id = search_data('youtube-id')
if youtube_id: if vevo_id:
self.to_screen(f'Vevo video detected: {vevo_id}')
return self.url_result(f'vevo:{vevo_id}', ie='Vevo')
elif youtube_id:
self.to_screen(f'Youtube video detected: {youtube_id}') self.to_screen(f'Youtube video detected: {youtube_id}')
return self.url_result(youtube_id, ie='Youtube') return self.url_result(youtube_id, ie='Youtube')
else: else:

View File

@@ -0,0 +1,352 @@
import json
import re
from .common import InfoExtractor
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
int_or_none,
parse_iso8601,
parse_qs,
)
class VevoBaseIE(InfoExtractor):
def _extract_json(self, webpage, video_id):
return self._parse_json(
self._search_regex(
r'window\.__INITIAL_STORE__\s*=\s*({.+?});\s*</script>',
webpage, 'initial store'),
video_id)
class VevoIE(VevoBaseIE):
"""
Accepts urls from vevo.com or in the format 'vevo:{id}'
(currently used by MTVIE and MySpaceIE)
"""
_VALID_URL = r'''(?x)
(?:https?://(?:www\.)?vevo\.com/watch/(?!playlist|genre)(?:[^/]+/(?:[^/]+/)?)?|
https?://cache\.vevo\.com/m/html/embed\.html\?video=|
https?://videoplayer\.vevo\.com/embed/embedded\?videoId=|
https?://embed\.vevo\.com/.*?[?&]isrc=|
https?://tv\.vevo\.com/watch/artist/(?:[^/]+)/|
vevo:)
(?P<id>[^&?#]+)'''
_EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:cache\.)?vevo\.com/.+?)\1']
_TESTS = [{
'url': 'http://www.vevo.com/watch/hurts/somebody-to-die-for/GB1101300280',
'md5': '95ee28ee45e70130e3ab02b0f579ae23',
'info_dict': {
'id': 'GB1101300280',
'ext': 'mp4',
'title': 'Hurts - Somebody to Die For',
'timestamp': 1372057200,
'upload_date': '20130624',
'uploader': 'Hurts',
'track': 'Somebody to Die For',
'artist': 'Hurts',
'genre': 'Pop',
},
'expected_warnings': ['Unable to download SMIL file', 'Unable to download info'],
}, {
'note': 'v3 SMIL format',
'url': 'http://www.vevo.com/watch/cassadee-pope/i-wish-i-could-break-your-heart/USUV71302923',
'md5': 'f6ab09b034f8c22969020b042e5ac7fc',
'info_dict': {
'id': 'USUV71302923',
'ext': 'mp4',
'title': 'Cassadee Pope - I Wish I Could Break Your Heart',
'timestamp': 1392796919,
'upload_date': '20140219',
'uploader': 'Cassadee Pope',
'track': 'I Wish I Could Break Your Heart',
'artist': 'Cassadee Pope',
'genre': 'Country',
},
'expected_warnings': ['Unable to download SMIL file', 'Unable to download info'],
}, {
'note': 'Age-limited video',
'url': 'https://www.vevo.com/watch/justin-timberlake/tunnel-vision-explicit/USRV81300282',
'info_dict': {
'id': 'USRV81300282',
'ext': 'mp4',
'title': 'Justin Timberlake - Tunnel Vision (Explicit)',
'age_limit': 18,
'timestamp': 1372888800,
'upload_date': '20130703',
'uploader': 'Justin Timberlake',
'track': 'Tunnel Vision (Explicit)',
'artist': 'Justin Timberlake',
'genre': 'Pop',
},
'expected_warnings': ['Unable to download SMIL file', 'Unable to download info'],
}, {
'note': 'No video_info',
'url': 'http://www.vevo.com/watch/k-camp-1/Till-I-Die/USUV71503000',
'md5': '8b83cc492d72fc9cf74a02acee7dc1b0',
'info_dict': {
'id': 'USUV71503000',
'ext': 'mp4',
'title': 'K Camp ft. T.I. - Till I Die',
'age_limit': 18,
'timestamp': 1449468000,
'upload_date': '20151207',
'uploader': 'K Camp',
'track': 'Till I Die',
'artist': 'K Camp',
'genre': 'Hip-Hop',
},
'expected_warnings': ['Unable to download SMIL file', 'Unable to download info'],
}, {
'note': 'Featured test',
'url': 'https://www.vevo.com/watch/lemaitre/Wait/USUV71402190',
'md5': 'd28675e5e8805035d949dc5cf161071d',
'info_dict': {
'id': 'USUV71402190',
'ext': 'mp4',
'title': 'Lemaitre ft. LoLo - Wait',
'age_limit': 0,
'timestamp': 1413432000,
'upload_date': '20141016',
'uploader': 'Lemaitre',
'track': 'Wait',
'artist': 'Lemaitre',
'genre': 'Electronic',
},
'expected_warnings': ['Unable to download SMIL file', 'Unable to download info'],
}, {
'note': 'Only available via webpage',
'url': 'http://www.vevo.com/watch/GBUV71600656',
'md5': '67e79210613865b66a47c33baa5e37fe',
'info_dict': {
'id': 'GBUV71600656',
'ext': 'mp4',
'title': 'ABC - Viva Love',
'age_limit': 0,
'timestamp': 1461830400,
'upload_date': '20160428',
'uploader': 'ABC',
'track': 'Viva Love',
'artist': 'ABC',
'genre': 'Pop',
},
'expected_warnings': ['Failed to download video versions info'],
}, {
# no genres available
'url': 'http://www.vevo.com/watch/INS171400764',
'only_matching': True,
}, {
# Another case available only via the webpage; using streams/streamsV3 formats
# Geo-restricted to Netherlands/Germany
'url': 'http://www.vevo.com/watch/boostee/pop-corn-clip-officiel/FR1A91600909',
'only_matching': True,
}, {
'url': 'https://embed.vevo.com/?isrc=USH5V1923499&partnerId=4d61b777-8023-4191-9ede-497ed6c24647&partnerAdCode=',
'only_matching': True,
}, {
'url': 'https://tv.vevo.com/watch/artist/janet-jackson/US0450100550',
'only_matching': True,
}]
_VERSIONS = {
0: 'youtube', # only in AuthenticateVideo videoVersions
1: 'level3',
2: 'akamai',
3: 'level3',
4: 'amazon',
}
def _initialize_api(self, video_id):
webpage = self._download_webpage(
'https://accounts.vevo.com/token', None,
note='Retrieving oauth token',
errnote='Unable to retrieve oauth token',
data=json.dumps({
'client_id': 'SPupX1tvqFEopQ1YS6SS',
'grant_type': 'urn:vevo:params:oauth:grant-type:anonymous',
}).encode(),
headers={
'Content-Type': 'application/json',
})
if re.search(r'(?i)THIS PAGE IS CURRENTLY UNAVAILABLE IN YOUR REGION', webpage):
self.raise_geo_restricted(
f'{self.IE_NAME} said: This page is currently unavailable in your region')
auth_info = self._parse_json(webpage, video_id)
self._api_url_template = self.http_scheme() + '//apiv2.vevo.com/%s?token=' + auth_info['legacy_token']
def _call_api(self, path, *args, **kwargs):
try:
data = self._download_json(self._api_url_template % path, *args, **kwargs)
except ExtractorError as e:
if isinstance(e.cause, HTTPError):
errors = self._parse_json(e.cause.response.read().decode(), None)['errors']
error_message = ', '.join([error['message'] for error in errors])
raise ExtractorError(f'{self.IE_NAME} said: {error_message}', expected=True)
raise
return data
def _real_extract(self, url):
video_id = self._match_id(url)
self._initialize_api(video_id)
video_info = self._call_api(
f'video/{video_id}', video_id, 'Downloading api video info',
'Failed to download video info')
video_versions = self._call_api(
f'video/{video_id}/streams', video_id,
'Downloading video versions info',
'Failed to download video versions info',
fatal=False)
# Some videos are only available via webpage (e.g.
# https://github.com/ytdl-org/youtube-dl/issues/9366)
if not video_versions:
webpage = self._download_webpage(url, video_id)
json_data = self._extract_json(webpage, video_id)
if 'streams' in json_data.get('default', {}):
video_versions = json_data['default']['streams'][video_id][0]
else:
video_versions = [
value
for key, value in json_data['apollo']['data'].items()
if key.startswith(f'{video_id}.streams')]
uploader = None
artist = None
featured_artist = None
artists = video_info.get('artists')
for curr_artist in artists:
if curr_artist.get('role') == 'Featured':
featured_artist = curr_artist['name']
else:
artist = uploader = curr_artist['name']
formats = []
for video_version in video_versions:
version = self._VERSIONS.get(video_version.get('version'), 'generic')
version_url = video_version.get('url')
if not version_url:
continue
if '.ism' in version_url:
continue
elif '.mpd' in version_url:
formats.extend(self._extract_mpd_formats(
version_url, video_id, mpd_id=f'dash-{version}',
note=f'Downloading {version} MPD information',
errnote=f'Failed to download {version} MPD information',
fatal=False))
elif '.m3u8' in version_url:
formats.extend(self._extract_m3u8_formats(
version_url, video_id, 'mp4', 'm3u8_native',
m3u8_id=f'hls-{version}',
note=f'Downloading {version} m3u8 information',
errnote=f'Failed to download {version} m3u8 information',
fatal=False))
else:
m = re.search(r'''(?xi)
_(?P<quality>[a-z0-9]+)
_(?P<width>[0-9]+)x(?P<height>[0-9]+)
_(?P<vcodec>[a-z0-9]+)
_(?P<vbr>[0-9]+)
_(?P<acodec>[a-z0-9]+)
_(?P<abr>[0-9]+)
\.(?P<ext>[a-z0-9]+)''', version_url)
if not m:
continue
formats.append({
'url': version_url,
'format_id': f'http-{version}-{video_version.get("quality") or m.group("quality")}',
'vcodec': m.group('vcodec'),
'acodec': m.group('acodec'),
'vbr': int(m.group('vbr')),
'abr': int(m.group('abr')),
'ext': m.group('ext'),
'width': int(m.group('width')),
'height': int(m.group('height')),
})
track = video_info['title']
if featured_artist:
artist = f'{artist} ft. {featured_artist}'
title = f'{artist} - {track}' if artist else track
genres = video_info.get('genres')
genre = (
genres[0] if genres and isinstance(genres, list)
and isinstance(genres[0], str) else None)
is_explicit = video_info.get('isExplicit')
if is_explicit is True:
age_limit = 18
elif is_explicit is False:
age_limit = 0
else:
age_limit = None
return {
'id': video_id,
'title': title,
'formats': formats,
'thumbnail': video_info.get('imageUrl') or video_info.get('thumbnailUrl'),
'timestamp': parse_iso8601(video_info.get('releaseDate')),
'uploader': uploader,
'duration': int_or_none(video_info.get('duration')),
'view_count': int_or_none(video_info.get('views', {}).get('total')),
'age_limit': age_limit,
'track': track,
'artist': uploader,
'genre': genre,
}
class VevoPlaylistIE(VevoBaseIE):
_VALID_URL = r'https?://(?:www\.)?vevo\.com/watch/(?P<kind>playlist|genre)/(?P<id>[^/?#&]+)'
_TESTS = [{
'url': 'http://www.vevo.com/watch/genre/rock',
'info_dict': {
'id': 'rock',
'title': 'Rock',
},
'playlist_count': 20,
}, {
'url': 'http://www.vevo.com/watch/genre/rock?index=0',
'only_matching': True,
}]
def _real_extract(self, url):
mobj = self._match_valid_url(url)
playlist_id = mobj.group('id')
playlist_kind = mobj.group('kind')
webpage = self._download_webpage(url, playlist_id)
qs = parse_qs(url)
index = qs.get('index', [None])[0]
if index:
video_id = self._search_regex(
r'<meta[^>]+content=(["\'])vevo://video/(?P<id>.+?)\1[^>]*>',
webpage, 'video id', default=None, group='id')
if video_id:
return self.url_result(f'vevo:{video_id}', VevoIE.ie_key())
playlists = self._extract_json(webpage, playlist_id)['default'][f'{playlist_kind}s']
playlist = (next(iter(playlists.values()))
if playlist_kind == 'playlist' else playlists[playlist_id])
entries = [
self.url_result(f'vevo:{src}', VevoIE.ie_key())
for src in playlist['isrcs']]
return self.playlist_result(
entries, playlist.get('playlistId') or playlist_id,
playlist.get('name'), playlist.get('description'))