From c82a70acdbac7c8b321fdcfd87a219b2b94c0ae9 Mon Sep 17 00:00:00 2001 From: "github-action[bot]" Date: Sun, 31 Aug 2025 20:34:09 +0200 Subject: [PATCH] Update On Sun Aug 31 20:34:09 CEST 2025 --- .github/update.log | 1 + clash-meta/docs/config.yaml | 19 +- clash-meta/listener/inbound/vless_test.go | 38 +- clash-meta/listener/sing_vless/server.go | 2 +- clash-meta/transport/tuic/server.go | 6 +- .../transport/vless/encryption/client.go | 68 ++-- .../transport/vless/encryption/common.go | 138 +++++-- clash-meta/transport/vless/encryption/doc.go | 3 + .../transport/vless/encryption/factory.go | 16 +- .../transport/vless/encryption/server.go | 96 +++-- .../.github/workflows/deps-build-macos.yaml | 2 +- .../.github/workflows/macos-aarch64.yaml | 2 +- clash-nyanpasu/backend/Cargo.lock | 121 +----- clash-nyanpasu/backend/tauri/Cargo.toml | 21 +- clash-nyanpasu/frontend/nyanpasu/package.json | 6 +- clash-nyanpasu/manifest/version.json | 4 +- clash-nyanpasu/pnpm-lock.yaml | 56 +-- lede/include/kernel-5.10 | 4 +- lede/include/kernel-5.15 | 4 +- lede/include/kernel-5.4 | 4 +- lede/include/kernel-6.1 | 4 +- lede/include/kernel-6.12 | 4 +- lede/include/kernel-6.6 | 4 +- ...ger-netdev-Configure-LED-blink-inter.patch | 81 ---- ...del-do-not-defer-queue-length-update.patch | 26 +- ...del-do-not-defer-queue-length-update.patch | 26 +- ...del-do-not-defer-queue-length-update.patch | 28 +- mihomo/docs/config.yaml | 19 +- mihomo/listener/inbound/vless_test.go | 38 +- mihomo/listener/sing_vless/server.go | 2 +- mihomo/transport/tuic/server.go | 6 +- mihomo/transport/vless/encryption/client.go | 68 ++-- mihomo/transport/vless/encryption/common.go | 138 +++++-- mihomo/transport/vless/encryption/doc.go | 3 + mihomo/transport/vless/encryption/factory.go | 16 +- mihomo/transport/vless/encryption/server.go | 96 +++-- .../model/cbi/passwall/client/other.lua | 9 + .../luci-app-passwall/luasrc/passwall/api.lua | 12 + .../luasrc/passwall/util_sing-box.lua | 10 +- .../luasrc/passwall/util_xray.lua | 38 +- .../luci-app-passwall/po/zh-cn/passwall.po | 9 + .../share/passwall/helper_smartdns_add.lua | 9 +- shadowsocks-rust/Cargo.lock | 318 ++++++++-------- .../model/cbi/passwall/client/other.lua | 9 + .../luci-app-passwall/luasrc/passwall/api.lua | 12 + .../luasrc/passwall/util_sing-box.lua | 10 +- .../luasrc/passwall/util_xray.lua | 38 +- small/luci-app-passwall/po/zh-cn/passwall.po | 9 + .../share/passwall/helper_smartdns_add.lua | 9 +- v2rayn/v2rayN/Directory.Build.props | 2 +- .../src/Directory.Packages.props | 12 +- .../src/Examples/Avalonia/MainWindow.axaml.cs | 2 +- .../GlobalHotKeys.Test.csproj | 12 +- .../ViewModels/CheckUpdateViewModel.cs | 36 +- .../ViewModels/ClashConnectionsViewModel.cs | 9 +- .../ViewModels/ClashProxiesViewModel.cs | 37 +- .../ServiceLib/ViewModels/MsgViewModel.cs | 4 +- .../ViewModels/ProfilesViewModel.cs | 36 +- .../ViewModels/RoutingRuleSettingViewModel.cs | 8 +- .../ViewModels/RoutingSettingViewModel.cs | 7 +- .../ViewModels/StatusBarViewModel.cs | 14 +- .../ViewModels/SubSettingViewModel.cs | 7 +- .../v2rayN.Desktop/Assets/GlobalStyles.axaml | 4 + .../Views/DNSSettingWindow.axaml | 8 +- .../Views/RoutingSettingWindow.axaml | 4 - .../v2rayN/v2rayN/Views/DNSSettingWindow.xaml | 8 +- xray-core/common/crypto/crypto.go | 3 + xray-core/core/core.go | 2 +- xray-core/infra/conf/vless.go | 32 +- xray-core/proxy/vless/account.go | 3 + xray-core/proxy/vless/account.pb.go | 24 +- xray-core/proxy/vless/account.proto | 1 + xray-core/proxy/vless/encoding/encoding.go | 28 +- xray-core/proxy/vless/encryption/client.go | 29 +- xray-core/proxy/vless/encryption/common.go | 95 ++++- xray-core/proxy/vless/encryption/server.go | 65 ++-- xray-core/proxy/vless/inbound/config.pb.go | 28 +- xray-core/proxy/vless/inbound/config.proto | 1 + xray-core/proxy/vless/inbound/inbound.go | 23 +- xray-core/proxy/vless/outbound/outbound.go | 23 +- yt-dlp/yt_dlp/extractor/_extractors.py | 4 + yt-dlp/yt_dlp/extractor/disney.py | 4 + yt-dlp/yt_dlp/extractor/myspace.py | 6 +- yt-dlp/yt_dlp/extractor/vevo.py | 352 ++++++++++++++++++ 84 files changed, 1646 insertions(+), 949 deletions(-) delete mode 100644 lede/target/linux/generic/backport-6.6/847-v6.17-Revert-leds-trigger-netdev-Configure-LED-blink-inter.patch create mode 100644 yt-dlp/yt_dlp/extractor/vevo.py diff --git a/.github/update.log b/.github/update.log index 012a161be3..a1cdf30dc9 100644 --- a/.github/update.log +++ b/.github/update.log @@ -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 Fri Aug 29 20:33:46 CEST 2025 Update On Sat Aug 30 20:36:28 CEST 2025 +Update On Sun Aug 31 20:34:00 CEST 2025 diff --git a/clash-meta/docs/config.yaml b/clash-meta/docs/config.yaml index aa105ff671..d5b028e0ad 100644 --- a/clash-meta/docs/config.yaml +++ b/clash-meta/docs/config.yaml @@ -610,7 +610,6 @@ proxies: # socks5 uuid: uuid network: tcp servername: example.com # AKA SNI - # flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS # skip-cert-verify: true # fingerprint: xxxx # client-fingerprint: random # Available: "chrome","firefox","safari","random","none" @@ -640,10 +639,16 @@ proxies: # socks5 network: tcp # ------------------------- # 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 生成,替换值时需去掉括号 + # + # 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 udp: true @@ -1367,8 +1372,14 @@ listeners: # vless encryption服务端配置: # (原生外观 / 只 XOR 公钥 / 全随机数。只允许 1-RTT 模式 / 同时允许 1-RTT 模式与 600 秒复用的 0-RTT 模式) # / 是只能选一个,后面 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(需要同时填写) # certificate: ./server.crt # private-key: ./server.key diff --git a/clash-meta/listener/inbound/vless_test.go b/clash-meta/listener/inbound/vless_test.go index 9a7fed59eb..b465565f92 100644 --- a/clash-meta/listener/inbound/vless_test.go +++ b/clash-meta/listener/inbound/vless_test.go @@ -99,6 +99,15 @@ func TestInboundVless_Encryption(t *testing.T) { t.Fatal(err) 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{ "native", "xorpub", @@ -107,19 +116,26 @@ func TestInboundVless_Encryption(t *testing.T) { for i := range modes { mode := modes[i] t.Run(mode, func(t *testing.T) { - inboundOptions := inbound.VlessOption{ - Decryption: "mlkem768x25519plus." + mode + ".600s." + privateKeyBase64 + "." + seedBase64, + t.Parallel() + 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) - }) }) + } } diff --git a/clash-meta/listener/sing_vless/server.go b/clash-meta/listener/sing_vless/server.go index 5280aa1376..3a5943b5ab 100644 --- a/clash-meta/listener/sing_vless/server.go +++ b/clash-meta/listener/sing_vless/server.go @@ -230,7 +230,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou ctx := sing.WithAdditions(context.TODO(), additions...) if l.decryption != nil { var err error - conn, err = l.decryption.Handshake(conn) + conn, err = l.decryption.Handshake(conn, nil) if err != nil { return } diff --git a/clash-meta/transport/tuic/server.go b/clash-meta/transport/tuic/server.go index 98c9ee3db1..d9d6439ec1 100644 --- a/clash-meta/transport/tuic/server.go +++ b/clash-meta/transport/tuic/server.go @@ -87,7 +87,11 @@ func (s *serverHandler) handle() { _ = 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() { if s.v4Handler != nil { if s.v4Handler.AuthOk() { diff --git a/clash-meta/transport/vless/encryption/client.go b/clash-meta/transport/vless/encryption/client.go index 8debb06f64..9c029ed73c 100644 --- a/clash-meta/transport/vless/encryption/client.go +++ b/clash-meta/transport/vless/encryption/client.go @@ -7,11 +7,23 @@ import ( "errors" "io" "net" + "runtime" "sync" "time" "github.com/metacubex/blake3" "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 { @@ -21,6 +33,8 @@ type ClientInstance struct { RelaysLength int XorMode uint32 Seconds uint32 + PaddingLens [][3]int + PaddingGaps [][3]int RWLock sync.RWMutex Expire time.Time @@ -28,15 +42,13 @@ type ClientInstance struct { Ticket []byte } -func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (err error) { +func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) { if i.NfsPKeys != nil { - err = errors.New("already initialized") - return + return errors.New("already initialized") } l := len(nfsPKeysBytes) if l == 0 { - err = errors.New("empty nfsPKeysBytes") - return + return errors.New("empty nfsPKeysBytes") } i.NfsPKeys = make([]any, l) i.NfsPKeysBytes = nfsPKeysBytes @@ -58,18 +70,18 @@ func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) ( i.RelaysLength -= 32 i.XorMode = xorMode i.Seconds = seconds - return + return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps) } func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { if i.NfsPKeys == nil { return nil, errors.New("uninitialized") } - c := NewCommonConn(conn) + c := NewCommonConn(conn, HasAESGCMHardwareSupport) ivAndRealysLength := 16 + i.RelaysLength pfsKeyExchangeLength := 18 + 1184 + 32 + 16 - paddingLength := int(randBetween(100, 1000)) + paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps) clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength) iv := clientHello[:16] @@ -107,18 +119,18 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:]) relays = relays[index+32:] } - nfsGCM := NewGCM(iv, nfsKey) + nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES) if i.Seconds > 0 { i.RWLock.RLock() if time.Now().Before(i.Expire) { c.Client = i c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection - nfsGCM.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil) - nfsGCM.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil) + nfsAEAD.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil) + nfsAEAD.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil) i.RWLock.RUnlock() 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 { 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] - nfsGCM.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil) + nfsAEAD.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil) mlkem768DKey, _ := mlkem.GenerateKey768() x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader) 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:] - nfsGCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) - nfsGCM.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) + nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) + nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) - if _, err := conn.Write(clientHello); err != nil { - return nil, err + paddingLens[0] = ivAndRealysLength + pfsKeyExchangeLength + paddingLens[0] + for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + if l > 0 { + if _, err := conn.Write(clientHello[:l]); err != nil { + return nil, err + } + clientHello = clientHello[l:] + } + if len(paddingGaps) > i { + time.Sleep(paddingGaps[i]) + } } - // padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control encryptedPfsPublicKey := make([]byte, 1088+32+16) if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { return nil, err } - nfsGCM.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil) + nfsAEAD.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil) mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088]) if err != nil { return nil, err @@ -164,14 +184,14 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { copy(pfsKey, mlkem768Key) copy(pfsKey[32:], x25519Key) c.UnitedKey = append(pfsKey, nfsKey...) - c.GCM = NewGCM(pfsPublicKey, c.UnitedKey) - c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1088+32], c.UnitedKey) + c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES) + c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1088+32], c.UnitedKey, c.UseAES) encryptedTicket := make([]byte, 32) if _, err := io.ReadFull(conn, encryptedTicket); err != nil { 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 } seconds := DecodeLength(encryptedTicket) @@ -188,7 +208,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { if _, err := io.ReadFull(conn, encryptedLength); err != nil { 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 } length := DecodeLength(encryptedLength[:2]) diff --git a/clash-meta/transport/vless/encryption/common.go b/clash-meta/transport/vless/encryption/common.go index 0275a21fa3..8a71f7193b 100644 --- a/clash-meta/transport/vless/encryption/common.go +++ b/clash-meta/transport/vless/encryption/common.go @@ -8,29 +8,34 @@ import ( "fmt" "io" "net" + "strconv" + "strings" "time" "github.com/metacubex/mihomo/common/pool" "github.com/metacubex/blake3" "github.com/metacubex/randv2" + "golang.org/x/crypto/chacha20poly1305" ) type CommonConn struct { net.Conn + UseAES bool Client *ClientInstance UnitedKey []byte PreWrite []byte - GCM *GCM + AEAD *AEAD + PeerAEAD *AEAD PeerPadding []byte rawInput bytes.Buffer // PeerInBytes - PeerGCM *GCM input bytes.Reader // PeerCache } -func NewCommonConn(conn net.Conn) *CommonConn { +func NewCommonConn(conn net.Conn, useAES bool) *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] EncodeHeader(headerAndData, len(b)+16) max := false - if bytes.Equal(c.GCM.Nonce[:], MaxNonce) { + if bytes.Equal(c.AEAD.Nonce[:], MaxNonce) { max = true } - c.GCM.Seal(headerAndData[:5], nil, b, headerAndData[:5]) + c.AEAD.Seal(headerAndData[:5], nil, b, headerAndData[:5]) if max { - c.GCM = NewGCM(headerAndData, c.UnitedKey) + c.AEAD = NewAEAD(headerAndData, c.UnitedKey, c.UseAES) } if c.PreWrite != nil { headerAndData = append(c.PreWrite, headerAndData...) @@ -71,12 +76,12 @@ func (c *CommonConn) Read(b []byte) (int, error) { if len(b) == 0 { return 0, nil } - if c.PeerGCM == nil { // client's 0-RTT + if c.PeerAEAD == nil { // client's 0-RTT serverRandom := make([]byte, 16) if _, err := io.ReadFull(c.Conn, serverRandom); err != nil { return 0, err } - c.PeerGCM = NewGCM(serverRandom, c.UnitedKey) + c.PeerAEAD = NewAEAD(serverRandom, c.UnitedKey, c.UseAES) if xorConn, ok := c.Conn.(*XorConn); ok { 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 { 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 } c.PeerPadding = nil @@ -93,11 +98,11 @@ func (c *CommonConn) Read(b []byte) (int, error) { if c.input.Len() > 0 { return c.input.Read(b) } - peerHeader := make([]byte, 5) - if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { + peerHeader := [5]byte{} + if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil { return 0, err } - l, err := DecodeHeader(peerHeader) // l: 17~17000 + l, err := DecodeHeader(peerHeader[:]) // l: 17~17000 if err != nil { if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT c.Client.RWLock.Lock() @@ -110,7 +115,9 @@ func (c *CommonConn) Read(b []byte) (int, error) { return 0, err } 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] if _, err := io.ReadFull(c.Conn, peerData); err != nil { return 0, err @@ -119,13 +126,13 @@ func (c *CommonConn) Read(b []byte) (int, error) { if len(dst) <= len(b) { dst = b[:len(dst)] // avoids another copy() } - var newGCM *GCM - if bytes.Equal(c.PeerGCM.Nonce[:], MaxNonce) { - newGCM = NewGCM(append(peerHeader, peerData...), c.UnitedKey) + var newAEAD *AEAD + if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) { + newAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES) } - _, err = c.PeerGCM.Open(dst[:0], nil, peerData, peerHeader) - if newGCM != nil { - c.PeerGCM = newGCM + _, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:]) + if newAEAD != nil { + c.PeerAEAD = newAEAD } if err != nil { return 0, err @@ -137,28 +144,32 @@ func (c *CommonConn) Read(b []byte) (int, error) { return len(dst), nil } -type GCM struct { +type AEAD struct { cipher.AEAD Nonce [12]byte } -func NewGCM(ctx, key []byte) *GCM { +func NewAEAD(ctx, key []byte, useAES bool) *AEAD { k := make([]byte, 32) blake3.DeriveKey(k, string(ctx), key) - block, _ := aes.NewCipher(k) - aead, _ := cipher.NewGCM(block) - return &GCM{AEAD: aead} - //chacha20poly1305.New() + var aead cipher.AEAD + if useAES { + block, _ := aes.NewCipher(k) + 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 { nonce = IncreaseNonce(a.Nonce[:]) } 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 { nonce = IncreaseNonce(a.Nonce[:]) } @@ -206,9 +217,80 @@ func DecodeHeader(h []byte) (l int, err error) { 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 { if from == to { return from } + + if to < from { + from, to = to, from + } + return from + randv2.Int64N(to-from) } diff --git a/clash-meta/transport/vless/encryption/doc.go b/clash-meta/transport/vless/encryption/doc.go index d3d72e5ad7..86bf80d0bf 100644 --- a/clash-meta/transport/vless/encryption/doc.go +++ b/clash-meta/transport/vless/encryption/doc.go @@ -21,4 +21,7 @@ // https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902 // https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e // 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 diff --git a/clash-meta/transport/vless/encryption/factory.go b/clash-meta/transport/vless/encryption/factory.go index c344fbf1b1..246f49dd2c 100644 --- a/clash-meta/transport/vless/encryption/factory.go +++ b/clash-meta/transport/vless/encryption/factory.go @@ -34,7 +34,12 @@ func NewClient(encryption string) (*ClientInstance, error) { return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) } var nfsPKeysBytes [][]byte + var paddings []string for _, r := range s[3:] { + if len(r) < 20 { + paddings = append(paddings, r) + continue + } b, err := base64.RawURLEncoding.DecodeString(r) if err != nil { return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) @@ -44,8 +49,9 @@ func NewClient(encryption string) (*ClientInstance, error) { } nfsPKeysBytes = append(nfsPKeysBytes, b) } + padding := strings.Join(paddings, ".") 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 client, nil @@ -84,7 +90,12 @@ func NewServer(decryption string) (*ServerInstance, error) { seconds = uint32(i) } var nfsSKeysBytes [][]byte + var paddings []string for _, r := range s[3:] { + if len(r) < 20 { + paddings = append(paddings, r) + continue + } b, err := base64.RawURLEncoding.DecodeString(r) if err != nil { return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) @@ -94,8 +105,9 @@ func NewServer(decryption string) (*ServerInstance, error) { } nfsSKeysBytes = append(nfsSKeysBytes, b) } + padding := strings.Join(paddings, ".") 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 server, nil diff --git a/clash-meta/transport/vless/encryption/server.go b/clash-meta/transport/vless/encryption/server.go index 14ffd1e503..8b4d6a4e49 100644 --- a/clash-meta/transport/vless/encryption/server.go +++ b/clash-meta/transport/vless/encryption/server.go @@ -29,21 +29,21 @@ type ServerInstance struct { RelaysLength int XorMode uint32 Seconds uint32 + PaddingLens [][3]int + PaddingGaps [][3]int RWLock sync.RWMutex Sessions map[[16]byte]*ServerSession Closed bool } -func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) { +func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) { if i.NfsSKeys != nil { - err = errors.New("already initialized") - return + return errors.New("already initialized") } l := len(nfsSKeysBytes) if l == 0 { - err = errors.New("empty nfsSKeysBytes") - return + return errors.New("empty nfsSKeysBytes") } i.NfsSKeys = make([]any, l) i.NfsPKeysBytes = make([][]byte, l) @@ -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) { @@ -97,16 +97,19 @@ func (i *ServerInstance) Close() (err error) { return } -func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { +func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn, error) { if i.NfsSKeys == nil { return nil, errors.New("uninitialized") } - c := NewCommonConn(conn) + c := NewCommonConn(conn, true) ivAndRelays := make([]byte, 16+i.RelaysLength) if _, err := io.ReadFull(conn, ivAndRelays); err != nil { return nil, err } + if fallback != nil { + *fallback = append(*fallback, ivAndRelays...) + } iv := ivAndRelays[:16] relays := ivAndRelays[16:] var nfsKey []byte @@ -150,16 +153,27 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { } relays = relays[32:] } - nfsGCM := NewGCM(iv, nfsKey) + nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES) encryptedLength := make([]byte, 18) if _, err := io.ReadFull(conn, encryptedLength); err != nil { return nil, err } - if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { - return nil, err + if fallback != nil { + *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 i.Seconds == 0 { @@ -169,7 +183,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { if _, err := io.ReadFull(conn, encryptedTicket); err != nil { return nil, err } - ticket, err := nfsGCM.Open(nil, nil, encryptedTicket, nil) + ticket, err := nfsAEAD.Open(nil, nil, encryptedTicket, nil) if err != nil { return nil, err } @@ -177,7 +191,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { s := i.Sessions[[16]byte(ticket)] i.RWLock.RUnlock() if s == nil { - noises := make([]byte, 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 for err == nil { 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.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") - c.GCM = NewGCM(c.PreWrite, c.UnitedKey) - c.PeerGCM = NewGCM(encryptedTicket, c.UnitedKey) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client) + c.AEAD = NewAEAD(c.PreWrite, c.UnitedKey, c.UseAES) + 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 { 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 { 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 } mlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184]) @@ -229,27 +243,12 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { copy(pfsKey[32:], x25519Key) pfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...) c.UnitedKey = append(pfsKey, nfsKey...) - c.GCM = NewGCM(pfsPublicKey, c.UnitedKey) - c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1184+32], c.UnitedKey) + c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES) + c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES) + ticket := make([]byte, 16) rand.Read(ticket) copy(ticket, EncodeLength(int(i.Seconds*4/5))) - - pfsKeyExchangeLength := 1088 + 32 + 16 - encryptedTicketLength := 32 - paddingLength := int(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 { i.RWLock.Lock() i.Sessions[[16]byte(ticket)] = &ServerSession{ @@ -259,18 +258,41 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { i.RWLock.Unlock() } + pfsKeyExchangeLength := 1088 + 32 + 16 + encryptedTicketLength := 32 + paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps) + serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength) + nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil) + c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil) + padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:] + c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) + c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) + + paddingLens[0] = pfsKeyExchangeLength + encryptedTicketLength + paddingLens[0] + for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + if l > 0 { + if _, err := conn.Write(serverHello[:l]); err != nil { + return nil, err + } + serverHello = serverHello[l:] + } + if len(paddingGaps) > i { + time.Sleep(paddingGaps[i]) + } + } + // important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern if _, err := io.ReadFull(conn, encryptedLength); err != nil { return nil, err } - 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 } encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2])) if _, err := io.ReadFull(conn, encryptedPadding); err != nil { 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 } diff --git a/clash-nyanpasu/.github/workflows/deps-build-macos.yaml b/clash-nyanpasu/.github/workflows/deps-build-macos.yaml index 181964f579..acc4ba0761 100644 --- a/clash-nyanpasu/.github/workflows/deps-build-macos.yaml +++ b/clash-nyanpasu/.github/workflows/deps-build-macos.yaml @@ -49,7 +49,7 @@ jobs: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 15 + xcode-version: 16 - name: install Rust stable run: | diff --git a/clash-nyanpasu/.github/workflows/macos-aarch64.yaml b/clash-nyanpasu/.github/workflows/macos-aarch64.yaml index c1e765121c..0ea1aa0400 100644 --- a/clash-nyanpasu/.github/workflows/macos-aarch64.yaml +++ b/clash-nyanpasu/.github/workflows/macos-aarch64.yaml @@ -26,7 +26,7 @@ jobs: shared-key: 'release' - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '15.0' + xcode-version: 16 - name: install the missing rust target run: | rustup target add aarch64-apple-darwin diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index d6f3a33f08..35af5cd2cf 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -1569,7 +1569,6 @@ dependencies = [ "once_cell", "oneshot", "open", - "openssl", "os_pipe", "oxc_allocator", "oxc_ast", @@ -1821,7 +1820,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", "core-graphics-types 0.1.3", - "foreign-types 0.5.0", + "foreign-types", "libc", ] @@ -1834,7 +1833,7 @@ dependencies = [ "bitflags 2.9.2", "core-foundation 0.10.1", "core-graphics-types 0.2.0", - "foreign-types 0.5.0", + "foreign-types", "libc", ] @@ -3055,15 +3054,6 @@ dependencies = [ "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]] name = "foreign-types" version = "0.5.0" @@ -3071,7 +3061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared 0.3.1", + "foreign-types-shared", ] [[package]] @@ -3085,12 +3075,6 @@ dependencies = [ "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]] name = "foreign-types-shared" version = "0.3.1" @@ -4088,22 +4072,6 @@ dependencies = [ "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]] name = "hyper-util" version = "0.1.16" @@ -5326,7 +5294,7 @@ dependencies = [ "bitflags 2.9.2", "block", "core-graphics-types 0.1.3", - "foreign-types 0.5.0", + "foreign-types", "log", "objc", "paste", @@ -5471,23 +5439,6 @@ dependencies = [ "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]] name = "ndk" version = "0.9.0" @@ -6361,47 +6312,12 @@ dependencies = [ "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]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "openssl-sys" version = "0.9.109" @@ -6410,7 +6326,6 @@ checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -7798,12 +7713,10 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -7814,7 +7727,6 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", "tokio-rustls", "tokio-util", "tower", @@ -8195,19 +8107,6 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "security-framework-sys" version = "2.14.0" @@ -8785,7 +8684,7 @@ dependencies = [ "bytemuck", "cfg_aliases", "core-graphics 0.24.0", - "foreign-types 0.5.0", + "foreign-types", "js-sys", "log", "objc2 0.5.2", @@ -9999,16 +9898,6 @@ dependencies = [ "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]] name = "tokio-rustls" version = "0.26.2" diff --git a/clash-nyanpasu/backend/tauri/Cargo.toml b/clash-nyanpasu/backend/tauri/Cargo.toml index 0c435b7aad..b590a767f3 100644 --- a/clash-nyanpasu/backend/tauri/Cargo.toml +++ b/clash-nyanpasu/backend/tauri/Cargo.toml @@ -89,7 +89,14 @@ rust-i18n = "3" axum = "0.8" url = "2" 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" urlencoding = "2.1" 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"] } # 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-os = "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] 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] objc2 = "0.6.1" objc2-app-kit = { version = "0.3.1", features = [ @@ -260,4 +270,3 @@ verge-dev = [] default-meta = [] devtools = ["tauri/devtools"] deadlock-detection = ["parking_lot/deadlock_detection"] -openssl_vendored = ["openssl/vendored"] diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index 69b9172395..d1f9a58c49 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -56,7 +56,7 @@ "@csstools/normalize.css": "12.1.1", "@emotion/babel-plugin": "11.13.5", "@emotion/react": "11.14.0", - "@iconify/json": "2.2.379", + "@iconify/json": "2.2.380", "@monaco-editor/react": "4.7.0", "@tanstack/react-query": "5.85.5", "@tanstack/react-router": "1.131.28", @@ -71,7 +71,7 @@ "@tauri-apps/plugin-shell": "2.3.0", "@tauri-apps/plugin-updater": "2.9.0", "@types/react": "19.1.12", - "@types/react-dom": "19.1.8", + "@types/react-dom": "19.1.9", "@types/validator": "13.15.2", "@vitejs/plugin-legacy": "7.2.1", "@vitejs/plugin-react": "5.0.2", @@ -80,7 +80,7 @@ "clsx": "2.1.1", "core-js": "3.45.1", "filesize": "11.0.2", - "meta-json-schema": "1.19.12", + "meta-json-schema": "1.19.13", "monaco-yaml": "5.4.0", "nanoid": "5.1.5", "sass-embedded": "1.90.0", diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 167b0efa23..9b611b23a1 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -5,7 +5,7 @@ "mihomo_alpha": "alpha-472cefb", "clash_rs": "v0.9.0", "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": { "mihomo": { @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-08-29T22:20:50.863Z" + "updated_at": "2025-08-30T22:20:39.907Z" } diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index ae6051dc88..27a311bb5f 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -343,8 +343,8 @@ importers: specifier: 11.14.0 version: 11.14.0(@types/react@19.1.12)(react@19.1.1) '@iconify/json': - specifier: 2.2.379 - version: 2.2.379 + specifier: 2.2.380 + version: 2.2.380 '@monaco-editor/react': 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) @@ -388,8 +388,8 @@ importers: specifier: 19.1.12 version: 19.1.12 '@types/react-dom': - specifier: 19.1.8 - version: 19.1.8(@types/react@19.1.12) + specifier: 19.1.9 + version: 19.1.9(@types/react@19.1.12) '@types/validator': specifier: 13.15.2 version: 13.15.2 @@ -415,8 +415,8 @@ importers: specifier: 11.0.2 version: 11.0.2 meta-json-schema: - specifier: 1.19.12 - version: 1.19.12 + specifier: 1.19.13 + version: 1.19.13 monaco-yaml: specifier: 5.4.0 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) '@radix-ui/react-portal': 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': 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': specifier: 2.6.0 version: 2.6.0 @@ -1799,8 +1799,8 @@ packages: prettier-plugin-ember-template-tag: optional: true - '@iconify/json@2.2.379': - resolution: {integrity: sha512-PInpWLQi2C+fDIbBdVNcKOj9QKl7TT6sXqFqYMa4e34sMx206PiUlg0puWgo1Q1C/TDNQiy/raGWUbssOb1eyg==} + '@iconify/json@2.2.380': + resolution: {integrity: sha512-+Al/Q+mMB/nLz/tawmJEOkCs6+RKKVUS/Yg9I80h2yRpu0kIzxVLQRfF0NifXz/fH92vDVXbS399wio4lMVF4Q==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -3438,8 +3438,8 @@ packages: '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - '@types/react-dom@19.1.8': - resolution: {integrity: sha512-xG7xaBMJCpcK0RpN8jDbAACQo54ycO6h4dSSmgv8+fu6ZIAdANkx/WsawASUjVXYfy+J9AbUpRMNNEsXCDfDBQ==} + '@types/react-dom@19.1.9': + resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} peerDependencies: '@types/react': ^19.0.0 @@ -6401,8 +6401,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - meta-json-schema@1.19.12: - resolution: {integrity: sha512-aiI0eILS8y474h5Wmmxp7GgxaFimqEc3z3L4EfZMjqec7N7q7wbXVrZHIR5mUUDHpHagZYFoT1Qf1QNrHMukcw==} + meta-json-schema@1.19.13: + resolution: {integrity: sha512-7oYti4CQ7JPGSWmSddD8bCdNvOhCyza1U+Eb8q/KfbEMT3g3M7cKSXYGS6tN3r9B2pTR5nLzevkhEy/C0DPaxw==} engines: {node: '>=18', pnpm: '>=9'} micromark-core-commonmark@2.0.1: @@ -10190,7 +10190,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@iconify/json@2.2.379': + '@iconify/json@2.2.380': dependencies: '@iconify/types': 2.0.0 pathe: 1.1.2 @@ -10865,17 +10865,17 @@ snapshots: optionalDependencies: '@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: - '@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) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@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: '@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) @@ -10883,33 +10883,33 @@ snapshots: react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@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: '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@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: '@radix-ui/number': 1.1.1 '@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-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-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-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-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.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-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@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)': dependencies: @@ -11770,7 +11770,7 @@ snapshots: '@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: '@types/react': 19.1.12 @@ -15170,7 +15170,7 @@ snapshots: merge2@1.4.1: {} - meta-json-schema@1.19.12: {} + meta-json-schema@1.19.13: {} micromark-core-commonmark@2.0.1: dependencies: diff --git a/lede/include/kernel-5.10 b/lede/include/kernel-5.10 index 2cdec543b6..41776ac0c9 100644 --- a/lede/include/kernel-5.10 +++ b/lede/include/kernel-5.10 @@ -1,2 +1,2 @@ -LINUX_VERSION-5.10 = .240 -LINUX_KERNEL_HASH-5.10.240 = 8d88c3977226d666554b75f480d1e6c5f4e4d2acdf2a3462840c6bac88634d13 +LINUX_VERSION-5.10 = .241 +LINUX_KERNEL_HASH-5.10.241 = 08c1e982064c81f8445a8fff2293d430c716c11fd3185606b8275718d696d8d6 diff --git a/lede/include/kernel-5.15 b/lede/include/kernel-5.15 index 5e3c3bc76e..43cc235a52 100644 --- a/lede/include/kernel-5.15 +++ b/lede/include/kernel-5.15 @@ -1,2 +1,2 @@ -LINUX_VERSION-5.15 = .189 -LINUX_KERNEL_HASH-5.15.189 = e3d0025b87278e14733cb326700f17c7cceb54d920622b0d5fcd58a88c6850c3 +LINUX_VERSION-5.15 = .190 +LINUX_KERNEL_HASH-5.15.190 = 62b814f1a48e1d67764a28611f23ca1f1ce1084ad7f1d319acc05720a9a68604 diff --git a/lede/include/kernel-5.4 b/lede/include/kernel-5.4 index 7cf6d8f9c6..06171ca99a 100644 --- a/lede/include/kernel-5.4 +++ b/lede/include/kernel-5.4 @@ -1,2 +1,2 @@ -LINUX_VERSION-5.4 = .296 -LINUX_KERNEL_HASH-5.4.296 = 3d63614e58bf1befaba3f5713145200d09f26e564832c8948094fdf5b11fa73a +LINUX_VERSION-5.4 = .297 +LINUX_KERNEL_HASH-5.4.297 = f10cfcea7acf7588087d7cc17ebeeb7d3ff783c040536698c544eed7c5e8a841 diff --git a/lede/include/kernel-6.1 b/lede/include/kernel-6.1 index 2fd3ff192c..86197c0983 100644 --- a/lede/include/kernel-6.1 +++ b/lede/include/kernel-6.1 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.1 = .148 -LINUX_KERNEL_HASH-6.1.148 = d9a03d3a2771c60fc726c58d3bba61123e5fd22d45ed27f6cf7a308c171180a1 +LINUX_VERSION-6.1 = .149 +LINUX_KERNEL_HASH-6.1.149 = c4e906b8d39a5866d25e06371d4de4454fbfaaeb67979a04e4137e807877be39 diff --git a/lede/include/kernel-6.12 b/lede/include/kernel-6.12 index 78b4d39332..e28a8c4870 100644 --- a/lede/include/kernel-6.12 +++ b/lede/include/kernel-6.12 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.12 = .43 -LINUX_KERNEL_HASH-6.12.43 = 0fcbbbbcd456e87bbbfc8bf37af541fda62ccfcce76903503424fd101ef7bdee +LINUX_VERSION-6.12 = .44 +LINUX_KERNEL_HASH-6.12.44 = b650210ed3027b224969d148aa377452a9aad3ae7f2851abedd31adfef16bdae diff --git a/lede/include/kernel-6.6 b/lede/include/kernel-6.6 index 048ae0b42a..8a2014fcc6 100644 --- a/lede/include/kernel-6.6 +++ b/lede/include/kernel-6.6 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.6 = .102 -LINUX_KERNEL_HASH-6.6.102 = 80d2feb7334c30bacbe1e7dafa9ea415efb2c0ea4f4740ecbd1467cf5d94de5c +LINUX_VERSION-6.6 = .103 +LINUX_KERNEL_HASH-6.6.103 = d288dd38c3e62ba576ba6b3ad2a84cfba65cd43b702f6c50d1f701aee942b18e diff --git a/lede/target/linux/generic/backport-6.6/847-v6.17-Revert-leds-trigger-netdev-Configure-LED-blink-inter.patch b/lede/target/linux/generic/backport-6.6/847-v6.17-Revert-leds-trigger-netdev-Configure-LED-blink-inter.patch deleted file mode 100644 index 192f12d5c8..0000000000 --- a/lede/target/linux/generic/backport-6.6/847-v6.17-Revert-leds-trigger-netdev-Configure-LED-blink-inter.patch +++ /dev/null @@ -1,81 +0,0 @@ -From 26f732791f2bcab18f59c61915bbe35225f30136 Mon Sep 17 00:00:00 2001 -From: Daniel Golle -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 -Cc: stable@vger.kernel.org -Signed-off-by: Daniel Golle -Reviewed-by: Andrew Lunn -Link: https://lore.kernel.org/r/6dcc77ee1c9676891d6250d8994850f521426a0f.1752334655.git.daniel@makrotopia.org -Signed-off-by: Lee Jones ---- - 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 */ - } - diff --git a/lede/target/linux/generic/pending-5.10/620-net_sched-codel-do-not-defer-queue-length-update.patch b/lede/target/linux/generic/pending-5.10/620-net_sched-codel-do-not-defer-queue-length-update.patch index 4b4825ae3b..dab80a31e1 100644 --- a/lede/target/linux/generic/pending-5.10/620-net_sched-codel-do-not-defer-queue-length-update.patch +++ b/lede/target/linux/generic/pending-5.10/620-net_sched-codel-do-not-defer-queue-length-update.patch @@ -22,19 +22,11 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581 --- a/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c -@@ -95,11 +95,17 @@ static struct sk_buff *codel_qdisc_deque - &q->stats, qdisc_pkt_len, codel_get_enqueue_time, +@@ -96,7 +96,12 @@ static struct sk_buff *codel_qdisc_deque drop_func, dequeue_func); -- /* We cant call qdisc_tree_reduce_backlog() if our qlen is 0, -- * 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) { + if (q->stats.drop_count) { - qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len); -+ if (q->stats.drop_count) { + if (skb) + sch->q.qlen++; + 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 +++ b/net/sched/sch_fq_codel.c @@ -304,6 +304,21 @@ begin: + skb = codel_dequeue(sch, &sch->qstats.backlog, &q->cparams, &flow->cvars, &q->cstats, qdisc_pkt_len, codel_get_enqueue_time, drop_func, dequeue_func); - ++ + /* 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. @@ -64,18 +57,15 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581 + q->cstats.drop_count = 0; + q->cstats.drop_len = 0; + } -+ + if (!skb) { /* force a pass through old_flows to prevent starvation */ - if ((head == &q->new_flows) && !list_empty(&q->old_flows)) -@@ -314,15 +329,6 @@ begin: +@@ -315,13 +330,6 @@ begin: } qdisc_bstats_update(sch, 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 && sch->q.qlen) { +- +- if (q->cstats.drop_count) { - qdisc_tree_reduce_backlog(sch, q->cstats.drop_count, - q->cstats.drop_len); - q->cstats.drop_count = 0; diff --git a/lede/target/linux/generic/pending-5.15/620-net_sched-codel-do-not-defer-queue-length-update.patch b/lede/target/linux/generic/pending-5.15/620-net_sched-codel-do-not-defer-queue-length-update.patch index 4b4825ae3b..dab80a31e1 100644 --- a/lede/target/linux/generic/pending-5.15/620-net_sched-codel-do-not-defer-queue-length-update.patch +++ b/lede/target/linux/generic/pending-5.15/620-net_sched-codel-do-not-defer-queue-length-update.patch @@ -22,19 +22,11 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581 --- a/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c -@@ -95,11 +95,17 @@ static struct sk_buff *codel_qdisc_deque - &q->stats, qdisc_pkt_len, codel_get_enqueue_time, +@@ -96,7 +96,12 @@ static struct sk_buff *codel_qdisc_deque drop_func, dequeue_func); -- /* We cant call qdisc_tree_reduce_backlog() if our qlen is 0, -- * 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) { + if (q->stats.drop_count) { - qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len); -+ if (q->stats.drop_count) { + if (skb) + sch->q.qlen++; + 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 +++ b/net/sched/sch_fq_codel.c @@ -304,6 +304,21 @@ begin: + skb = codel_dequeue(sch, &sch->qstats.backlog, &q->cparams, &flow->cvars, &q->cstats, qdisc_pkt_len, codel_get_enqueue_time, drop_func, dequeue_func); - ++ + /* 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. @@ -64,18 +57,15 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581 + q->cstats.drop_count = 0; + q->cstats.drop_len = 0; + } -+ + if (!skb) { /* force a pass through old_flows to prevent starvation */ - if ((head == &q->new_flows) && !list_empty(&q->old_flows)) -@@ -314,15 +329,6 @@ begin: +@@ -315,13 +330,6 @@ begin: } qdisc_bstats_update(sch, 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 && sch->q.qlen) { +- +- if (q->cstats.drop_count) { - qdisc_tree_reduce_backlog(sch, q->cstats.drop_count, - q->cstats.drop_len); - q->cstats.drop_count = 0; diff --git a/lede/target/linux/generic/pending-5.4/620-net_sched-codel-do-not-defer-queue-length-update.patch b/lede/target/linux/generic/pending-5.4/620-net_sched-codel-do-not-defer-queue-length-update.patch index ca85b8a98c..dab80a31e1 100644 --- a/lede/target/linux/generic/pending-5.4/620-net_sched-codel-do-not-defer-queue-length-update.patch +++ b/lede/target/linux/generic/pending-5.4/620-net_sched-codel-do-not-defer-queue-length-update.patch @@ -22,19 +22,11 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581 --- a/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c -@@ -95,11 +95,17 @@ static struct sk_buff *codel_qdisc_deque - &q->stats, qdisc_pkt_len, codel_get_enqueue_time, +@@ -96,7 +96,12 @@ static struct sk_buff *codel_qdisc_deque drop_func, dequeue_func); -- /* We cant call qdisc_tree_reduce_backlog() if our qlen is 0, -- * 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) { + if (q->stats.drop_count) { - qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len); -+ if (q->stats.drop_count) { + if (skb) + sch->q.qlen++; + 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 +++ 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, codel_get_enqueue_time, drop_func, dequeue_func); - ++ + /* 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. @@ -64,18 +57,15 @@ Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581 + q->cstats.drop_count = 0; + q->cstats.drop_len = 0; + } -+ + if (!skb) { /* force a pass through old_flows to prevent starvation */ - if ((head == &q->new_flows) && !list_empty(&q->old_flows)) -@@ -315,15 +330,6 @@ begin: +@@ -315,13 +330,6 @@ begin: } qdisc_bstats_update(sch, 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 && sch->q.qlen) { +- +- if (q->cstats.drop_count) { - qdisc_tree_reduce_backlog(sch, q->cstats.drop_count, - q->cstats.drop_len); - q->cstats.drop_count = 0; diff --git a/mihomo/docs/config.yaml b/mihomo/docs/config.yaml index aa105ff671..d5b028e0ad 100644 --- a/mihomo/docs/config.yaml +++ b/mihomo/docs/config.yaml @@ -610,7 +610,6 @@ proxies: # socks5 uuid: uuid network: tcp servername: example.com # AKA SNI - # flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS # skip-cert-verify: true # fingerprint: xxxx # client-fingerprint: random # Available: "chrome","firefox","safari","random","none" @@ -640,10 +639,16 @@ proxies: # socks5 network: tcp # ------------------------- # 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 生成,替换值时需去掉括号 + # + # 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 udp: true @@ -1367,8 +1372,14 @@ listeners: # vless encryption服务端配置: # (原生外观 / 只 XOR 公钥 / 全随机数。只允许 1-RTT 模式 / 同时允许 1-RTT 模式与 600 秒复用的 0-RTT 模式) # / 是只能选一个,后面 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(需要同时填写) # certificate: ./server.crt # private-key: ./server.key diff --git a/mihomo/listener/inbound/vless_test.go b/mihomo/listener/inbound/vless_test.go index 9a7fed59eb..b465565f92 100644 --- a/mihomo/listener/inbound/vless_test.go +++ b/mihomo/listener/inbound/vless_test.go @@ -99,6 +99,15 @@ func TestInboundVless_Encryption(t *testing.T) { t.Fatal(err) 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{ "native", "xorpub", @@ -107,19 +116,26 @@ func TestInboundVless_Encryption(t *testing.T) { for i := range modes { mode := modes[i] t.Run(mode, func(t *testing.T) { - inboundOptions := inbound.VlessOption{ - Decryption: "mlkem768x25519plus." + mode + ".600s." + privateKeyBase64 + "." + seedBase64, + t.Parallel() + 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) - }) }) + } } diff --git a/mihomo/listener/sing_vless/server.go b/mihomo/listener/sing_vless/server.go index 5280aa1376..3a5943b5ab 100644 --- a/mihomo/listener/sing_vless/server.go +++ b/mihomo/listener/sing_vless/server.go @@ -230,7 +230,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou ctx := sing.WithAdditions(context.TODO(), additions...) if l.decryption != nil { var err error - conn, err = l.decryption.Handshake(conn) + conn, err = l.decryption.Handshake(conn, nil) if err != nil { return } diff --git a/mihomo/transport/tuic/server.go b/mihomo/transport/tuic/server.go index 98c9ee3db1..d9d6439ec1 100644 --- a/mihomo/transport/tuic/server.go +++ b/mihomo/transport/tuic/server.go @@ -87,7 +87,11 @@ func (s *serverHandler) handle() { _ = 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() { if s.v4Handler != nil { if s.v4Handler.AuthOk() { diff --git a/mihomo/transport/vless/encryption/client.go b/mihomo/transport/vless/encryption/client.go index 8debb06f64..9c029ed73c 100644 --- a/mihomo/transport/vless/encryption/client.go +++ b/mihomo/transport/vless/encryption/client.go @@ -7,11 +7,23 @@ import ( "errors" "io" "net" + "runtime" "sync" "time" "github.com/metacubex/blake3" "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 { @@ -21,6 +33,8 @@ type ClientInstance struct { RelaysLength int XorMode uint32 Seconds uint32 + PaddingLens [][3]int + PaddingGaps [][3]int RWLock sync.RWMutex Expire time.Time @@ -28,15 +42,13 @@ type ClientInstance struct { Ticket []byte } -func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (err error) { +func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) { if i.NfsPKeys != nil { - err = errors.New("already initialized") - return + return errors.New("already initialized") } l := len(nfsPKeysBytes) if l == 0 { - err = errors.New("empty nfsPKeysBytes") - return + return errors.New("empty nfsPKeysBytes") } i.NfsPKeys = make([]any, l) i.NfsPKeysBytes = nfsPKeysBytes @@ -58,18 +70,18 @@ func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) ( i.RelaysLength -= 32 i.XorMode = xorMode i.Seconds = seconds - return + return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps) } func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { if i.NfsPKeys == nil { return nil, errors.New("uninitialized") } - c := NewCommonConn(conn) + c := NewCommonConn(conn, HasAESGCMHardwareSupport) ivAndRealysLength := 16 + i.RelaysLength pfsKeyExchangeLength := 18 + 1184 + 32 + 16 - paddingLength := int(randBetween(100, 1000)) + paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps) clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength) iv := clientHello[:16] @@ -107,18 +119,18 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:]) relays = relays[index+32:] } - nfsGCM := NewGCM(iv, nfsKey) + nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES) if i.Seconds > 0 { i.RWLock.RLock() if time.Now().Before(i.Expire) { c.Client = i c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection - nfsGCM.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil) - nfsGCM.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil) + nfsAEAD.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil) + nfsAEAD.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil) i.RWLock.RUnlock() 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 { 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] - nfsGCM.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil) + nfsAEAD.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil) mlkem768DKey, _ := mlkem.GenerateKey768() x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader) 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:] - nfsGCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) - nfsGCM.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) + nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) + nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) - if _, err := conn.Write(clientHello); err != nil { - return nil, err + paddingLens[0] = ivAndRealysLength + pfsKeyExchangeLength + paddingLens[0] + for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + if l > 0 { + if _, err := conn.Write(clientHello[:l]); err != nil { + return nil, err + } + clientHello = clientHello[l:] + } + if len(paddingGaps) > i { + time.Sleep(paddingGaps[i]) + } } - // padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control encryptedPfsPublicKey := make([]byte, 1088+32+16) if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { return nil, err } - nfsGCM.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil) + nfsAEAD.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil) mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088]) if err != nil { return nil, err @@ -164,14 +184,14 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { copy(pfsKey, mlkem768Key) copy(pfsKey[32:], x25519Key) c.UnitedKey = append(pfsKey, nfsKey...) - c.GCM = NewGCM(pfsPublicKey, c.UnitedKey) - c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1088+32], c.UnitedKey) + c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES) + c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1088+32], c.UnitedKey, c.UseAES) encryptedTicket := make([]byte, 32) if _, err := io.ReadFull(conn, encryptedTicket); err != nil { 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 } seconds := DecodeLength(encryptedTicket) @@ -188,7 +208,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { if _, err := io.ReadFull(conn, encryptedLength); err != nil { 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 } length := DecodeLength(encryptedLength[:2]) diff --git a/mihomo/transport/vless/encryption/common.go b/mihomo/transport/vless/encryption/common.go index 0275a21fa3..8a71f7193b 100644 --- a/mihomo/transport/vless/encryption/common.go +++ b/mihomo/transport/vless/encryption/common.go @@ -8,29 +8,34 @@ import ( "fmt" "io" "net" + "strconv" + "strings" "time" "github.com/metacubex/mihomo/common/pool" "github.com/metacubex/blake3" "github.com/metacubex/randv2" + "golang.org/x/crypto/chacha20poly1305" ) type CommonConn struct { net.Conn + UseAES bool Client *ClientInstance UnitedKey []byte PreWrite []byte - GCM *GCM + AEAD *AEAD + PeerAEAD *AEAD PeerPadding []byte rawInput bytes.Buffer // PeerInBytes - PeerGCM *GCM input bytes.Reader // PeerCache } -func NewCommonConn(conn net.Conn) *CommonConn { +func NewCommonConn(conn net.Conn, useAES bool) *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] EncodeHeader(headerAndData, len(b)+16) max := false - if bytes.Equal(c.GCM.Nonce[:], MaxNonce) { + if bytes.Equal(c.AEAD.Nonce[:], MaxNonce) { max = true } - c.GCM.Seal(headerAndData[:5], nil, b, headerAndData[:5]) + c.AEAD.Seal(headerAndData[:5], nil, b, headerAndData[:5]) if max { - c.GCM = NewGCM(headerAndData, c.UnitedKey) + c.AEAD = NewAEAD(headerAndData, c.UnitedKey, c.UseAES) } if c.PreWrite != nil { headerAndData = append(c.PreWrite, headerAndData...) @@ -71,12 +76,12 @@ func (c *CommonConn) Read(b []byte) (int, error) { if len(b) == 0 { return 0, nil } - if c.PeerGCM == nil { // client's 0-RTT + if c.PeerAEAD == nil { // client's 0-RTT serverRandom := make([]byte, 16) if _, err := io.ReadFull(c.Conn, serverRandom); err != nil { return 0, err } - c.PeerGCM = NewGCM(serverRandom, c.UnitedKey) + c.PeerAEAD = NewAEAD(serverRandom, c.UnitedKey, c.UseAES) if xorConn, ok := c.Conn.(*XorConn); ok { 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 { 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 } c.PeerPadding = nil @@ -93,11 +98,11 @@ func (c *CommonConn) Read(b []byte) (int, error) { if c.input.Len() > 0 { return c.input.Read(b) } - peerHeader := make([]byte, 5) - if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { + peerHeader := [5]byte{} + if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil { return 0, err } - l, err := DecodeHeader(peerHeader) // l: 17~17000 + l, err := DecodeHeader(peerHeader[:]) // l: 17~17000 if err != nil { if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT c.Client.RWLock.Lock() @@ -110,7 +115,9 @@ func (c *CommonConn) Read(b []byte) (int, error) { return 0, err } 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] if _, err := io.ReadFull(c.Conn, peerData); err != nil { return 0, err @@ -119,13 +126,13 @@ func (c *CommonConn) Read(b []byte) (int, error) { if len(dst) <= len(b) { dst = b[:len(dst)] // avoids another copy() } - var newGCM *GCM - if bytes.Equal(c.PeerGCM.Nonce[:], MaxNonce) { - newGCM = NewGCM(append(peerHeader, peerData...), c.UnitedKey) + var newAEAD *AEAD + if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) { + newAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES) } - _, err = c.PeerGCM.Open(dst[:0], nil, peerData, peerHeader) - if newGCM != nil { - c.PeerGCM = newGCM + _, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:]) + if newAEAD != nil { + c.PeerAEAD = newAEAD } if err != nil { return 0, err @@ -137,28 +144,32 @@ func (c *CommonConn) Read(b []byte) (int, error) { return len(dst), nil } -type GCM struct { +type AEAD struct { cipher.AEAD Nonce [12]byte } -func NewGCM(ctx, key []byte) *GCM { +func NewAEAD(ctx, key []byte, useAES bool) *AEAD { k := make([]byte, 32) blake3.DeriveKey(k, string(ctx), key) - block, _ := aes.NewCipher(k) - aead, _ := cipher.NewGCM(block) - return &GCM{AEAD: aead} - //chacha20poly1305.New() + var aead cipher.AEAD + if useAES { + block, _ := aes.NewCipher(k) + 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 { nonce = IncreaseNonce(a.Nonce[:]) } 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 { nonce = IncreaseNonce(a.Nonce[:]) } @@ -206,9 +217,80 @@ func DecodeHeader(h []byte) (l int, err error) { 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 { if from == to { return from } + + if to < from { + from, to = to, from + } + return from + randv2.Int64N(to-from) } diff --git a/mihomo/transport/vless/encryption/doc.go b/mihomo/transport/vless/encryption/doc.go index d3d72e5ad7..86bf80d0bf 100644 --- a/mihomo/transport/vless/encryption/doc.go +++ b/mihomo/transport/vless/encryption/doc.go @@ -21,4 +21,7 @@ // https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902 // https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e // 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 diff --git a/mihomo/transport/vless/encryption/factory.go b/mihomo/transport/vless/encryption/factory.go index c344fbf1b1..246f49dd2c 100644 --- a/mihomo/transport/vless/encryption/factory.go +++ b/mihomo/transport/vless/encryption/factory.go @@ -34,7 +34,12 @@ func NewClient(encryption string) (*ClientInstance, error) { return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) } var nfsPKeysBytes [][]byte + var paddings []string for _, r := range s[3:] { + if len(r) < 20 { + paddings = append(paddings, r) + continue + } b, err := base64.RawURLEncoding.DecodeString(r) if err != nil { return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) @@ -44,8 +49,9 @@ func NewClient(encryption string) (*ClientInstance, error) { } nfsPKeysBytes = append(nfsPKeysBytes, b) } + padding := strings.Join(paddings, ".") 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 client, nil @@ -84,7 +90,12 @@ func NewServer(decryption string) (*ServerInstance, error) { seconds = uint32(i) } var nfsSKeysBytes [][]byte + var paddings []string for _, r := range s[3:] { + if len(r) < 20 { + paddings = append(paddings, r) + continue + } b, err := base64.RawURLEncoding.DecodeString(r) if err != nil { return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) @@ -94,8 +105,9 @@ func NewServer(decryption string) (*ServerInstance, error) { } nfsSKeysBytes = append(nfsSKeysBytes, b) } + padding := strings.Join(paddings, ".") 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 server, nil diff --git a/mihomo/transport/vless/encryption/server.go b/mihomo/transport/vless/encryption/server.go index 14ffd1e503..8b4d6a4e49 100644 --- a/mihomo/transport/vless/encryption/server.go +++ b/mihomo/transport/vless/encryption/server.go @@ -29,21 +29,21 @@ type ServerInstance struct { RelaysLength int XorMode uint32 Seconds uint32 + PaddingLens [][3]int + PaddingGaps [][3]int RWLock sync.RWMutex Sessions map[[16]byte]*ServerSession Closed bool } -func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) { +func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) { if i.NfsSKeys != nil { - err = errors.New("already initialized") - return + return errors.New("already initialized") } l := len(nfsSKeysBytes) if l == 0 { - err = errors.New("empty nfsSKeysBytes") - return + return errors.New("empty nfsSKeysBytes") } i.NfsSKeys = make([]any, l) i.NfsPKeysBytes = make([][]byte, l) @@ -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) { @@ -97,16 +97,19 @@ func (i *ServerInstance) Close() (err error) { return } -func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { +func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn, error) { if i.NfsSKeys == nil { return nil, errors.New("uninitialized") } - c := NewCommonConn(conn) + c := NewCommonConn(conn, true) ivAndRelays := make([]byte, 16+i.RelaysLength) if _, err := io.ReadFull(conn, ivAndRelays); err != nil { return nil, err } + if fallback != nil { + *fallback = append(*fallback, ivAndRelays...) + } iv := ivAndRelays[:16] relays := ivAndRelays[16:] var nfsKey []byte @@ -150,16 +153,27 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { } relays = relays[32:] } - nfsGCM := NewGCM(iv, nfsKey) + nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES) encryptedLength := make([]byte, 18) if _, err := io.ReadFull(conn, encryptedLength); err != nil { return nil, err } - if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { - return nil, err + if fallback != nil { + *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 i.Seconds == 0 { @@ -169,7 +183,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { if _, err := io.ReadFull(conn, encryptedTicket); err != nil { return nil, err } - ticket, err := nfsGCM.Open(nil, nil, encryptedTicket, nil) + ticket, err := nfsAEAD.Open(nil, nil, encryptedTicket, nil) if err != nil { return nil, err } @@ -177,7 +191,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { s := i.Sessions[[16]byte(ticket)] i.RWLock.RUnlock() if s == nil { - noises := make([]byte, 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 for err == nil { 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.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") - c.GCM = NewGCM(c.PreWrite, c.UnitedKey) - c.PeerGCM = NewGCM(encryptedTicket, c.UnitedKey) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client) + c.AEAD = NewAEAD(c.PreWrite, c.UnitedKey, c.UseAES) + 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 { 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 { 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 } mlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184]) @@ -229,27 +243,12 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { copy(pfsKey[32:], x25519Key) pfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...) c.UnitedKey = append(pfsKey, nfsKey...) - c.GCM = NewGCM(pfsPublicKey, c.UnitedKey) - c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1184+32], c.UnitedKey) + c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES) + c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES) + ticket := make([]byte, 16) rand.Read(ticket) copy(ticket, EncodeLength(int(i.Seconds*4/5))) - - pfsKeyExchangeLength := 1088 + 32 + 16 - encryptedTicketLength := 32 - paddingLength := int(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 { i.RWLock.Lock() i.Sessions[[16]byte(ticket)] = &ServerSession{ @@ -259,18 +258,41 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { i.RWLock.Unlock() } + pfsKeyExchangeLength := 1088 + 32 + 16 + encryptedTicketLength := 32 + paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps) + serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength) + nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil) + c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil) + padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:] + c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) + c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) + + paddingLens[0] = pfsKeyExchangeLength + encryptedTicketLength + paddingLens[0] + for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + if l > 0 { + if _, err := conn.Write(serverHello[:l]); err != nil { + return nil, err + } + serverHello = serverHello[l:] + } + if len(paddingGaps) > i { + time.Sleep(paddingGaps[i]) + } + } + // important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern if _, err := io.ReadFull(conn, encryptedLength); err != nil { return nil, err } - 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 } encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2])) if _, err := io.ReadFull(conn, encryptedPadding); err != nil { 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 } diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua index 17485c97d1..470e5d791b 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua @@ -185,6 +185,10 @@ if has_xray then o.default = "10-20" 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.default = 0 @@ -238,6 +242,11 @@ if has_xray then o = s_xray_noise:option(Value, "delay", translate("Delay (ms)")) o.datatype = "or(uinteger,portrange)" 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 if has_singbox then diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/api.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/api.lua index 179b53965b..ba0524c15d 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/api.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/api.lua @@ -429,6 +429,18 @@ function is_ipv6(val) return false 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) if is_ipv6(val) then local address, port = val:match('%[(.*)%]:([^:]+)$') diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua index a05fb272ff..e638d8f7f1 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -1555,6 +1555,9 @@ function gen_config(var) end 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) end @@ -1610,6 +1613,9 @@ function gen_config(var) end 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) 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 local remote_shunt_server = api.clone(remote_server) 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) dns_rule.server = remote_shunt_server.tag end diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua index 0ede4a92f7..62372cd496 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -23,7 +23,8 @@ local function get_noise_packets() local noise = (n.enabled == "1") and { type = n.type, 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 table.insert(noises, noise) end) @@ -1370,25 +1371,37 @@ function gen_config(var) end 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(routing.rules, { - inboundTag = { - dns_server.tag - }, - outboundTag = value.outboundTag or nil, - balancerTag = value.balancerTag or nil + inboundTag = { dns_server.tag }, + outboundTag = outboundTag, + balancerTag = balancerTag }) 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, { - inboundTag = { - "dns-global" - }, - balancerTag = COMMON.default_balancer_tag, - outboundTag = dns_outbound_tag + inboundTag = { "dns-global" }, + balancerTag = _balancerTag, + outboundTag = _outboundTag }) local default_rule_index = nil @@ -1460,7 +1473,8 @@ function gen_config(var) fragment = (xray_settings.fragment == "1") and { 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, - 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, noises = (xray_settings.noise == "1") and get_noise_packets() or nil }, diff --git a/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po b/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po index 7fcc4f76bf..4c35579c93 100644 --- a/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po +++ b/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po @@ -1759,6 +1759,12 @@ msgstr "分片间隔" msgid "Fragmentation interval (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." msgstr "将握手数据拆分为多个 TLS 记录,提升抗封锁能力,几乎不增加延迟,建议优先启用。" @@ -1783,6 +1789,9 @@ msgstr "数据包" msgid "Delay (ms)" msgstr "延迟(ms)" +msgid "IP Type" +msgstr "IP 类型" + msgid "If is domain name, The requested domain name will be resolved to IP before connect." msgstr "如果是域名,域名将在请求发出之前解析为 IP。" diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua index b638f4017b..b41c6928f1 100644 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua @@ -214,14 +214,7 @@ if DNS_MODE == "socks" then end - -- 判断是否为本地地址 - 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 + if not api.is_local_ip(w) then server_param = server_param .. " -proxy " .. proxy_server_name end diff --git a/shadowsocks-rust/Cargo.lock b/shadowsocks-rust/Cargo.lock index 007d0a68b4..338605e5d4 100644 --- a/shadowsocks-rust/Cargo.lock +++ b/shadowsocks-rust/Cargo.lock @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -151,29 +151,29 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "arc-swap" @@ -195,9 +195,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "async-channel" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c74e56284d2188cabb6ad99603d1ace887a5d7e7b695d01b728155ed9ed427" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ "concurrent-queue", "event-listener-strategy", @@ -213,13 +213,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -273,7 +273,7 @@ version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cexpr", "clang-sys", "itertools", @@ -282,7 +282,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -293,9 +293,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "bitvec" @@ -333,9 +333,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ "async-channel", "async-task", @@ -409,7 +409,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -478,9 +478,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.28" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad45f4f74e4e20eaa392913b7b33a7091c87e59628f4dd27888205ad888843c" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "jobserver", "libc", @@ -679,9 +679,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -792,7 +792,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -880,7 +880,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -900,7 +900,7 @@ checksum = "0b0713d5c1d52e774c5cd7bb8b043d7c0fc4f921abfb678556140bfbe6ab2364" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -966,7 +966,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1019,9 +1019,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -1159,9 +1159,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "futures-core", "pin-project-lite", @@ -1175,7 +1175,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1210,9 +1210,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" dependencies = [ "cc", "cfg-if", @@ -1256,7 +1256,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.3+wasi-0.2.4", "wasm-bindgen", ] @@ -1278,9 +1278,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "group" @@ -1295,9 +1295,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -1351,9 +1351,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "heapless" @@ -1568,9 +1568,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64", "bytes", @@ -1584,7 +1584,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.0", "system-configuration", "tokio", "tower-service", @@ -1725,9 +1725,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown", @@ -1739,7 +1739,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "inotify-sys", "libc", ] @@ -1764,11 +1764,11 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg-if", "libc", ] @@ -1872,14 +1872,14 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", @@ -1945,7 +1945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -1960,11 +1960,11 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.4" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "libc", ] @@ -2209,7 +2209,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg-if", "cfg_aliases", "libc", @@ -2231,7 +2231,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "fsevent-sys", "inotify", "kqueue", @@ -2319,7 +2319,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg-if", "foreign-types", "libc", @@ -2336,7 +2336,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2347,9 +2347,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] 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" -checksum = "735230c832b28c000e3bc117119e6466a663ec73506bc0a9907ea4187508e42a" +checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4" dependencies = [ "cc", ] @@ -2469,7 +2469,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2499,7 +2499,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2581,9 +2581,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -2631,14 +2631,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -2651,9 +2651,9 @@ checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", @@ -2663,7 +2663,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.0", "thiserror 2.0.16", "tokio", "tracing", @@ -2672,9 +2672,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "getrandom 0.3.3", @@ -2693,16 +2693,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.0", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2787,18 +2787,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", ] [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", @@ -2819,9 +2819,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -2830,9 +2830,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reqwest" @@ -2973,9 +2973,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -2994,22 +2994,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.28" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "log", "once_cell", @@ -3029,7 +3029,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.2.0", + "security-framework 3.3.0", ] [[package]] @@ -3044,9 +3044,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -3055,9 +3055,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -3103,7 +3103,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3125,7 +3125,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -3134,11 +3134,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -3207,7 +3207,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3445,9 +3445,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -3597,9 +3597,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -3623,7 +3623,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3638,7 +3638,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3682,25 +3682,25 @@ checksum = "3b7ad73e635dd232c2c2106d59269f59a61de421cc6b95252d2d932094ff1f40" [[package]] name = "tempfile" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3729,7 +3729,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3740,7 +3740,7 @@ checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3807,9 +3807,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -3848,7 +3848,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3890,9 +3890,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -3922,7 +3922,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "bytes", "futures-util", "http", @@ -3978,7 +3978,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4029,7 +4029,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4143,9 +4143,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -4198,11 +4198,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] 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" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -4227,7 +4227,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -4262,7 +4262,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4338,11 +4338,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4405,7 +4405,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4416,7 +4416,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4461,7 +4461,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193cae8e647981c35bc947fdd57ba7928b1fa0d4a79305f6dd2dc55221ac35ac" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "widestring", "windows-sys 0.59.0", ] @@ -4508,7 +4508,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -4544,10 +4544,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -4742,13 +4743,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] +checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" [[package]] name = "writeable" @@ -4791,7 +4789,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] @@ -4812,7 +4810,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4832,7 +4830,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] @@ -4855,9 +4853,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -4872,7 +4870,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua index 17485c97d1..470e5d791b 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua @@ -185,6 +185,10 @@ if has_xray then o.default = "10-20" 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.default = 0 @@ -238,6 +242,11 @@ if has_xray then o = s_xray_noise:option(Value, "delay", translate("Delay (ms)")) o.datatype = "or(uinteger,portrange)" 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 if has_singbox then diff --git a/small/luci-app-passwall/luasrc/passwall/api.lua b/small/luci-app-passwall/luasrc/passwall/api.lua index 179b53965b..ba0524c15d 100644 --- a/small/luci-app-passwall/luasrc/passwall/api.lua +++ b/small/luci-app-passwall/luasrc/passwall/api.lua @@ -429,6 +429,18 @@ function is_ipv6(val) return false 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) if is_ipv6(val) then local address, port = val:match('%[(.*)%]:([^:]+)$') diff --git a/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua index a05fb272ff..e638d8f7f1 100644 --- a/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -1555,6 +1555,9 @@ function gen_config(var) end 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) end @@ -1610,6 +1613,9 @@ function gen_config(var) end 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) 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 local remote_shunt_server = api.clone(remote_server) 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) dns_rule.server = remote_shunt_server.tag end diff --git a/small/luci-app-passwall/luasrc/passwall/util_xray.lua b/small/luci-app-passwall/luasrc/passwall/util_xray.lua index 0ede4a92f7..62372cd496 100644 --- a/small/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/small/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -23,7 +23,8 @@ local function get_noise_packets() local noise = (n.enabled == "1") and { type = n.type, 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 table.insert(noises, noise) end) @@ -1370,25 +1371,37 @@ function gen_config(var) end 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(routing.rules, { - inboundTag = { - dns_server.tag - }, - outboundTag = value.outboundTag or nil, - balancerTag = value.balancerTag or nil + inboundTag = { dns_server.tag }, + outboundTag = outboundTag, + balancerTag = balancerTag }) 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, { - inboundTag = { - "dns-global" - }, - balancerTag = COMMON.default_balancer_tag, - outboundTag = dns_outbound_tag + inboundTag = { "dns-global" }, + balancerTag = _balancerTag, + outboundTag = _outboundTag }) local default_rule_index = nil @@ -1460,7 +1473,8 @@ function gen_config(var) fragment = (xray_settings.fragment == "1") and { 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, - 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, noises = (xray_settings.noise == "1") and get_noise_packets() or nil }, diff --git a/small/luci-app-passwall/po/zh-cn/passwall.po b/small/luci-app-passwall/po/zh-cn/passwall.po index 7fcc4f76bf..4c35579c93 100644 --- a/small/luci-app-passwall/po/zh-cn/passwall.po +++ b/small/luci-app-passwall/po/zh-cn/passwall.po @@ -1759,6 +1759,12 @@ msgstr "分片间隔" msgid "Fragmentation interval (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." msgstr "将握手数据拆分为多个 TLS 记录,提升抗封锁能力,几乎不增加延迟,建议优先启用。" @@ -1783,6 +1789,9 @@ msgstr "数据包" msgid "Delay (ms)" msgstr "延迟(ms)" +msgid "IP Type" +msgstr "IP 类型" + msgid "If is domain name, The requested domain name will be resolved to IP before connect." msgstr "如果是域名,域名将在请求发出之前解析为 IP。" diff --git a/small/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua b/small/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua index b638f4017b..b41c6928f1 100644 --- a/small/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua +++ b/small/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua @@ -214,14 +214,7 @@ if DNS_MODE == "socks" then end - -- 判断是否为本地地址 - 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 + if not api.is_local_ip(w) then server_param = server_param .. " -proxy " .. proxy_server_name end diff --git a/v2rayn/v2rayN/Directory.Build.props b/v2rayn/v2rayN/Directory.Build.props index 589eb88989..657abfabbf 100644 --- a/v2rayn/v2rayN/Directory.Build.props +++ b/v2rayn/v2rayN/Directory.Build.props @@ -1,7 +1,7 @@ - 7.14.4 + 7.14.5 diff --git a/v2rayn/v2rayN/GlobalHotKeys/src/Directory.Packages.props b/v2rayn/v2rayN/GlobalHotKeys/src/Directory.Packages.props index 1a0e9e088b..1dd780fdf3 100644 --- a/v2rayn/v2rayN/GlobalHotKeys/src/Directory.Packages.props +++ b/v2rayn/v2rayN/GlobalHotKeys/src/Directory.Packages.props @@ -5,12 +5,12 @@ false - - - - - - + + + + + + diff --git a/v2rayn/v2rayN/GlobalHotKeys/src/Examples/Avalonia/MainWindow.axaml.cs b/v2rayn/v2rayN/GlobalHotKeys/src/Examples/Avalonia/MainWindow.axaml.cs index ef1a69d6a4..9db4a1046c 100644 --- a/v2rayn/v2rayN/GlobalHotKeys/src/Examples/Avalonia/MainWindow.axaml.cs +++ b/v2rayn/v2rayN/GlobalHotKeys/src/Examples/Avalonia/MainWindow.axaml.cs @@ -4,7 +4,7 @@ using Avalonia.Markup.Xaml; namespace AvaloniaApp { - public class MainWindow : Window + public partial class MainWindow : Window { public MainWindow() { diff --git a/v2rayn/v2rayN/GlobalHotKeys/src/GlobalHotKeys.Test/GlobalHotKeys.Test.csproj b/v2rayn/v2rayN/GlobalHotKeys/src/GlobalHotKeys.Test/GlobalHotKeys.Test.csproj index 87dd1eb4a9..53569d5522 100644 --- a/v2rayn/v2rayN/GlobalHotKeys/src/GlobalHotKeys.Test/GlobalHotKeys.Test.csproj +++ b/v2rayn/v2rayN/GlobalHotKeys/src/GlobalHotKeys.Test/GlobalHotKeys.Test.csproj @@ -10,11 +10,17 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs index c87f6737b8..579a2288d2 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs @@ -13,11 +13,11 @@ namespace ServiceLib.ViewModels; public class CheckUpdateViewModel : MyReactiveObject { private const string _geo = "GeoFiles"; - private string _v2rayN = ECoreType.v2rayN.ToString(); + private readonly string _v2rayN = ECoreType.v2rayN.ToString(); private List _lstUpdated = []; + private static readonly string _tag = "CheckUpdateViewModel"; - private IObservableCollection _checkUpdateModel = new ObservableCollectionExtended(); - public IObservableCollection CheckUpdateModels => _checkUpdateModel; + public IObservableCollection CheckUpdateModels { get; } = new ObservableCollectionExtended(); public ReactiveCommand CheckUpdateCmd { get; } [Reactive] public bool EnableCheckPreReleaseUpdate { get; set; } @@ -26,9 +26,11 @@ public class CheckUpdateViewModel : MyReactiveObject _config = AppManager.Instance.Config; _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; @@ -43,20 +45,20 @@ public class CheckUpdateViewModel : MyReactiveObject private void RefreshCheckUpdateItems() { - _checkUpdateModel.Clear(); + CheckUpdateModels.Clear(); if (RuntimeInformation.ProcessArchitecture != Architecture.X86) { - _checkUpdateModel.Add(GetCheckUpdateModel(_v2rayN)); + CheckUpdateModels.Add(GetCheckUpdateModel(_v2rayN)); //Not Windows and under Win10 if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10)) { - _checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.Xray.ToString())); - _checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString())); - _checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString())); + CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.Xray.ToString())); + CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString())); + CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString())); } } - _checkUpdateModel.Add(GetCheckUpdateModel(_geo)); + CheckUpdateModels.Add(GetCheckUpdateModel(_geo)); } private CheckUpdateModel GetCheckUpdateModel(string coreType) @@ -71,7 +73,7 @@ public class CheckUpdateViewModel : MyReactiveObject 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); } @@ -83,13 +85,13 @@ public class CheckUpdateViewModel : MyReactiveObject private async Task CheckUpdateTask() { _lstUpdated.Clear(); - _lstUpdated = _checkUpdateModel.Where(x => x.IsSelected == true) + _lstUpdated = CheckUpdateModels.Where(x => x.IsSelected == true) .Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList(); 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) { continue; @@ -320,7 +322,7 @@ public class CheckUpdateViewModel : MyReactiveObject 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) { return; @@ -328,6 +330,6 @@ public class CheckUpdateViewModel : MyReactiveObject var itemCopy = JsonUtils.DeepCopy(found); itemCopy.Remarks = model.Remarks; - _checkUpdateModel.Replace(found, itemCopy); + CheckUpdateModels.Replace(found, itemCopy); } } diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs index 1fd9015c8d..d45b8e7d21 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs @@ -10,8 +10,7 @@ namespace ServiceLib.ViewModels; public class ClashConnectionsViewModel : MyReactiveObject { - private IObservableCollection _connectionItems = new ObservableCollectionExtended(); - public IObservableCollection ConnectionItems => _connectionItems; + public IObservableCollection ConnectionItems { get; } = new ObservableCollectionExtended(); [Reactive] public ClashConnectionModel SelectedSource { get; set; } @@ -74,7 +73,7 @@ public class ClashConnectionsViewModel : MyReactiveObject public async Task RefreshConnections(List? connections) { - _connectionItems.Clear(); + ConnectionItems.Clear(); var dtNow = DateTime.Now; var lstModel = new List(); @@ -104,7 +103,7 @@ public class ClashConnectionsViewModel : MyReactiveObject return; } - _connectionItems.AddRange(lstModel); + ConnectionItems.AddRange(lstModel); } public async Task ClashConnectionClose(bool all) @@ -121,7 +120,7 @@ public class ClashConnectionsViewModel : MyReactiveObject } else { - _connectionItems.Clear(); + ConnectionItems.Clear(); } await ClashApiManager.Instance.ClashConnectionClose(id); await GetClashConnections(); diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs index 531b4a8ef3..54cbdc8d74 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs @@ -17,11 +17,8 @@ public class ClashProxiesViewModel : MyReactiveObject private Dictionary? _providers; private readonly int _delayTimeout = 99999999; - private IObservableCollection _proxyGroups = new ObservableCollectionExtended(); - private IObservableCollection _proxyDetails = new ObservableCollectionExtended(); - - public IObservableCollection ProxyGroups => _proxyGroups; - public IObservableCollection ProxyDetails => _proxyDetails; + public IObservableCollection ProxyGroups { get; } = new ObservableCollectionExtended(); + public IObservableCollection ProxyDetails { get; } = new ObservableCollectionExtended(); [Reactive] public ClashProxyModel SelectedGroup { get; set; } @@ -182,7 +179,7 @@ public class ClashProxiesViewModel : MyReactiveObject } var selectedName = SelectedGroup?.Name; - _proxyGroups.Clear(); + ProxyGroups.Clear(); var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups(); if (proxyGroups != null && proxyGroups.Count > 0) @@ -198,7 +195,7 @@ public class ClashProxiesViewModel : MyReactiveObject { continue; } - _proxyGroups.Add(new ClashProxyModel() + ProxyGroups.Add(new ClashProxyModel() { Now = item.now, Name = item.name, @@ -214,12 +211,12 @@ public class ClashProxiesViewModel : MyReactiveObject { 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()) { continue; } - _proxyGroups.Add(new ClashProxyModel() + ProxyGroups.Add(new ClashProxyModel() { Now = kv.Value.now, 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 { - SelectedGroup = _proxyGroups.First(); + SelectedGroup = ProxyGroups.First(); } } else @@ -246,7 +243,7 @@ public class ClashProxiesViewModel : MyReactiveObject private void RefreshProxyDetails(bool c) { - _proxyDetails.Clear(); + ProxyDetails.Clear(); if (!c) { return; @@ -299,7 +296,7 @@ public class ClashProxiesViewModel : MyReactiveObject default: break; } - _proxyDetails.AddRange(lstDetails); + ProxyDetails.AddRange(lstDetails); } private ProxiesItem? TryGetProxy(string name) @@ -361,12 +358,12 @@ public class ClashProxiesViewModel : MyReactiveObject await ClashApiManager.Instance.ClashSetActiveProxy(name, 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) { group.Now = nameNode; var group2 = JsonUtils.DeepCopy(group); - _proxyGroups.Replace(group, group2); + ProxyGroups.Replace(group, group2); SelectedGroup = group2; } @@ -375,7 +372,7 @@ public class ClashProxiesViewModel : MyReactiveObject 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()) { @@ -395,7 +392,7 @@ public class ClashProxiesViewModel : MyReactiveObject public async Task ProxiesDelayTestResult(SpeedTestResult 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) { return; @@ -417,7 +414,7 @@ public class ClashProxiesViewModel : MyReactiveObject detail.Delay = _delayTimeout; detail.DelayName = string.Empty; } - _proxyDetails.Replace(detail, JsonUtils.DeepCopy(detail)); + ProxyDetails.Replace(detail, JsonUtils.DeepCopy(detail)); } #endregion proxy function diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs index 87a3016cd2..8fc62dfe61 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs @@ -8,8 +8,8 @@ namespace ServiceLib.ViewModels; public class MsgViewModel : MyReactiveObject { - private ConcurrentQueue _queueMsg = new(); - private int _numMaxMsg = 500; + private readonly ConcurrentQueue _queueMsg = new(); + private readonly int _numMaxMsg = 500; private bool _lastMsgFilterNotAvailable; private bool _blLockShow = false; diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 058855a889..5209f4dd33 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -23,13 +23,9 @@ public class ProfilesViewModel : MyReactiveObject #region ObservableCollection - private IObservableCollection _profileItems = new ObservableCollectionExtended(); - public IObservableCollection ProfileItems => _profileItems; + public IObservableCollection ProfileItems { get; } = new ObservableCollectionExtended(); - private IObservableCollection _subItems = new ObservableCollectionExtended(); - public IObservableCollection SubItems => _subItems; - - private IObservableCollection _servers = new ObservableCollectionExtended(); + public IObservableCollection SubItems { get; } = new ObservableCollectionExtended(); [Reactive] public ProfileItemModel SelectedProfile { get; set; } @@ -293,7 +289,7 @@ public class ProfilesViewModel : MyReactiveObject NoticeManager.Instance.Enqueue(result.Delay); return; } - var item = _profileItems.FirstOrDefault(it => it.IndexId == result.IndexId); + var item = ProfileItems.FirstOrDefault(it => it.IndexId == result.IndexId); if (item == null) { return; @@ -323,7 +319,7 @@ public class ProfilesViewModel : MyReactiveObject try { - var item = _profileItems.FirstOrDefault(it => it.IndexId == update.IndexId); + var item = ProfileItems.FirstOrDefault(it => it.IndexId == update.IndexId); if (item != null) { item.TodayDown = Utils.HumanFy(update.TodayDown); @@ -390,8 +386,8 @@ public class ProfilesViewModel : MyReactiveObject var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter); _lstProfile = JsonUtils.Deserialize>(JsonUtils.Serialize(lstModel)) ?? []; - _profileItems.Clear(); - _profileItems.AddRange(lstModel); + ProfileItems.Clear(); + ProfileItems.AddRange(lstModel); if (lstModel.Count > 0) { var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); @@ -410,21 +406,21 @@ public class ProfilesViewModel : MyReactiveObject 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()) { - _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 { - SelectedSub = _subItems.First(); + SelectedSub = SubItems.First(); } } @@ -548,9 +544,9 @@ public class ProfilesViewModel : MyReactiveObject await ConfigHandler.RemoveServers(_config, lstSelected); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); - if (lstSelected.Count == _profileItems.Count) + if (lstSelected.Count == ProfileItems.Count) { - _profileItems.Clear(); + ProfileItems.Clear(); } await RefreshServers(); if (exists) @@ -740,7 +736,7 @@ public class ProfilesViewModel : MyReactiveObject 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 (await ConfigHandler.MoveServer(_config, _lstProfile, startIndex, EMove.Position, targetIndex) == 0) @@ -754,7 +750,7 @@ public class ProfilesViewModel : MyReactiveObject { if (actionType == ESpeedActionType.Mixedtest) { - SelectedProfiles = _profileItems; + SelectedProfiles = ProfileItems; } var lstSelected = await GetProfileItems(false); if (lstSelected == null) diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs index c3acc7ba18..f176e29b3b 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs @@ -13,9 +13,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject [Reactive] public RoutingItem SelectedRouting { get; set; } - - private IObservableCollection _rulesItems = new ObservableCollectionExtended(); - public IObservableCollection RulesItems => _rulesItems; + public IObservableCollection RulesItems { get; } = new ObservableCollectionExtended(); [Reactive] public RulesItemModel SelectedSource { get; set; } @@ -101,7 +99,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject public void RefreshRulesItems() { - _rulesItems.Clear(); + RulesItems.Clear(); foreach (var item in _rules) { @@ -118,7 +116,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject Enabled = item.Enabled, Remarks = item.Remarks, }; - _rulesItems.Add(it); + RulesItems.Add(it); } } diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs index 8aa36f0c27..5237a8d276 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs @@ -9,8 +9,7 @@ public class RoutingSettingViewModel : MyReactiveObject { #region Reactive - private IObservableCollection _routingItems = new ObservableCollectionExtended(); - public IObservableCollection RoutingItems => _routingItems; + public IObservableCollection RoutingItems { get; } = new ObservableCollectionExtended(); [Reactive] public RoutingItemModel SelectedSource { get; set; } @@ -82,7 +81,7 @@ public class RoutingSettingViewModel : MyReactiveObject public async Task RefreshRoutingItems() { - _routingItems.Clear(); + RoutingItems.Clear(); var routings = await AppManager.Instance.RoutingItems(); foreach (var item in routings) @@ -98,7 +97,7 @@ public class RoutingSettingViewModel : MyReactiveObject CustomRulesetPath4Singbox = item.CustomRulesetPath4Singbox, Sort = item.Sort, }; - _routingItems.Add(it); + RoutingItems.Add(it); } } diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs index c89f55cbdb..e9ee033e25 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs @@ -13,11 +13,9 @@ public class StatusBarViewModel : MyReactiveObject { #region ObservableCollection - private IObservableCollection _routingItems = new ObservableCollectionExtended(); - public IObservableCollection RoutingItems => _routingItems; + public IObservableCollection RoutingItems { get; } = new ObservableCollectionExtended(); - private IObservableCollection _servers = new ObservableCollectionExtended(); - public IObservableCollection Servers => _servers; + public IObservableCollection Servers { get; } = new ObservableCollectionExtended(); [Reactive] public RoutingItem SelectedRouting { get; set; } @@ -295,7 +293,7 @@ public class StatusBarViewModel : MyReactiveObject { var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, ""); - _servers.Clear(); + Servers.Clear(); if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit) { BlServers = false; @@ -309,7 +307,7 @@ public class StatusBarViewModel : MyReactiveObject string name = it.GetSummary(); var item = new ComboItem() { ID = it.IndexId, Text = name }; - _servers.Add(item); + Servers.Add(item); if (_config.IndexId == it.IndexId) { SelectedServer = item; @@ -397,13 +395,13 @@ public class StatusBarViewModel : MyReactiveObject public async Task RefreshRoutingsMenu() { - _routingItems.Clear(); + RoutingItems.Clear(); BlRouting = true; var routings = await AppManager.Instance.RoutingItems(); foreach (var item in routings) { - _routingItems.Add(item); + RoutingItems.Add(item); if (item.IsActive) { SelectedRouting = item; diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs index e9c657c488..88f33619c1 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs @@ -8,8 +8,7 @@ namespace ServiceLib.ViewModels; public class SubSettingViewModel : MyReactiveObject { - private IObservableCollection _subItems = new ObservableCollectionExtended(); - public IObservableCollection SubItems => _subItems; + public IObservableCollection SubItems { get; } = new ObservableCollectionExtended(); [Reactive] public SubItem SelectedSource { get; set; } @@ -60,8 +59,8 @@ public class SubSettingViewModel : MyReactiveObject public async Task RefreshSubItems() { - _subItems.Clear(); - _subItems.AddRange(await AppManager.Instance.SubItems()); + SubItems.Clear(); + SubItems.AddRange(await AppManager.Instance.SubItems()); } public async Task EditSubAsync(bool blNew) diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Assets/GlobalStyles.axaml b/v2rayn/v2rayN/v2rayN.Desktop/Assets/GlobalStyles.axaml index 40337f6193..6a2cfacd21 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Assets/GlobalStyles.axaml +++ b/v2rayn/v2rayN/v2rayN.Desktop/Assets/GlobalStyles.axaml @@ -22,4 +22,8 @@ + + diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml b/v2rayn/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml index c9cf690b6e..18f294d3d0 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml +++ b/v2rayn/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml @@ -59,7 +59,7 @@ x:Name="cmbDirectDNS" Grid.Row="1" Grid.Column="1" - Width="200" + Width="300" Margin="{StaticResource Margin4}" Text="{Binding DirectDNS, Mode=TwoWay}" /> @@ -73,7 +73,7 @@ x:Name="cmbRemoteDNS" Grid.Row="2" Grid.Column="1" - Width="200" + Width="300" Margin="{StaticResource Margin4}" Text="{Binding RemoteDNS, Mode=TwoWay}" /> @@ -87,7 +87,7 @@ x:Name="cmbSBResolverDNS" Grid.Row="3" Grid.Column="1" - Width="200" + Width="300" Margin="{StaticResource Margin4}" Text="{Binding SingboxOutboundsResolveDNS, Mode=TwoWay}" /> - diff --git a/v2rayn/v2rayN/v2rayN/Views/DNSSettingWindow.xaml b/v2rayn/v2rayN/v2rayN/Views/DNSSettingWindow.xaml index 38be609186..b0f2062cb5 100644 --- a/v2rayn/v2rayN/v2rayN/Views/DNSSettingWindow.xaml +++ b/v2rayn/v2rayN/v2rayN/Views/DNSSettingWindow.xaml @@ -80,7 +80,7 @@ x:Name="cmbDirectDNS" Grid.Row="1" Grid.Column="1" - Width="200" + Width="300" Margin="{StaticResource Margin8}" IsEditable="True" Style="{StaticResource DefComboBox}" /> @@ -96,7 +96,7 @@ x:Name="cmbRemoteDNS" Grid.Row="2" Grid.Column="1" - Width="200" + Width="300" Margin="{StaticResource Margin8}" IsEditable="True" Style="{StaticResource DefComboBox}" /> @@ -112,7 +112,7 @@ x:Name="cmbSBResolverDNS" Grid.Row="3" Grid.Column="1" - Width="200" + Width="300" Margin="{StaticResource Margin8}" IsEditable="True" Style="{StaticResource DefComboBox}" /> @@ -136,7 +136,7 @@ x:Name="cmbSBFinalResolverDNS" Grid.Row="4" Grid.Column="1" - Width="200" + Width="300" Margin="{StaticResource Margin8}" IsEditable="True" Style="{StaticResource DefComboBox}" /> diff --git a/xray-core/common/crypto/crypto.go b/xray-core/common/crypto/crypto.go index 464b4fa714..efc33a11b9 100644 --- a/xray-core/common/crypto/crypto.go +++ b/xray-core/common/crypto/crypto.go @@ -10,6 +10,9 @@ func RandBetween(from int64, to int64) int64 { if from == to { return from } + if from > to { + from, to = to, from + } bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from)) return from + bigInt.Int64() } diff --git a/xray-core/core/core.go b/xray-core/core/core.go index 7a22384385..2604f4b621 100644 --- a/xray-core/core/core.go +++ b/xray-core/core/core.go @@ -19,7 +19,7 @@ import ( var ( Version_x byte = 25 Version_y byte = 8 - Version_z byte = 29 + Version_z byte = 31 ) var ( diff --git a/xray-core/infra/conf/vless.go b/xray-core/infra/conf/vless.go index 5d10cc97a3..1dd9bad6b2 100644 --- a/xray-core/infra/conf/vless.go +++ b/xray-core/infra/conf/vless.go @@ -74,7 +74,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { } if account.Encryption != "" { - return nil, errors.New(`VLESS clients: "encryption" should not in inbound settings`) + return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`) } user.Account = serial.ToTypedMessage(account) @@ -107,12 +107,21 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { } config.Seconds = uint32(i) } - for i := 3; i < len(s); i++ { - if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 64 { + padding := 0 + for _, r := range s[3:] { + if len(r) < 20 { + padding += len(r) + 1 + continue + } + if b, _ := base64.RawURLEncoding.DecodeString(r); len(b) != 32 && len(b) != 64 { return false } } config.Decryption = config.Decryption[27+len(s[2]):] + if padding > 0 { + config.Padding = config.Decryption[:padding-1] + config.Decryption = config.Decryption[padding:] + } return true }() && config.Decryption != "none" { if config.Decryption == "" { @@ -121,6 +130,10 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { return nil, errors.New(`VLESS settings: unsupported "decryption": ` + config.Decryption) } + if config.Decryption != "none" && c.Fallbacks != nil { + return nil, errors.New(`VLESS settings: "fallbacks" can not be used together with "decryption"`) + } + for _, fb := range c.Fallbacks { var i uint16 var s string @@ -250,12 +263,21 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) { default: return false } - for i := 3; i < len(s); i++ { - if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 1184 { + padding := 0 + for _, r := range s[3:] { + if len(r) < 20 { + padding += len(r) + 1 + continue + } + if b, _ := base64.RawURLEncoding.DecodeString(r); len(b) != 32 && len(b) != 1184 { return false } } account.Encryption = account.Encryption[27+len(s[2]):] + if padding > 0 { + account.Padding = account.Encryption[:padding-1] + account.Encryption = account.Encryption[padding:] + } return true }() && account.Encryption != "none" { if account.Encryption == "" { diff --git a/xray-core/proxy/vless/account.go b/xray-core/proxy/vless/account.go index b1e09619c2..ce3eca279f 100644 --- a/xray-core/proxy/vless/account.go +++ b/xray-core/proxy/vless/account.go @@ -20,6 +20,7 @@ func (a *Account) AsAccount() (protocol.Account, error) { Encryption: a.Encryption, // needs parser here? XorMode: a.XorMode, Seconds: a.Seconds, + Padding: a.Padding, }, nil } @@ -33,6 +34,7 @@ type MemoryAccount struct { Encryption string XorMode uint32 Seconds uint32 + Padding string } // Equals implements protocol.Account.Equals(). @@ -51,5 +53,6 @@ func (a *MemoryAccount) ToProto() proto.Message { Encryption: a.Encryption, XorMode: a.XorMode, Seconds: a.Seconds, + Padding: a.Padding, } } diff --git a/xray-core/proxy/vless/account.pb.go b/xray-core/proxy/vless/account.pb.go index 6048dc4e43..b3027c47d4 100644 --- a/xray-core/proxy/vless/account.pb.go +++ b/xray-core/proxy/vless/account.pb.go @@ -32,6 +32,7 @@ type Account struct { Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"` XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"` Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"` + Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"` } func (x *Account) Reset() { @@ -99,12 +100,19 @@ func (x *Account) GetSeconds() uint32 { return 0 } +func (x *Account) GetPadding() string { + if x != nil { + return x.Padding + } + return "" +} + var File_proxy_vless_account_proto protoreflect.FileDescriptor var file_proxy_vless_account_proto_rawDesc = []byte{ 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, - 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x81, 0x01, + 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x9b, 0x01, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, @@ -113,12 +121,14 @@ var file_proxy_vless_account_proto_rawDesc = []byte{ 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, - 0x73, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, - 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, - 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, - 0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, - 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x52, 0x0a, 0x14, 0x63, + 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, + 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, + 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/xray-core/proxy/vless/account.proto b/xray-core/proxy/vless/account.proto index ebb1feff0f..dab861bcfa 100644 --- a/xray-core/proxy/vless/account.proto +++ b/xray-core/proxy/vless/account.proto @@ -15,4 +15,5 @@ message Account { string encryption = 3; uint32 xorMode = 4; uint32 seconds = 5; + string padding = 6; } diff --git a/xray-core/proxy/vless/encoding/encoding.go b/xray-core/proxy/vless/encoding/encoding.go index 1176a96039..da3a25176f 100644 --- a/xray-core/proxy/vless/encoding/encoding.go +++ b/xray-core/proxy/vless/encoding/encoding.go @@ -172,7 +172,7 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A } // XtlsRead filter and read xtls protocol -func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, peerCache *[]byte, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error { +func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error { err := func() error { for { if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { @@ -194,23 +194,17 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, if !buffer.IsEmpty() { timer.Update() if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { - // XTLS Vision processes struct Encryption Conn's peerCache or TLS Conn's input and rawInput - if peerCache != nil { - if len(*peerCache) != 0 { - buffer = buf.MergeBytes(buffer, *peerCache) - } - } else { - if inputBuffer, err := buf.ReadFrom(input); err == nil { - if !inputBuffer.IsEmpty() { - buffer, _ = buf.MergeMulti(buffer, inputBuffer) - } - } - if rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil { - if !rawInputBuffer.IsEmpty() { - buffer, _ = buf.MergeMulti(buffer, rawInputBuffer) - } - } + // XTLS Vision processes TLS-like conn's input and rawInput + if inputBuffer, err := buf.ReadFrom(input); err == nil && !inputBuffer.IsEmpty() { + buffer, _ = buf.MergeMulti(buffer, inputBuffer) } + if rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil && !rawInputBuffer.IsEmpty() { + buffer, _ = buf.MergeMulti(buffer, rawInputBuffer) + } + *input = bytes.Reader{} // release memory + input = nil + *rawInput = bytes.Buffer{} // release memory + rawInput = nil } if werr := writer.WriteMultiBuffer(buffer); werr != nil { return werr diff --git a/xray-core/proxy/vless/encryption/client.go b/xray-core/proxy/vless/encryption/client.go index 77c0b33487..54959d0ef4 100644 --- a/xray-core/proxy/vless/encryption/client.go +++ b/xray-core/proxy/vless/encryption/client.go @@ -10,7 +10,6 @@ import ( "sync" "time" - "github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/protocol" "lukechampine.com/blake3" @@ -23,6 +22,8 @@ type ClientInstance struct { RelaysLength int XorMode uint32 Seconds uint32 + PaddingLens [][3]int + PaddingGaps [][3]int RWLock sync.RWMutex Expire time.Time @@ -30,15 +31,13 @@ type ClientInstance struct { Ticket []byte } -func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (err error) { +func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) { if i.NfsPKeys != nil { - err = errors.New("already initialized") - return + return errors.New("already initialized") } l := len(nfsPKeysBytes) if l == 0 { - err = errors.New("empty nfsPKeysBytes") - return + return errors.New("empty nfsPKeysBytes") } i.NfsPKeys = make([]any, l) i.NfsPKeysBytes = nfsPKeysBytes @@ -60,7 +59,7 @@ func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) ( i.RelaysLength -= 32 i.XorMode = xorMode i.Seconds = seconds - return + return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps) } func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { @@ -71,7 +70,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { ivAndRealysLength := 16 + i.RelaysLength pfsKeyExchangeLength := 18 + 1184 + 32 + 16 - paddingLength := int(crypto.RandBetween(100, 1000)) + paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps) clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength) iv := clientHello[:16] @@ -140,10 +139,18 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) - if _, err := conn.Write(clientHello); err != nil { - return nil, err + paddingLens[0] = ivAndRealysLength + pfsKeyExchangeLength + paddingLens[0] + for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + if l > 0 { + if _, err := conn.Write(clientHello[:l]); err != nil { + return nil, err + } + clientHello = clientHello[l:] + } + if len(paddingGaps) > i { + time.Sleep(paddingGaps[i]) + } } - // padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control encryptedPfsPublicKey := make([]byte, 1088+32+16) if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { diff --git a/xray-core/proxy/vless/encryption/common.go b/xray-core/proxy/vless/encryption/common.go index 00528acc16..65959c141b 100644 --- a/xray-core/proxy/vless/encryption/common.go +++ b/xray-core/proxy/vless/encryption/common.go @@ -7,10 +7,12 @@ import ( "fmt" "io" "net" + "strconv" "strings" "sync" "time" + "github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/errors" "golang.org/x/crypto/chacha20poly1305" "lukechampine.com/blake3" @@ -31,15 +33,14 @@ type CommonConn struct { AEAD *AEAD PeerAEAD *AEAD PeerPadding []byte - PeerInBytes []byte - PeerCache []byte + rawInput bytes.Buffer + input bytes.Reader } func NewCommonConn(conn net.Conn, useAES bool) *CommonConn { return &CommonConn{ - Conn: conn, - UseAES: useAES, - PeerInBytes: make([]byte, 5+17000), // no need to use sync.Pool, because we are always reading + Conn: conn, + UseAES: useAES, } } @@ -99,16 +100,14 @@ func (c *CommonConn) Read(b []byte) (int, error) { } c.PeerPadding = nil } - if len(c.PeerCache) > 0 { - n := copy(b, c.PeerCache) - c.PeerCache = c.PeerCache[n:] - return n, nil + if c.input.Len() > 0 { + return c.input.Read(b) } - peerHeader := c.PeerInBytes[:5] - if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { + peerHeader := [5]byte{} + if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil { return 0, err } - l, err := DecodeHeader(c.PeerInBytes[:5]) // l: 17~17000 + l, err := DecodeHeader(peerHeader[:]) // l: 17~17000 if err != nil { if c.Client != nil && strings.Contains(err.Error(), "invalid header: ") { // client's 0-RTT c.Client.RWLock.Lock() @@ -121,7 +120,10 @@ func (c *CommonConn) Read(b []byte) (int, error) { return 0, err } c.Client = nil - peerData := c.PeerInBytes[5 : 5+l] + if c.rawInput.Cap() < l { + c.rawInput.Grow(l) // no need to use sync.Pool, because we are always reading + } + peerData := c.rawInput.Bytes()[:l] if _, err := io.ReadFull(c.Conn, peerData); err != nil { return 0, err } @@ -131,9 +133,9 @@ func (c *CommonConn) Read(b []byte) (int, error) { } var newAEAD *AEAD if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) { - newAEAD = NewAEAD(c.PeerInBytes[:5+l], c.UnitedKey, c.UseAES) + newAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES) } - _, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader) + _, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:]) if newAEAD != nil { c.PeerAEAD = newAEAD } @@ -141,7 +143,7 @@ func (c *CommonConn) Read(b []byte) (int, error) { return 0, err } if len(dst) > len(b) { - c.PeerCache = dst[copy(b, dst):] + c.input.Reset(dst[copy(b, dst):]) dst = b // for len(dst) } return len(dst), nil @@ -213,7 +215,66 @@ func DecodeHeader(h []byte) (l int, err error) { l = 0 } if l < 17 || l > 17000 { // TODO: TLSv1.3 max length - err = errors.New("invalid header: ", fmt.Sprintf("%v", h[:5])) // DO NOT CHANGE: relied by client's Read() + err = errors.New("invalid header: " + fmt.Sprintf("%v", h[:5])) // DO NOT CHANGE: relied by client's Read() + } + return +} + +func ParsePadding(padding string, paddingLens, paddingGaps *[][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 } diff --git a/xray-core/proxy/vless/encryption/server.go b/xray-core/proxy/vless/encryption/server.go index ec0532bbfa..89161da299 100644 --- a/xray-core/proxy/vless/encryption/server.go +++ b/xray-core/proxy/vless/encryption/server.go @@ -30,21 +30,21 @@ type ServerInstance struct { RelaysLength int XorMode uint32 Seconds uint32 + PaddingLens [][3]int + PaddingGaps [][3]int RWLock sync.RWMutex Sessions map[[16]byte]*ServerSession Closed bool } -func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) { +func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) { if i.NfsSKeys != nil { - err = errors.New("already initialized") - return + return errors.New("already initialized") } l := len(nfsSKeysBytes) if l == 0 { - err = errors.New("empty nfsSKeysBytes") - return + return errors.New("empty nfsSKeysBytes") } i.NfsSKeys = make([]any, l) i.NfsPKeysBytes = make([][]byte, l) @@ -88,7 +88,7 @@ func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) ( } }() } - return + return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps) } func (i *ServerInstance) Close() (err error) { @@ -98,7 +98,7 @@ func (i *ServerInstance) Close() (err error) { return } -func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { +func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn, error) { if i.NfsSKeys == nil { return nil, errors.New("uninitialized") } @@ -108,6 +108,9 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { if _, err := io.ReadFull(conn, ivAndRelays); err != nil { return nil, err } + if fallback != nil { + *fallback = append(*fallback, ivAndRelays...) + } iv := ivAndRelays[:16] relays := ivAndRelays[16:] var nfsKey []byte @@ -157,6 +160,9 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { if _, err := io.ReadFull(conn, encryptedLength); err != nil { return nil, err } + if fallback != nil { + *fallback = append(*fallback, encryptedLength...) + } decryptedLength := make([]byte, 2) if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil { c.UseAES = !c.UseAES @@ -165,6 +171,9 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { return nil, err } } + if fallback != nil { + *fallback = nil + } length := DecodeLength(decryptedLength) if length == 32 { @@ -183,7 +192,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { s := i.Sessions[[16]byte(ticket)] i.RWLock.RUnlock() if s == nil { - noises := make([]byte, crypto.RandBetween(1268, 2268)) // matches 1-RTT's server hello length for "random", though it is not important, just for example + noises := make([]byte, crypto.RandBetween(1279, 2279)) // matches 1-RTT's server hello length for "random", though it is not important, just for example var err error for err == nil { rand.Read(noises) @@ -237,25 +246,10 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { c.UnitedKey = append(pfsKey, nfsKey...) c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES) c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES) + ticket := make([]byte, 16) rand.Read(ticket) copy(ticket, EncodeLength(int(i.Seconds*4/5))) - - pfsKeyExchangeLength := 1088 + 32 + 16 - encryptedTicketLength := 32 - paddingLength := int(crypto.RandBetween(100, 1000)) - serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength) - nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil) - c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil) - padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:] - c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) - c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) - - if _, err := conn.Write(serverHello); err != nil { - return nil, err - } - // padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control - if i.Seconds > 0 { i.RWLock.Lock() i.Sessions[[16]byte(ticket)] = &ServerSession{ @@ -265,6 +259,29 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { i.RWLock.Unlock() } + pfsKeyExchangeLength := 1088 + 32 + 16 + encryptedTicketLength := 32 + paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps) + serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength) + nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil) + c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil) + padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:] + c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) + c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) + + paddingLens[0] = pfsKeyExchangeLength + encryptedTicketLength + paddingLens[0] + for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + if l > 0 { + if _, err := conn.Write(serverHello[:l]); err != nil { + return nil, err + } + serverHello = serverHello[l:] + } + if len(paddingGaps) > i { + time.Sleep(paddingGaps[i]) + } + } + // important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern if _, err := io.ReadFull(conn, encryptedLength); err != nil { return nil, err diff --git a/xray-core/proxy/vless/inbound/config.pb.go b/xray-core/proxy/vless/inbound/config.pb.go index e3192cf8ec..ec28db8d58 100644 --- a/xray-core/proxy/vless/inbound/config.pb.go +++ b/xray-core/proxy/vless/inbound/config.pb.go @@ -116,6 +116,7 @@ type Config struct { Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"` XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"` Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"` + Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"` } func (x *Config) Reset() { @@ -183,6 +184,13 @@ func (x *Config) GetSeconds() uint32 { return 0 } +func (x *Config) GetPadding() string { + if x != nil { + return x.Padding + } + return "" +} + var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor var file_proxy_vless_inbound_config_proto_rawDesc = []byte{ @@ -199,7 +207,7 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{ 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, - 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xd4, 0x01, + 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xee, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, @@ -213,14 +221,16 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{ 0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, - 0x6f, 0x6e, 0x64, 0x73, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, - 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, - 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, - 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, - 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x6a, + 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, + 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, + 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, + 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, + 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, + 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, + 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( diff --git a/xray-core/proxy/vless/inbound/config.proto b/xray-core/proxy/vless/inbound/config.proto index e1ebc8d370..2a7ad83366 100644 --- a/xray-core/proxy/vless/inbound/config.proto +++ b/xray-core/proxy/vless/inbound/config.proto @@ -24,4 +24,5 @@ message Config { string decryption = 3; uint32 xorMode = 4; uint32 seconds = 5; + string padding = 6; } diff --git a/xray-core/proxy/vless/inbound/inbound.go b/xray-core/proxy/vless/inbound/inbound.go index ca5176b928..617313bdde 100644 --- a/xray-core/proxy/vless/inbound/inbound.go +++ b/xray-core/proxy/vless/inbound/inbound.go @@ -92,7 +92,7 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val nfsSKeysBytes = append(nfsSKeysBytes, b) } handler.decryption = &encryption.ServerInstance{} - if err := handler.decryption.Init(nfsSKeysBytes, config.XorMode, config.Seconds); err != nil { + if err := handler.decryption.Init(nfsSKeysBytes, config.XorMode, config.Seconds, config.Padding); err != nil { return nil, errors.New("failed to use decryption").Base(err).AtError() } } @@ -220,8 +220,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s if h.decryption != nil { var err error - connection, err = h.decryption.Handshake(connection) - if err != nil { + if connection, err = h.decryption.Handshake(connection, nil); err != nil { return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() } } @@ -491,7 +490,6 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s // Flow: requestAddons.Flow, } - var peerCache *[]byte var input *bytes.Reader var rawInput *bytes.Buffer switch requestAddons.Flow { @@ -504,16 +502,15 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s case protocol.RequestCommandMux: fallthrough // we will break Mux connections that contain TCP requests case protocol.RequestCommandTCP: - if serverConn, ok := connection.(*encryption.CommonConn); ok { - peerCache = &serverConn.PeerCache - if _, ok := serverConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) { - inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice - } - break - } var t reflect.Type var p uintptr - if tlsConn, ok := iConn.(*tls.Conn); ok { + if commonConn, ok := connection.(*encryption.CommonConn); ok { + if _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) { + inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice + } + t = reflect.TypeOf(commonConn).Elem() + p = uintptr(unsafe.Pointer(commonConn)) + } else if tlsConn, ok := iConn.(*tls.Conn); ok { if tlsConn.ConnectionState().Version != gotls.VersionTLS13 { return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning() } @@ -579,7 +576,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s if requestAddons.Flow == vless.XRV { ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx1) - err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, peerCache, input, rawInput, trafficState, nil, true, ctx1) + err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, input, rawInput, trafficState, nil, true, ctx1) } else { // from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)) diff --git a/xray-core/proxy/vless/outbound/outbound.go b/xray-core/proxy/vless/outbound/outbound.go index ee1b6dfb4c..76352a0a9b 100644 --- a/xray-core/proxy/vless/outbound/outbound.go +++ b/xray-core/proxy/vless/outbound/outbound.go @@ -77,7 +77,7 @@ func New(ctx context.Context, config *Config) (*Handler, error) { nfsPKeysBytes = append(nfsPKeysBytes, b) } handler.encryption = &encryption.ClientInstance{} - if err := handler.encryption.Init(nfsPKeysBytes, a.XorMode, a.Seconds); err != nil { + if err := handler.encryption.Init(nfsPKeysBytes, a.XorMode, a.Seconds, a.Padding); err != nil { return nil, errors.New("failed to use encryption").Base(err).AtError() } } @@ -118,8 +118,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte if h.encryption != nil { var err error - conn, err = h.encryption.Handshake(conn) - if err != nil { + if conn, err = h.encryption.Handshake(conn); err != nil { return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() } } @@ -146,7 +145,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte Flow: account.Flow, } - var peerCache *[]byte var input *bytes.Reader var rawInput *bytes.Buffer allowUDP443 := false @@ -165,16 +163,15 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte case protocol.RequestCommandMux: fallthrough // let server break Mux connections that contain TCP requests case protocol.RequestCommandTCP: - if clientConn, ok := conn.(*encryption.CommonConn); ok { - peerCache = &clientConn.PeerCache - if _, ok := clientConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) { - ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice - } - break - } var t reflect.Type var p uintptr - if tlsConn, ok := iConn.(*tls.Conn); ok { + if commonConn, ok := conn.(*encryption.CommonConn); ok { + if _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) { + ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice + } + t = reflect.TypeOf(commonConn).Elem() + p = uintptr(unsafe.Pointer(commonConn)) + } else if tlsConn, ok := iConn.(*tls.Conn); ok { t = reflect.TypeOf(tlsConn.Conn).Elem() p = uintptr(unsafe.Pointer(tlsConn.Conn)) } else if utlsConn, ok := iConn.(*tls.UConn); ok { @@ -306,7 +303,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } if requestAddons.Flow == vless.XRV { - err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, peerCache, input, rawInput, trafficState, ob, false, ctx) + err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, input, rawInput, trafficState, ob, false, ctx) } else { // from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)) diff --git a/yt-dlp/yt_dlp/extractor/_extractors.py b/yt-dlp/yt_dlp/extractor/_extractors.py index 25bad4f0dc..cb41381275 100644 --- a/yt-dlp/yt_dlp/extractor/_extractors.py +++ b/yt-dlp/yt_dlp/extractor/_extractors.py @@ -2288,6 +2288,10 @@ from .varzesh3 import Varzesh3IE from .vbox7 import Vbox7IE from .veo import VeoIE from .vesti import VestiIE +from .vevo import ( + VevoIE, + VevoPlaylistIE, +) from .vgtv import ( VGTVIE, BTArticleIE, diff --git a/yt-dlp/yt_dlp/extractor/disney.py b/yt-dlp/yt_dlp/extractor/disney.py index d5cf6bbb38..a90f12389e 100644 --- a/yt-dlp/yt_dlp/extractor/disney.py +++ b/yt-dlp/yt_dlp/extractor/disney.py @@ -90,6 +90,10 @@ class DisneyIE(InfoExtractor): webpage, 'embed data'), video_id) 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'] title = video_data['title'] diff --git a/yt-dlp/yt_dlp/extractor/myspace.py b/yt-dlp/yt_dlp/extractor/myspace.py index 701c2f447f..fa2ef14e13 100644 --- a/yt-dlp/yt_dlp/extractor/myspace.py +++ b/yt-dlp/yt_dlp/extractor/myspace.py @@ -111,8 +111,12 @@ class MySpaceIE(InfoExtractor): search_data('stream-url'), search_data('hls-stream-url'), search_data('http-stream-url')) if not formats: + vevo_id = search_data('vevo-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}') return self.url_result(youtube_id, ie='Youtube') else: diff --git a/yt-dlp/yt_dlp/extractor/vevo.py b/yt-dlp/yt_dlp/extractor/vevo.py new file mode 100644 index 0000000000..8552a609c9 --- /dev/null +++ b/yt-dlp/yt_dlp/extractor/vevo.py @@ -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*', + 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[^&?#]+)''' + _EMBED_REGEX = [r']+?src=(["\'])(?P(?: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[a-z0-9]+) + _(?P[0-9]+)x(?P[0-9]+) + _(?P[a-z0-9]+) + _(?P[0-9]+) + _(?P[a-z0-9]+) + _(?P[0-9]+) + \.(?P[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/(?Pplaylist|genre)/(?P[^/?#&]+)' + + _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']+content=(["\'])vevo://video/(?P.+?)\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'))