diff --git a/.github/update.log b/.github/update.log index 5fd2a32927..68126a9444 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1093,3 +1093,4 @@ Update On Thu Aug 14 20:40:52 CEST 2025 Update On Fri Aug 15 20:40:48 CEST 2025 Update On Sat Aug 16 20:36:37 CEST 2025 Update On Sun Aug 17 20:39:03 CEST 2025 +Update On Mon Aug 18 20:42:30 CEST 2025 diff --git a/clash-meta/.github/workflows/build.yml b/clash-meta/.github/workflows/build.yml index 64cf15bd37..48858ef500 100644 --- a/clash-meta/.github/workflows/build.yml +++ b/clash-meta/.github/workflows/build.yml @@ -179,6 +179,7 @@ jobs: - name: Revert Golang1.25 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1 @@ -197,6 +198,7 @@ jobs: - name: Revert Golang1.24 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.24' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1 @@ -215,6 +217,7 @@ jobs: - name: Revert Golang1.23 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.23' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1 @@ -233,6 +236,7 @@ jobs: - name: Revert Golang1.22 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.22' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1 @@ -243,6 +247,7 @@ jobs: - name: Revert Golang1.21 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.21' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1 diff --git a/clash-meta/.github/workflows/test.yml b/clash-meta/.github/workflows/test.yml index 28af5e76d3..ab046bd17f 100644 --- a/clash-meta/.github/workflows/test.yml +++ b/clash-meta/.github/workflows/test.yml @@ -60,6 +60,7 @@ jobs: - name: Revert Golang1.25 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.25' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1 @@ -78,6 +79,7 @@ jobs: - name: Revert Golang1.24 commit for Windows7/8 if: ${{ runner.os == 'Windows' && matrix.go-version == '1.24' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1 @@ -96,6 +98,7 @@ jobs: - name: Revert Golang1.23 commit for Windows7/8 if: ${{ runner.os == 'Windows' && matrix.go-version == '1.23' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1 @@ -114,6 +117,7 @@ jobs: - name: Revert Golang1.22 commit for Windows7/8 if: ${{ runner.os == 'Windows' && matrix.go-version == '1.22' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1 @@ -124,6 +128,7 @@ jobs: - name: Revert Golang1.21 commit for Windows7/8 if: ${{ runner.os == 'Windows' && matrix.go-version == '1.21' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1 diff --git a/clash-meta/listener/inbound/vless_test.go b/clash-meta/listener/inbound/vless_test.go index c110228db3..f3fcd39cb0 100644 --- a/clash-meta/listener/inbound/vless_test.go +++ b/clash-meta/listener/inbound/vless_test.go @@ -116,6 +116,11 @@ func TestInboundVless_Encryption(t *testing.T) { Encryption: "8min-xored-mlkem768client-" + 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/transport/vless/encryption/client.go b/clash-meta/transport/vless/encryption/client.go index b6e2c19dfd..9d24903886 100644 --- a/clash-meta/transport/vless/encryption/client.go +++ b/clash-meta/transport/vless/encryption/client.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/cipher" "crypto/rand" - "crypto/sha256" "errors" "fmt" "io" @@ -15,6 +14,7 @@ import ( "time" "github.com/metacubex/utls/mlkem" + "golang.org/x/crypto/sha3" "golang.org/x/sys/cpu" ) @@ -39,6 +39,7 @@ func init() { type ClientInstance struct { sync.RWMutex nfsEKey *mlkem.EncapsulationKey768 + hash11 [11]byte // no more capacity xorKey []byte minutes time.Duration expire time.Time @@ -56,7 +57,7 @@ type ClientConn struct { nonce []byte peerAead cipher.AEAD peerNonce []byte - peerCache []byte + input bytes.Reader // peerCache } func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) { @@ -68,8 +69,10 @@ func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Dura if err != nil { return } + hash256 := sha3.Sum256(nfsEKeyBytes) + copy(i.hash11[:], hash256[:]) if xor > 0 { - xorKey := sha256.Sum256(nfsEKeyBytes) + xorKey := sha3.Sum256(nfsEKeyBytes) i.xorKey = xorKey[:] } i.minutes = minutes @@ -104,13 +107,14 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate() paddingLen := randBetween(100, 1000) - clientHello := make([]byte, 5+1+1184+1088+5+paddingLen) - EncodeHeader(clientHello, 1, 1+1184+1088) - clientHello[5] = ClientCipher - copy(clientHello[5+1:], pfsEKeyBytes) - copy(clientHello[5+1+1184:], encapsulatedNfsKey) - EncodeHeader(clientHello[5+1+1184+1088:], 23, int(paddingLen)) - rand.Read(clientHello[5+1+1184+1088+5:]) + clientHello := make([]byte, 5+11+1+1184+1088+5+paddingLen) + EncodeHeader(clientHello, 1, 11+1+1184+1088) + copy(clientHello[5:], i.hash11[:]) + clientHello[5+11] = ClientCipher + copy(clientHello[5+11+1:], pfsEKeyBytes) + copy(clientHello[5+11+1+1184:], encapsulatedNfsKey) + EncodeHeader(clientHello[5+11+1+1184+1088:], 23, int(paddingLen)) + rand.Read(clientHello[5+11+1+1184+1088+5:]) if _, err := c.Conn.Write(clientHello); err != nil { return nil, err @@ -123,17 +127,17 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { } if t != 1 { - return nil, fmt.Errorf("unexpected type %v, expect random hello", t) + return nil, fmt.Errorf("unexpected type %v, expect server hello", t) } - peerRandomHello := make([]byte, 1088+21) - if l != len(peerRandomHello) { - return nil, fmt.Errorf("unexpected length %v for random hello", l) + peerServerHello := make([]byte, 1088+21) + if l != len(peerServerHello) { + return nil, fmt.Errorf("unexpected length %v for server hello", l) } - if _, err := io.ReadFull(c.Conn, peerRandomHello); err != nil { + if _, err := io.ReadFull(c.Conn, peerServerHello); err != nil { return nil, err } - encapsulatedPfsKey := peerRandomHello[:1088] - c.ticket = peerRandomHello[1088:] + encapsulatedPfsKey := peerServerHello[:1088] + c.ticket = append(i.hash11[:], peerServerHello[1088:]...) pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey) if err != nil { @@ -141,8 +145,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { } c.baseKey = append(pfsKey, nfsKey...) - nonce := [12]byte{ClientCipher} - VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, nonce[:], c.ticket, pfsEKeyBytes) + VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes) if !bytes.Equal(VLESS, []byte("VLESS")) { return nil, errors.New("invalid server") } @@ -170,16 +173,16 @@ func (c *ClientConn) Write(b []byte) (int, error) { } n += len(b) if c.aead == nil { + data = make([]byte, 5+32+32+5+len(b)+16) + EncodeHeader(data, 0, 32+32) + copy(data[5:], c.ticket) c.random = make([]byte, 32) rand.Read(c.random) + copy(data[5+32:], c.random) + EncodeHeader(data[5+32+32:], 23, len(b)+16) c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket) c.nonce = make([]byte, 12) - data = make([]byte, 5+21+32+5+len(b)+16) - EncodeHeader(data, 0, 21+32) - copy(data[5:], c.ticket) - copy(data[5+21:], c.random) - EncodeHeader(data[5+21+32:], 23, len(b)+16) - c.aead.Seal(data[:5+21+32+5], c.nonce, b, data[5+21+32:5+21+32+5]) + c.aead.Seal(data[:5+32+32+5], c.nonce, b, data[5+32+32:5+32+32+5]) } else { data = make([]byte, 5+len(b)+16) EncodeHeader(data, 23, len(b)+16) @@ -216,23 +219,21 @@ func (c *ClientConn) Read(b []byte) (int, error) { if t != 0 { return 0, fmt.Errorf("unexpected type %v, expect server random", t) } - peerRandom := make([]byte, 32) - if l != len(peerRandom) { + peerRandomHello := make([]byte, 32) + if l != len(peerRandomHello) { return 0, fmt.Errorf("unexpected length %v for server random", l) } - if _, err := io.ReadFull(c.Conn, peerRandom); err != nil { + if _, err := io.ReadFull(c.Conn, peerRandomHello); err != nil { return 0, err } if c.random == nil { return 0, errors.New("empty c.random") } - c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandom, c.random) + c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandomHello, c.random) c.peerNonce = make([]byte, 12) } - 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) } h, t, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000 if err != nil { @@ -262,7 +263,7 @@ func (c *ClientConn) 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 diff --git a/clash-meta/transport/vless/encryption/common.go b/clash-meta/transport/vless/encryption/common.go index 151a48e40a..a67a616927 100644 --- a/clash-meta/transport/vless/encryption/common.go +++ b/clash-meta/transport/vless/encryption/common.go @@ -5,7 +5,6 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" - "crypto/sha256" "fmt" "io" "math/big" @@ -13,6 +12,7 @@ import ( "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/sha3" ) var MaxNonce = bytes.Repeat([]byte{255}, 12) @@ -75,7 +75,7 @@ func ReadAndDiscardPaddings(conn net.Conn) (h []byte, t byte, l int, err error) func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) { key := make([]byte, 32) - hkdf.New(sha256.New, secret, salt, info).Read(key) + hkdf.New(sha3.New256, secret, salt, info).Read(key) if c&1 == 1 { block, _ := aes.NewCipher(key) aead, _ = cipher.NewGCM(block) diff --git a/clash-meta/transport/vless/encryption/doc.go b/clash-meta/transport/vless/encryption/doc.go index 900866ec05..9ba7eb113d 100644 --- a/clash-meta/transport/vless/encryption/doc.go +++ b/clash-meta/transport/vless/encryption/doc.go @@ -11,4 +11,6 @@ // https://github.com/XTLS/Xray-core/commit/09cc92c61d9067e0d65c1cae9124664ecfc78f43 // https://github.com/XTLS/Xray-core/commit/2807ee432a1fbeb301815647189eacd650b12a8b // https://github.com/XTLS/Xray-core/commit/bfe4820f2f086daf639b1957eb23dc13c843cad1 +// https://github.com/XTLS/Xray-core/commit/d1fb48521271251a8c74bd64fcc2fc8700717a3b +// https://github.com/XTLS/Xray-core/commit/49580705f6029648399304b816a2737f991582a8 package encryption diff --git a/clash-meta/transport/vless/encryption/server.go b/clash-meta/transport/vless/encryption/server.go index f67c9720ce..9c5c102e8c 100644 --- a/clash-meta/transport/vless/encryption/server.go +++ b/clash-meta/transport/vless/encryption/server.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/cipher" "crypto/rand" - "crypto/sha256" "errors" "fmt" "io" @@ -13,6 +12,7 @@ import ( "time" "github.com/metacubex/utls/mlkem" + "golang.org/x/crypto/sha3" ) type ServerSession struct { @@ -25,9 +25,10 @@ type ServerSession struct { type ServerInstance struct { sync.RWMutex nfsDKey *mlkem.DecapsulationKey768 + hash11 [11]byte // no more capacity xorKey []byte minutes time.Duration - sessions map[[21]byte]*ServerSession + sessions map[[32]byte]*ServerSession closed bool } @@ -39,7 +40,7 @@ type ServerConn struct { peerRandom []byte peerAead cipher.AEAD peerNonce []byte - peerCache []byte + input bytes.Reader // peerCache aead cipher.AEAD nonce []byte } @@ -53,13 +54,15 @@ func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Durat if err != nil { return } + hash256 := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) + copy(i.hash11[:], hash256[:]) if xor > 0 { - xorKey := sha256.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) + xorKey := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) i.xorKey = xorKey[:] } if minutes > 0 { i.minutes = minutes - i.sessions = make(map[[21]byte]*ServerSession) + i.sessions = make(map[[32]byte]*ServerSession) go func() { for { time.Sleep(time.Minute) @@ -106,15 +109,18 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { if i.minutes == 0 { return nil, errors.New("0-RTT is not allowed") } - peerTicketHello := make([]byte, 21+32) + peerTicketHello := make([]byte, 32+32) if l != len(peerTicketHello) { return nil, fmt.Errorf("unexpected length %v for ticket hello", l) } if _, err := io.ReadFull(c.Conn, peerTicketHello); err != nil { return nil, err } + if !bytes.Equal(peerTicketHello[:11], i.hash11[:]) { + return nil, fmt.Errorf("unexpected hash11: %v", peerTicketHello[:11]) + } i.RLock() - s := i.sessions[[21]byte(peerTicketHello)] + s := i.sessions[[32]byte(peerTicketHello)] i.RUnlock() if s == nil { noises := make([]byte, randBetween(100, 1000)) @@ -126,26 +132,29 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { c.Conn.Write(noises) // make client do new handshake return nil, errors.New("expired ticket") } - if _, replay := s.randoms.LoadOrStore([32]byte(peerTicketHello[21:]), true); replay { + if _, replay := s.randoms.LoadOrStore([32]byte(peerTicketHello[32:]), true); replay { return nil, errors.New("replay detected") } c.cipher = s.cipher c.baseKey = s.baseKey - c.ticket = peerTicketHello[:21] - c.peerRandom = peerTicketHello[21:] + c.ticket = peerTicketHello[:32] + c.peerRandom = peerTicketHello[32:] return c, nil } - peerClientHello := make([]byte, 1+1184+1088) + peerClientHello := make([]byte, 11+1+1184+1088) if l != len(peerClientHello) { return nil, fmt.Errorf("unexpected length %v for client hello", l) } if _, err := io.ReadFull(c.Conn, peerClientHello); err != nil { return nil, err } - c.cipher = peerClientHello[0] - pfsEKeyBytes := peerClientHello[1:1185] - encapsulatedNfsKey := peerClientHello[1185:2273] + if !bytes.Equal(peerClientHello[:11], i.hash11[:]) { + return nil, fmt.Errorf("unexpected hash11: %v", peerClientHello[:11]) + } + c.cipher = peerClientHello[11] + pfsEKeyBytes := peerClientHello[11+1 : 11+1+1184] + encapsulatedNfsKey := peerClientHello[11+1+1184:] pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes) if err != nil { @@ -158,15 +167,14 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate() c.baseKey = append(pfsKey, nfsKey...) - nonce := [12]byte{c.cipher} - c.ticket = NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, nonce[:], []byte("VLESS"), pfsEKeyBytes) + c.ticket = append(i.hash11[:], NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, peerClientHello[:12], []byte("VLESS"), pfsEKeyBytes)...) paddingLen := randBetween(100, 1000) serverHello := make([]byte, 5+1088+21+5+paddingLen) EncodeHeader(serverHello, 1, 1088+21) copy(serverHello[5:], encapsulatedPfsKey) - copy(serverHello[5+1088:], c.ticket) + copy(serverHello[5+1088:], c.ticket[11:]) EncodeHeader(serverHello[5+1088+21:], 23, int(paddingLen)) rand.Read(serverHello[5+1088+21+5:]) @@ -177,7 +185,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { if i.minutes > 0 { i.Lock() - i.sessions[[21]byte(c.ticket)] = &ServerSession{ + i.sessions[[32]byte(c.ticket)] = &ServerSession{ expire: time.Now().Add(i.minutes), cipher: c.cipher, baseKey: c.baseKey, @@ -201,25 +209,23 @@ func (c *ServerConn) Read(b []byte) (int, error) { if t != 0 { return 0, fmt.Errorf("unexpected type %v, expect ticket hello", t) } - peerTicketHello := make([]byte, 21+32) + peerTicketHello := make([]byte, 32+32) if l != len(peerTicketHello) { return 0, fmt.Errorf("unexpected length %v for ticket hello", l) } if _, err := io.ReadFull(c.Conn, peerTicketHello); err != nil { return 0, err } - if !bytes.Equal(peerTicketHello[:21], c.ticket) { + if !bytes.Equal(peerTicketHello[:32], c.ticket) { return 0, errors.New("naughty boy") } - c.peerRandom = peerTicketHello[21:] + c.peerRandom = peerTicketHello[32:] } c.peerAead = NewAead(c.cipher, c.baseKey, c.peerRandom, c.ticket) c.peerNonce = make([]byte, 12) } - 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) } h, t, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000 if err != nil { @@ -249,7 +255,7 @@ func (c *ServerConn) 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 @@ -273,9 +279,9 @@ func (c *ServerConn) Write(b []byte) (int, error) { data = make([]byte, 5+32+5+len(b)+16) EncodeHeader(data, 0, 32) rand.Read(data[5 : 5+32]) + EncodeHeader(data[5+32:], 23, len(b)+16) c.aead = NewAead(c.cipher, c.baseKey, data[5:5+32], c.peerRandom) c.nonce = make([]byte, 12) - EncodeHeader(data[5+32:], 23, len(b)+16) c.aead.Seal(data[:5+32+5], c.nonce, b, data[5+32:5+32+5]) } else { data = make([]byte, 5+len(b)+16) diff --git a/clash-meta/transport/vless/encryption/xor.go b/clash-meta/transport/vless/encryption/xor.go index 64828eaa26..69ff15780f 100644 --- a/clash-meta/transport/vless/encryption/xor.go +++ b/clash-meta/transport/vless/encryption/xor.go @@ -15,6 +15,14 @@ type XorConn struct { peerCtr cipher.Stream isHeader bool skipNext bool + + out_after0 bool + out_header []byte + out_skip int + + in_after0 bool + in_header []byte + in_skip int } func NewXorConn(conn net.Conn, key []byte) *XorConn { @@ -26,29 +34,56 @@ func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records if len(b) == 0 { return 0, nil } - var iv []byte - if c.ctr == nil { - block, _ := aes.NewCipher(c.key) - iv = make([]byte, 16) - rand.Read(iv) - c.ctr = cipher.NewCTR(block, iv) + if !c.out_after0 { + var iv []byte + if c.ctr == nil { + block, _ := aes.NewCipher(c.key) + iv = make([]byte, 16) + rand.Read(iv) + c.ctr = cipher.NewCTR(block, iv) + } + t, l, _ := DecodeHeader(b) + if t == 23 { // single 23 + l = 5 + } else { // 1/0 + 23, or noises only + l += 10 + if t == 0 { + c.out_after0 = true + } + } + c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard b + if iv != nil { + b = append(iv, b...) + } + if _, err := c.Conn.Write(b); err != nil { + return 0, err + } + if iv != nil { + b = b[16:] // for len(b) + } + return len(b), nil } - t, l, _ := DecodeHeader(b) - if t == 23 { // single 23 - l = 5 - } else { // 1/0 + 23, or noises only - l += 10 - } - c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard b - if iv != nil { - b = append(iv, b...) + for p := b; ; { // for XTLS + if len(p) <= c.out_skip { + c.out_skip -= len(p) + break + } + p = p[c.out_skip:] + c.out_skip = 0 + need := 5 - len(c.out_header) + if len(p) < need { + c.out_header = append(c.out_header, p...) + c.ctr.XORKeyStream(p, p) + break + } + _, c.out_skip, _ = DecodeHeader(append(c.out_header, p[:need]...)) + c.out_header = make([]byte, 0, 5) // DO NOT CHANGE + c.ctr.XORKeyStream(p[:need], p[:need]) + p = p[need:] } if _, err := c.Conn.Write(b); err != nil { return 0, err } - if iv != nil { - b = b[16:] // for len(b) - } return len(b), nil } @@ -56,31 +91,56 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes... if len(b) == 0 { return 0, nil } - if c.peerCtr == nil { - peerIv := make([]byte, 16) - if _, err := io.ReadFull(c.Conn, peerIv); err != nil { + if !c.in_after0 || !c.isHeader { + if c.peerCtr == nil { + peerIv := make([]byte, 16) + if _, err := io.ReadFull(c.Conn, peerIv); err != nil { + return 0, err + } + block, _ := aes.NewCipher(c.key) + c.peerCtr = cipher.NewCTR(block, peerIv) + c.isHeader = true + } + if _, err := io.ReadFull(c.Conn, b); err != nil { return 0, err } - block, _ := aes.NewCipher(c.key) - c.peerCtr = cipher.NewCTR(block, peerIv) - c.isHeader = true - } - if _, err := io.ReadFull(c.Conn, b); err != nil { - return 0, err - } - if c.skipNext { - c.skipNext = false + if c.skipNext { + c.skipNext = false + return len(b), nil + } + c.peerCtr.XORKeyStream(b, b) + if c.isHeader { // always 5-bytes + if t, _, _ := DecodeHeader(b); t == 23 { + c.skipNext = true + } else { + c.isHeader = false + if t == 0 { + c.in_after0 = true + } + } + } else { + c.isHeader = true + } return len(b), nil } - c.peerCtr.XORKeyStream(b, b) - if c.isHeader { // always 5-bytes - if t, _, _ := DecodeHeader(b); t == 23 { - c.skipNext = true - } else { - c.isHeader = false + n, err := c.Conn.Read(b) + for p := b[:n]; ; { // for XTLS + if len(p) <= c.in_skip { + c.in_skip -= len(p) + break } - } else { - c.isHeader = true + p = p[c.in_skip:] + c.in_skip = 0 + need := 5 - len(c.in_header) + if len(p) < need { + c.peerCtr.XORKeyStream(p, p) + c.in_header = append(c.in_header, p...) + break + } + c.peerCtr.XORKeyStream(p[:need], p[:need]) + _, c.in_skip, _ = DecodeHeader(append(c.in_header, p[:need]...)) + c.in_header = make([]byte, 0, 5) // DO NOT CHANGE + p = p[need:] } - return len(b), nil + return n, err } diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index 2df5c2fab9..b1ed37f991 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -352,7 +352,7 @@ dependencies = [ "objc2-foundation 0.3.1", "parking_lot", "percent-encoding", - "windows-sys 0.59.0", + "windows-sys 0.52.0", "wl-clipboard-rs", "x11rb", ] @@ -1601,7 +1601,7 @@ dependencies = [ "specta", "specta-typescript", "strum 0.27.2", - "sysinfo 0.36.1", + "sysinfo", "sysproxy", "tauri", "tauri-build", @@ -1693,7 +1693,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -2363,9 +2363,9 @@ dependencies = [ [[package]] name = "display-info" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8ffedfe3971acf2350b12253a7c8845822a28c1fd07fe4feb2d03cffa3b24f" +checksum = "d1fc23a435d3010e7fdd1b8bc9e687efff051bfa6cf7d2caeb9f6f770e8fb8b1" dependencies = [ "fxhash", "log", @@ -2399,7 +2399,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.8", + "libloading 0.7.4", ] [[package]] @@ -2838,7 +2838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -5002,7 +5002,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-targets 0.48.5", ] [[package]] @@ -5904,7 +5904,7 @@ dependencies = [ "serde", "shared_child", "specta", - "sysinfo 0.37.0", + "sysinfo", "thiserror 2.0.15", "tokio", "tracing", @@ -6552,9 +6552,9 @@ dependencies = [ [[package]] name = "oxc_allocator" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b37f326db7599f4b4d10b56f696a959d949f5cef8538ecae7adcc3c161aa192c" +checksum = "01443e3e95c7916a6a260335ebd4dee8775ad5e2f285adcf49bd35ddff49cf4d" dependencies = [ "allocator-api2", "bumpalo", @@ -6565,9 +6565,9 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4259700fbdd926114ade8041ed68dbb83917acaca4669526b1ab29ae79f79e4d" +checksum = "0ee82b1e75533bd63802583d42cff22173e799bff19681fbd300a254585cf6b8" dependencies = [ "bitflags 2.9.2", "oxc_allocator", @@ -6581,9 +6581,9 @@ dependencies = [ [[package]] name = "oxc_ast_macros" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60269747adf5090dfa21a3ff408b7dd62627282e7e85030b08bec55dce07972f" +checksum = "5a119414d0ea2d4ead85d7d9095f59a248fa1eb8e0832687016180655fc2b622" dependencies = [ "phf 0.12.1", "proc-macro2", @@ -6593,9 +6593,9 @@ dependencies = [ [[package]] name = "oxc_ast_visit" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b27076752c60c46fc37639ff3042549437827fdc5d22641a7b592fb7bbcb5d7c" +checksum = "f393c0ed3f2750ed971d1da803c6816f9c2e1881d0261d3a7220d67c27fedbf0" dependencies = [ "oxc_allocator", "oxc_ast", @@ -6605,15 +6605,15 @@ dependencies = [ [[package]] name = "oxc_data_structures" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b99bd27751e6e99cdc0e9e8b6a2d88d18bd6cdb3516aa516466080b2b4c3d13" +checksum = "fd798dc264a18b4c472246ca60f4bc9d723e679e57f8b1daa3af65bacccb2987" [[package]] name = "oxc_diagnostics" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1963cb534cda91da652b9b6c35eeb1f099aef273457268fe43f7f6e7e3a82d40" +checksum = "b5a103b0344d5b9bc38db54b2214ae2f45aebf6bc5473ed0a204ae458be8b0a6" dependencies = [ "cow-utils", "oxc-miette", @@ -6622,9 +6622,9 @@ dependencies = [ [[package]] name = "oxc_ecmascript" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aaa81649df64a361d55a0356ada772edd53a3a4f7f695f987528a401ba65431" +checksum = "0c128f12fc96dc9e9694ea97e49869f1f181f9afc3e90bc94f677d8cb85ecee2" dependencies = [ "cow-utils", "num-bigint", @@ -6637,9 +6637,9 @@ dependencies = [ [[package]] name = "oxc_estree" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dc17babfecb9b93e5df7f52ea2d01990b93a129d8b9185fc5a9e8a22b46d1a3" +checksum = "25b152c37513a595c4013f1da6c32a5652da2bbeea8dee2c94231be9e29f9511" [[package]] name = "oxc_index" @@ -6649,9 +6649,9 @@ checksum = "2fa07b0cfa997730afed43705766ef27792873fdf5215b1391949fec678d2392" [[package]] name = "oxc_parser" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b6b70cd5b179a34289003d84d4de9abb920ddcc25f70ff10cddf6f2e5d97ed" +checksum = "78d36a87565a624893fff4c2ed46eafe9dd447a0dd82cbcfaf9b2a52e35a84ba" dependencies = [ "bitflags 2.9.2", "cow-utils", @@ -6672,9 +6672,9 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ddf077833348e25ea39a0b95b3490abafa02daa87bbd8260e60d64ff3f2c3b" +checksum = "81876a23eb6e3d951c8d23066d9377cf2b5dd66035b00f38ffdd21b29c2694a1" dependencies = [ "bitflags 2.9.2", "oxc_allocator", @@ -6688,9 +6688,9 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8740fd6737f52f14a14c28434d144e8491816812f7401e6a889bbaa595c074" +checksum = "8352ac817efc16314a8da4a8f3c73275b0e1d364a2fb4903092e1ac971806229" dependencies = [ "compact_str", "oxc-miette", @@ -6701,9 +6701,9 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.82.1" +version = "0.82.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e460636ae51b185fa38507402ee11c6d7b5998fe392aa64c1b3c2c9f5dbd611" +checksum = "9dcec4c2eea751e2823f814ba0e543a1ce64288154b0d784e883d46c216da92f" dependencies = [ "bitflags 2.9.2", "cow-utils", @@ -7436,7 +7436,7 @@ dependencies = [ "once_cell", "socket2 0.5.10", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -8007,7 +8007,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -8020,7 +8020,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -9077,20 +9077,6 @@ dependencies = [ "libc", ] -[[package]] -name = "sysinfo" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d" -dependencies = [ - "libc", - "memchr", - "ntapi", - "objc2-core-foundation", - "objc2-io-kit", - "windows 0.61.3", -] - [[package]] name = "sysinfo" version = "0.37.0" @@ -9690,7 +9676,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.8", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -11401,7 +11387,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/clash-nyanpasu/backend/tauri/Cargo.toml b/clash-nyanpasu/backend/tauri/Cargo.toml index d01093406f..0c435b7aad 100644 --- a/clash-nyanpasu/backend/tauri/Cargo.toml +++ b/clash-nyanpasu/backend/tauri/Cargo.toml @@ -134,7 +134,7 @@ runas = { git = "https://github.com/libnyanpasu/rust-runas.git" } single-instance = "0.3.3" which = "8" open = "5.0.1" -sysinfo = "0.36" +sysinfo = "0.37" num_cpus = "1" os_pipe = "1.2.1" whoami = "1.5.1" diff --git a/clash-nyanpasu/frontend/interface/package.json b/clash-nyanpasu/frontend/interface/package.json index ca4ad3d6b3..1ac126e12a 100644 --- a/clash-nyanpasu/frontend/interface/package.json +++ b/clash-nyanpasu/frontend/interface/package.json @@ -13,7 +13,7 @@ "dependencies": { "@tanstack/react-query": "5.84.2", "@tauri-apps/api": "2.6.0", - "ahooks": "3.9.1", + "ahooks": "3.9.3", "dayjs": "1.11.13", "lodash-es": "4.17.21", "ofetch": "1.4.1", diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index c7d723efdf..7482c2eb3b 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -26,7 +26,7 @@ "@tanstack/router-zod-adapter": "1.81.5", "@tauri-apps/api": "2.6.0", "@types/json-schema": "7.0.15", - "ahooks": "3.9.1", + "ahooks": "3.9.3", "allotment": "1.20.4", "country-code-emoji": "2.3.0", "country-emoji": "1.5.6", @@ -59,9 +59,9 @@ "@iconify/json": "2.2.375", "@monaco-editor/react": "4.7.0", "@tanstack/react-query": "5.84.2", - "@tanstack/react-router": "1.131.22", - "@tanstack/react-router-devtools": "1.131.22", - "@tanstack/router-plugin": "1.131.22", + "@tanstack/react-router": "1.131.26", + "@tanstack/react-router-devtools": "1.131.26", + "@tanstack/router-plugin": "1.131.26", "@tauri-apps/plugin-clipboard-manager": "2.3.0", "@tauri-apps/plugin-dialog": "2.3.0", "@tauri-apps/plugin-fs": "2.4.0", diff --git a/clash-nyanpasu/frontend/ui/package.json b/clash-nyanpasu/frontend/ui/package.json index f2f893b99b..65bbea8dc4 100644 --- a/clash-nyanpasu/frontend/ui/package.json +++ b/clash-nyanpasu/frontend/ui/package.json @@ -21,7 +21,7 @@ "@types/d3": "7.4.3", "@types/react": "19.1.10", "@vitejs/plugin-react": "5.0.0", - "ahooks": "3.9.1", + "ahooks": "3.9.3", "d3": "7.9.0", "framer-motion": "12.23.12", "react": "19.1.1", diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index e0377d86d3..50e435325d 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,10 +2,10 @@ "manifest_version": 1, "latest": { "mihomo": "v1.19.12", - "mihomo_alpha": "alpha-76e40ba", + "mihomo_alpha": "alpha-b481eca", "clash_rs": "v0.8.2", "clash_premium": "2023-09-05-gdcc8d87", - "clash_rs_alpha": "0.8.2-alpha+sha.25b7dd9" + "clash_rs_alpha": "0.8.2-alpha+sha.3010e42" }, "arch_template": { "mihomo": { @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-08-15T22:21:20.039Z" + "updated_at": "2025-08-17T22:21:09.107Z" } diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 553c815fc6..90934d73e8 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -182,8 +182,8 @@ importers: specifier: 2.6.0 version: 2.6.0 ahooks: - specifier: 3.9.1 - version: 3.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 3.9.3 + version: 3.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) dayjs: specifier: 1.11.13 version: 1.11.13 @@ -250,7 +250,7 @@ importers: version: 4.1.11 '@tanstack/router-zod-adapter': specifier: 1.81.5 - version: 1.81.5(@tanstack/react-router@1.131.22(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(zod@4.0.17) + version: 1.81.5(@tanstack/react-router@1.131.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(zod@4.0.17) '@tauri-apps/api': specifier: 2.6.0 version: 2.6.0 @@ -258,8 +258,8 @@ importers: specifier: 7.0.15 version: 7.0.15 ahooks: - specifier: 3.9.1 - version: 3.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 3.9.3 + version: 3.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) allotment: specifier: 1.20.4 version: 1.20.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -352,14 +352,14 @@ importers: specifier: 5.84.2 version: 5.84.2(react@19.1.1) '@tanstack/react-router': - specifier: 1.131.22 - version: 1.131.22(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.131.26 + version: 1.131.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/react-router-devtools': - specifier: 1.131.22 - version: 1.131.22(@tanstack/react-router@1.131.22(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.22)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)(tiny-invariant@1.3.3) + specifier: 1.131.26 + version: 1.131.26(@tanstack/react-router@1.131.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.26)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)(tiny-invariant@1.3.3) '@tanstack/router-plugin': - specifier: 1.131.22 - version: 1.131.22(@tanstack/react-router@1.131.22(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.2(@types/node@22.17.2)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) + specifier: 1.131.26 + version: 1.131.26(@tanstack/react-router@1.131.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.2(@types/node@22.17.2)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) '@tauri-apps/plugin-clipboard-manager': specifier: 2.3.0 version: 2.3.0 @@ -490,8 +490,8 @@ importers: specifier: 5.0.0 version: 5.0.0(vite@7.1.2(@types/node@22.17.2)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1)) ahooks: - specifier: 3.9.1 - version: 3.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 3.9.3 + version: 3.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) d3: specifier: 7.9.0 version: 7.9.0 @@ -2945,16 +2945,16 @@ packages: peerDependencies: react: ^18 || ^19 - '@tanstack/react-router-devtools@1.131.22': - resolution: {integrity: sha512-1IEN2wyb3wM3FjyOy6WX1SGAXmofqerAqaZWEXwQ6NcOOa+r46q3MYza/MOymghchEWA2Y7RkXruqcBPa9vu6g==} + '@tanstack/react-router-devtools@1.131.26': + resolution: {integrity: sha512-QdDF2t3ILZLqblBYDWQXpQ8QsHzo2ZJcWhaeQEdAkMZ0w0mlfKdZKOGigA21KvDbyTOgkfuQBj+DlkiQPqKYMA==} engines: {node: '>=12'} peerDependencies: - '@tanstack/react-router': ^1.131.22 + '@tanstack/react-router': ^1.131.26 react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router@1.131.22': - resolution: {integrity: sha512-gLCAVIglv25MRXMcYQ6XR7FTN93qAR7P3ITMaejDtWofAcqVYJWs3ou1VaXXlOHklEPNgvTa8rcuAD6GQtRzFA==} + '@tanstack/react-router@1.131.26': + resolution: {integrity: sha512-bXfONifen0f3EBfHXTSSCQMT/svV+/te/ncgZSUdxrN/nE01GqGsBvD590wOQMV9CBw5iqFfxEM3kA5GM3RhXw==} engines: {node: '>=12'} peerDependencies: react: '>=18.0.0 || >=19.0.0' @@ -2979,15 +2979,15 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.131.22': - resolution: {integrity: sha512-lOmX1fzEriOlxJzrkg+QT2897pscJmFrmp4Tz2uEejaNS/dwC0B9wQ8EwSgE9vwmD3OFwaNNjWkvAB37NVYj+g==} + '@tanstack/router-core@1.131.26': + resolution: {integrity: sha512-MED3i/vhHqBFfQZp309JduePtnJwG30KTM+swKgBWBwDoQHvYbtTWhJKPAm1EhkuFyIXuZo/mWTCwdzo/Te7pA==} engines: {node: '>=12'} - '@tanstack/router-devtools-core@1.131.22': - resolution: {integrity: sha512-YbQFAE2BtGXbW5mLEDteiKLmArtLxqOKolJSDNMjaLjX4HQAJKRn1uRs5iNJpgCBBy4KK1AW1bdbPQdAgBgTWA==} + '@tanstack/router-devtools-core@1.131.26': + resolution: {integrity: sha512-TGHmRDQpYphuRbDH+jJp418vQuIydzITaUx7MiPk5U1ZZ+2O/GxcF/ycXmyYR0IHTpSky35I83X3bKTiv+thyw==} engines: {node: '>=12'} peerDependencies: - '@tanstack/router-core': ^1.131.22 + '@tanstack/router-core': ^1.131.26 csstype: ^3.0.10 solid-js: '>=1.9.5' tiny-invariant: ^1.3.3 @@ -2995,16 +2995,16 @@ packages: csstype: optional: true - '@tanstack/router-generator@1.131.22': - resolution: {integrity: sha512-iFlxngM+c4MaIyKIaqBx0F89tp8mjuAjVSylE2sE3sekF4Irh9pfAHqNZYMFgou4aZR9BPHvcU6cuDO7mQBHjg==} + '@tanstack/router-generator@1.131.26': + resolution: {integrity: sha512-FUeFIBp22XQ985Xuy+8YGSm/Spe9/ZJIZKJlgDokq09b3HTJTGydne8aXPUtemaOkiA50LOUMxrZV2dBtSbB9w==} engines: {node: '>=12'} - '@tanstack/router-plugin@1.131.22': - resolution: {integrity: sha512-JyHI0r1CvfgpG7XFwdEr9+i6Bql3E6s27lbYS2qYX2cjZkv9Vte95jGJ0Qk1XseNx97qGWYT5eVpfmqMz+1+dQ==} + '@tanstack/router-plugin@1.131.26': + resolution: {integrity: sha512-fw4XxcoGHAdNIikfqyxDyF2Sxku99/LKB7BFopPW3EPnjBMul/XpglTzoe7FMKdoBjts4961rr54/QwimeYnug==} engines: {node: '>=12'} peerDependencies: '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.131.22 + '@tanstack/react-router': ^1.131.26 vite: '>=5.0.0 || >=6.0.0' vite-plugin-solid: ^2.11.2 webpack: '>=5.92.0' @@ -3679,9 +3679,9 @@ packages: resolution: {integrity: sha512-JVzqkCNRT+VfqzzgPWDPnwvDheSAUdiMUn3NoLXpDJF5lRqeJqyC9iGsAxIOAW+mzIdq+uP1TvcX6bMtrH0agg==} engines: {node: '>= 14'} - ahooks@3.9.1: - resolution: {integrity: sha512-5HaldTF43CywmC9qsVqXjbszimAKXXtPKQp0NUtelml0BM3x8i2rq/XNcvjQnw2w7AxbhFQSPDYTdbr1CkbRfg==} - engines: {node: '>=20'} + ahooks@3.9.3: + resolution: {integrity: sha512-pw4oNW9at8c/EDpZEQY/2rcU1NQw1GOzw6CUlDPMKUp1hh79NZVfPxgUUhUVKs8Dr48GWYC5ILovuQSO5CeH0A==} + engines: {node: '>=18'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -11055,10 +11055,10 @@ snapshots: '@tanstack/query-core': 5.83.1 react: 19.1.1 - '@tanstack/react-router-devtools@1.131.22(@tanstack/react-router@1.131.22(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.22)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)(tiny-invariant@1.3.3)': + '@tanstack/react-router-devtools@1.131.26(@tanstack/react-router@1.131.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.26)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)(tiny-invariant@1.3.3)': dependencies: - '@tanstack/react-router': 1.131.22(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/router-devtools-core': 1.131.22(@tanstack/router-core@1.131.22)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) + '@tanstack/react-router': 1.131.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tanstack/router-devtools-core': 1.131.26(@tanstack/router-core@1.131.26)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) transitivePeerDependencies: @@ -11067,11 +11067,11 @@ snapshots: - solid-js - tiny-invariant - '@tanstack/react-router@1.131.22(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@tanstack/react-router@1.131.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@tanstack/history': 1.131.2 '@tanstack/react-store': 0.7.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/router-core': 1.131.22 + '@tanstack/router-core': 1.131.26 isbot: 5.1.28 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -11097,7 +11097,7 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@tanstack/router-core@1.131.22': + '@tanstack/router-core@1.131.26': dependencies: '@tanstack/history': 1.131.2 '@tanstack/store': 0.7.0 @@ -11107,9 +11107,9 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/router-devtools-core@1.131.22(@tanstack/router-core@1.131.22)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)': + '@tanstack/router-devtools-core@1.131.26(@tanstack/router-core@1.131.26)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)': dependencies: - '@tanstack/router-core': 1.131.22 + '@tanstack/router-core': 1.131.26 clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) solid-js: 1.9.5 @@ -11117,9 +11117,9 @@ snapshots: optionalDependencies: csstype: 3.1.3 - '@tanstack/router-generator@1.131.22': + '@tanstack/router-generator@1.131.26': dependencies: - '@tanstack/router-core': 1.131.22 + '@tanstack/router-core': 1.131.26 '@tanstack/router-utils': 1.131.2 '@tanstack/virtual-file-routes': 1.131.2 prettier: 3.6.2 @@ -11130,7 +11130,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.131.22(@tanstack/react-router@1.131.22(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.2(@types/node@22.17.2)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1))': + '@tanstack/router-plugin@1.131.26(@tanstack/react-router@1.131.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.2(@types/node@22.17.2)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) @@ -11138,8 +11138,8 @@ snapshots: '@babel/template': 7.27.2 '@babel/traverse': 7.28.0 '@babel/types': 7.28.1 - '@tanstack/router-core': 1.131.22 - '@tanstack/router-generator': 1.131.22 + '@tanstack/router-core': 1.131.26 + '@tanstack/router-generator': 1.131.26 '@tanstack/router-utils': 1.131.2 '@tanstack/virtual-file-routes': 1.131.2 babel-dead-code-elimination: 1.0.10 @@ -11147,7 +11147,7 @@ snapshots: unplugin: 2.3.5 zod: 3.25.76 optionalDependencies: - '@tanstack/react-router': 1.131.22(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tanstack/react-router': 1.131.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) vite: 7.1.2(@types/node@22.17.2)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.4)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -11163,9 +11163,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.131.22(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(zod@4.0.17)': + '@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.131.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(zod@4.0.17)': dependencies: - '@tanstack/react-router': 1.131.22(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tanstack/react-router': 1.131.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) zod: 4.0.17 '@tanstack/store@0.7.0': {} @@ -11871,7 +11871,7 @@ snapshots: transitivePeerDependencies: - supports-color - ahooks@3.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + ahooks@3.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@babel/runtime': 7.28.2 dayjs: 1.11.13 diff --git a/lede/package/libs/openssl/Makefile b/lede/package/libs/openssl/Makefile index 7e3afb5b8f..8a74c1b5a7 100644 --- a/lede/package/libs/openssl/Makefile +++ b/lede/package/libs/openssl/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=openssl -PKG_VERSION:=3.5.1 +PKG_VERSION:=3.5.2 PKG_RELEASE:=1 PKG_USE_MIPS16:=0 PKG_BUILD_FLAGS:=gc-sections no-lto @@ -22,10 +22,10 @@ PKG_SOURCE_URL:= \ https://www.openssl.org/source/old/$(PKG_BASE)/ \ https://github.com/openssl/openssl/releases/download/$(PKG_NAME)-$(PKG_VERSION)/ -PKG_HASH:=529043b15cffa5f36077a4d0af83f3de399807181d607441d734196d889b641f +PKG_HASH:=c53a47e5e441c930c3928cf7bf6fb00e5d129b630e0aa873b08258656e7345ec PKG_LICENSE:=Apache-2.0 -PKG_LICENSE_FILES:=LICENSE +PKG_LICENSE_FILES:=LICENSE.txt PKG_MAINTAINER:=Eneas U de Queiroz PKG_CPE_ID:=cpe:/a:openssl:openssl PKG_CONFIG_DEPENDS:= \ diff --git a/lede/target/linux/generic/backport-6.12/620-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch b/lede/target/linux/generic/backport-6.12/620-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch new file mode 100644 index 0000000000..64effc5ec8 --- /dev/null +++ b/lede/target/linux/generic/backport-6.12/620-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch @@ -0,0 +1,79 @@ +From: Qingfang Deng +Date: Sat, 1 Mar 2025 21:55:16 +0800 +Subject: [PATCH] ppp: use IFF_NO_QUEUE in virtual interfaces +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +For PPPoE, PPTP, and PPPoL2TP, the start_xmit() function directly +forwards packets to the underlying network stack and never returns +anything other than 1. So these interfaces do not require a qdisc, +and the IFF_NO_QUEUE flag should be set. + +Introduces a direct_xmit flag in struct ppp_channel to indicate when +IFF_NO_QUEUE should be applied. The flag is set in ppp_connect_channel() +for relevant protocols. + +While at it, remove the usused latency member from struct ppp_channel. + +Signed-off-by: Qingfang Deng +Reviewed-by: Toke Høiland-Jørgensen +Link: https://patch.msgid.link/20250301135517.695809-1-dqfext@gmail.com +Signed-off-by: Jakub Kicinski +--- + +--- a/drivers/net/ppp/ppp_generic.c ++++ b/drivers/net/ppp/ppp_generic.c +@@ -3500,6 +3500,10 @@ ppp_connect_channel(struct channel *pch, + ret = -ENOTCONN; + goto outl; + } ++ if (pch->chan->direct_xmit) ++ ppp->dev->priv_flags |= IFF_NO_QUEUE; ++ else ++ ppp->dev->priv_flags &= ~IFF_NO_QUEUE; + spin_unlock_bh(&pch->downl); + if (pch->file.hdrlen > ppp->file.hdrlen) + ppp->file.hdrlen = pch->file.hdrlen; +--- a/drivers/net/ppp/pppoe.c ++++ b/drivers/net/ppp/pppoe.c +@@ -693,6 +693,7 @@ static int pppoe_connect(struct socket * + po->chan.mtu = dev->mtu - sizeof(struct pppoe_hdr) - 2; + po->chan.private = sk; + po->chan.ops = &pppoe_chan_ops; ++ po->chan.direct_xmit = true; + + error = ppp_register_net_channel(dev_net(dev), &po->chan); + if (error) { +--- a/drivers/net/ppp/pptp.c ++++ b/drivers/net/ppp/pptp.c +@@ -465,6 +465,7 @@ static int pptp_connect(struct socket *s + po->chan.mtu -= PPTP_HEADER_OVERHEAD; + + po->chan.hdrlen = 2 + sizeof(struct pptp_gre_header); ++ po->chan.direct_xmit = true; + error = ppp_register_channel(&po->chan); + if (error) { + pr_err("PPTP: failed to register PPP channel (%d)\n", error); +--- a/include/linux/ppp_channel.h ++++ b/include/linux/ppp_channel.h +@@ -42,8 +42,7 @@ struct ppp_channel { + int hdrlen; /* amount of headroom channel needs */ + void *ppp; /* opaque to channel */ + int speed; /* transfer rate (bytes/second) */ +- /* the following is not used at present */ +- int latency; /* overhead time in milliseconds */ ++ bool direct_xmit; /* no qdisc, xmit directly */ + }; + + #ifdef __KERNEL__ +--- a/net/l2tp/l2tp_ppp.c ++++ b/net/l2tp/l2tp_ppp.c +@@ -806,6 +806,7 @@ static int pppol2tp_connect(struct socke + po->chan.private = sk; + po->chan.ops = &pppol2tp_chan_ops; + po->chan.mtu = pppol2tp_tunnel_mtu(tunnel); ++ po->chan.direct_xmit = true; + + error = ppp_register_net_channel(sock_net(sk), &po->chan); + if (error) { diff --git a/lede/target/linux/generic/backport-6.6/624-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch b/lede/target/linux/generic/backport-6.6/624-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch new file mode 100644 index 0000000000..ad98807a6f --- /dev/null +++ b/lede/target/linux/generic/backport-6.6/624-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch @@ -0,0 +1,79 @@ +From: Qingfang Deng +Date: Sat, 1 Mar 2025 21:55:16 +0800 +Subject: [PATCH] ppp: use IFF_NO_QUEUE in virtual interfaces +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +For PPPoE, PPTP, and PPPoL2TP, the start_xmit() function directly +forwards packets to the underlying network stack and never returns +anything other than 1. So these interfaces do not require a qdisc, +and the IFF_NO_QUEUE flag should be set. + +Introduces a direct_xmit flag in struct ppp_channel to indicate when +IFF_NO_QUEUE should be applied. The flag is set in ppp_connect_channel() +for relevant protocols. + +While at it, remove the usused latency member from struct ppp_channel. + +Signed-off-by: Qingfang Deng +Reviewed-by: Toke Høiland-Jørgensen +Link: https://patch.msgid.link/20250301135517.695809-1-dqfext@gmail.com +Signed-off-by: Jakub Kicinski +--- + +--- a/drivers/net/ppp/ppp_generic.c ++++ b/drivers/net/ppp/ppp_generic.c +@@ -3500,6 +3500,10 @@ ppp_connect_channel(struct channel *pch, + ret = -ENOTCONN; + goto outl; + } ++ if (pch->chan->direct_xmit) ++ ppp->dev->priv_flags |= IFF_NO_QUEUE; ++ else ++ ppp->dev->priv_flags &= ~IFF_NO_QUEUE; + spin_unlock_bh(&pch->downl); + if (pch->file.hdrlen > ppp->file.hdrlen) + ppp->file.hdrlen = pch->file.hdrlen; +--- a/drivers/net/ppp/pppoe.c ++++ b/drivers/net/ppp/pppoe.c +@@ -693,6 +693,7 @@ static int pppoe_connect(struct socket * + po->chan.mtu = dev->mtu - sizeof(struct pppoe_hdr) - 2; + po->chan.private = sk; + po->chan.ops = &pppoe_chan_ops; ++ po->chan.direct_xmit = true; + + error = ppp_register_net_channel(dev_net(dev), &po->chan); + if (error) { +--- a/drivers/net/ppp/pptp.c ++++ b/drivers/net/ppp/pptp.c +@@ -465,6 +465,7 @@ static int pptp_connect(struct socket *s + po->chan.mtu -= PPTP_HEADER_OVERHEAD; + + po->chan.hdrlen = 2 + sizeof(struct pptp_gre_header); ++ po->chan.direct_xmit = true; + error = ppp_register_channel(&po->chan); + if (error) { + pr_err("PPTP: failed to register PPP channel (%d)\n", error); +--- a/include/linux/ppp_channel.h ++++ b/include/linux/ppp_channel.h +@@ -42,8 +42,7 @@ struct ppp_channel { + int hdrlen; /* amount of headroom channel needs */ + void *ppp; /* opaque to channel */ + int speed; /* transfer rate (bytes/second) */ +- /* the following is not used at present */ +- int latency; /* overhead time in milliseconds */ ++ bool direct_xmit; /* no qdisc, xmit directly */ + }; + + #ifdef __KERNEL__ +--- a/net/l2tp/l2tp_ppp.c ++++ b/net/l2tp/l2tp_ppp.c +@@ -820,6 +820,7 @@ static int pppol2tp_connect(struct socke + po->chan.private = sk; + po->chan.ops = &pppol2tp_chan_ops; + po->chan.mtu = pppol2tp_tunnel_mtu(tunnel); ++ po->chan.direct_xmit = true; + + error = ppp_register_net_channel(sock_net(sk), &po->chan); + if (error) { diff --git a/lede/target/linux/generic/pending-6.12/650-net-pppoe-implement-GRO-support.patch b/lede/target/linux/generic/pending-6.12/650-net-pppoe-implement-GRO-support.patch new file mode 100644 index 0000000000..d7260296bd --- /dev/null +++ b/lede/target/linux/generic/pending-6.12/650-net-pppoe-implement-GRO-support.patch @@ -0,0 +1,248 @@ +From: Felix Fietkau +Date: Tue, 15 Jul 2025 12:37:45 +0200 +Subject: [PATCH] net: pppoe: implement GRO support + +Only handles packets where the pppoe header length field matches the exact +packet length. Significantly improves rx throughput. + +When running NAT traffic through a MediaTek MT7621 devices from a host +behind PPPoE to a host directly connected via ethernet, the TCP throughput +that the device is able to handle improves from ~130 Mbit/s to ~630 Mbit/s, +using fraglist GRO. + +Signed-off-by: Felix Fietkau +--- + +--- a/drivers/net/ppp/pppoe.c ++++ b/drivers/net/ppp/pppoe.c +@@ -77,6 +77,7 @@ + #include + #include + #include ++#include + + #include + +@@ -435,7 +436,7 @@ static int pppoe_rcv(struct sk_buff *skb + if (skb->len < len) + goto drop; + +- if (pskb_trim_rcsum(skb, len)) ++ if (!skb_is_gso(skb) && pskb_trim_rcsum(skb, len)) + goto drop; + + ph = pppoe_hdr(skb); +@@ -1173,6 +1174,161 @@ static struct pernet_operations pppoe_ne + .size = sizeof(struct pppoe_net), + }; + ++static u16 ++compare_pppoe_header(struct pppoe_hdr *phdr, struct pppoe_hdr *phdr2) ++{ ++ return (__force __u16)((phdr->sid ^ phdr2->sid) | ++ (phdr->tag[0].tag_type ^ phdr2->tag[0].tag_type)); ++} ++ ++static __be16 pppoe_hdr_proto(struct pppoe_hdr *phdr) ++{ ++ switch (phdr->tag[0].tag_type) { ++ case cpu_to_be16(PPP_IP): ++ return cpu_to_be16(ETH_P_IP); ++ case cpu_to_be16(PPP_IPV6): ++ return cpu_to_be16(ETH_P_IPV6); ++ default: ++ return 0; ++ } ++ ++} ++ ++static struct sk_buff *pppoe_gro_receive(struct list_head *head, ++ struct sk_buff *skb) ++{ ++ const struct packet_offload *ptype; ++ unsigned int hlen, off_pppoe; ++ struct sk_buff *pp = NULL; ++ struct pppoe_hdr *phdr; ++ struct sk_buff *p; ++ int flush = 1; ++ __be16 type; ++ ++ off_pppoe = skb_gro_offset(skb); ++ hlen = off_pppoe + sizeof(*phdr); ++ phdr = skb_gro_header(skb, hlen + 2, off_pppoe); ++ if (unlikely(!phdr)) ++ goto out; ++ ++ /* ignore packets with padding or invalid length */ ++ if (skb_gro_len(skb) != be16_to_cpu(phdr->length) + hlen) ++ goto out; ++ ++ type = pppoe_hdr_proto(phdr); ++ if (!type) ++ goto out; ++ ++ ptype = gro_find_receive_by_type(type); ++ if (!ptype) ++ goto out; ++ ++ flush = 0; ++ ++ list_for_each_entry(p, head, list) { ++ struct pppoe_hdr *phdr2; ++ ++ if (!NAPI_GRO_CB(p)->same_flow) ++ continue; ++ ++ phdr2 = (struct pppoe_hdr *)(p->data + off_pppoe); ++ if (compare_pppoe_header(phdr, phdr2)) ++ NAPI_GRO_CB(p)->same_flow = 0; ++ } ++ ++ skb_gro_pull(skb, sizeof(*phdr) + 2); ++ skb_gro_postpull_rcsum(skb, phdr, sizeof(*phdr) + 2); ++ ++ pp = indirect_call_gro_receive_inet(ptype->callbacks.gro_receive, ++ ipv6_gro_receive, inet_gro_receive, ++ head, skb); ++ ++out: ++ skb_gro_flush_final(skb, pp, flush); ++ ++ return pp; ++} ++ ++static int pppoe_gro_complete(struct sk_buff *skb, int nhoff) ++{ ++ struct pppoe_hdr *phdr = (struct pppoe_hdr *)(skb->data + nhoff); ++ __be16 type = pppoe_hdr_proto(phdr); ++ struct packet_offload *ptype; ++ int len, err; ++ ++ ptype = gro_find_complete_by_type(type); ++ if (!ptype) ++ return -ENOENT; ++ ++ err = INDIRECT_CALL_INET(ptype->callbacks.gro_complete, ++ ipv6_gro_complete, inet_gro_complete, ++ skb, nhoff + sizeof(*phdr) + 2); ++ if (err) ++ return err; ++ ++ len = skb->len - (nhoff + sizeof(*phdr)); ++ phdr->length = cpu_to_be16(len); ++ ++ return 0; ++} ++ ++static struct sk_buff *pppoe_gso_segment(struct sk_buff *skb, ++ netdev_features_t features) ++{ ++ unsigned int pppoe_hlen = sizeof(struct pppoe_hdr) + 2; ++ struct sk_buff *segs = ERR_PTR(-EINVAL); ++ u16 mac_offset = skb->mac_header; ++ struct packet_offload *ptype; ++ u16 mac_len = skb->mac_len; ++ struct pppoe_hdr *phdr; ++ __be16 orig_type, type; ++ int len, nhoff; ++ ++ skb_reset_network_header(skb); ++ nhoff = skb_network_header(skb) - skb_mac_header(skb); ++ ++ if (unlikely(!pskb_may_pull(skb, pppoe_hlen))) ++ goto out; ++ ++ phdr = (struct pppoe_hdr *)skb_network_header(skb); ++ type = pppoe_hdr_proto(phdr); ++ ptype = gro_find_complete_by_type(type); ++ if (!ptype) ++ goto out; ++ ++ orig_type = skb->protocol; ++ __skb_pull(skb, pppoe_hlen); ++ segs = ptype->callbacks.gso_segment(skb, features); ++ if (IS_ERR_OR_NULL(segs)) { ++ skb_gso_error_unwind(skb, orig_type, pppoe_hlen, mac_offset, ++ mac_len); ++ goto out; ++ } ++ ++ skb = segs; ++ do { ++ phdr = (struct pppoe_hdr *)(skb_mac_header(skb) + nhoff); ++ len = skb->len - (nhoff + sizeof(*phdr)); ++ phdr->length = cpu_to_be16(len); ++ skb->network_header = (u8 *)phdr - skb->head; ++ skb->protocol = orig_type; ++ skb_reset_mac_len(skb); ++ } while ((skb = skb->next)); ++ ++out: ++ return segs; ++} ++ ++static struct packet_offload pppoe_packet_offload __read_mostly = { ++ .type = cpu_to_be16(ETH_P_PPP_SES), ++ .priority = 20, ++ .callbacks = { ++ .gro_receive = pppoe_gro_receive, ++ .gro_complete = pppoe_gro_complete, ++ .gso_segment = pppoe_gso_segment, ++ }, ++}; ++ + static int __init pppoe_init(void) + { + int err; +@@ -1189,6 +1345,7 @@ static int __init pppoe_init(void) + if (err) + goto out_unregister_pppoe_proto; + ++ dev_add_offload(&pppoe_packet_offload); + dev_add_pack(&pppoes_ptype); + dev_add_pack(&pppoed_ptype); + register_netdevice_notifier(&pppoe_notifier); +@@ -1208,6 +1365,7 @@ static void __exit pppoe_exit(void) + unregister_netdevice_notifier(&pppoe_notifier); + dev_remove_pack(&pppoed_ptype); + dev_remove_pack(&pppoes_ptype); ++ dev_remove_offload(&pppoe_packet_offload); + unregister_pppox_proto(PX_PROTO_OE); + proto_unregister(&pppoe_sk_proto); + unregister_pernet_device(&pppoe_net_ops); +--- a/net/ipv4/af_inet.c ++++ b/net/ipv4/af_inet.c +@@ -1546,6 +1546,7 @@ out: + + return pp; + } ++EXPORT_INDIRECT_CALLABLE(inet_gro_receive); + + static struct sk_buff *ipip_gro_receive(struct list_head *head, + struct sk_buff *skb) +@@ -1631,6 +1632,7 @@ int inet_gro_complete(struct sk_buff *sk + out: + return err; + } ++EXPORT_INDIRECT_CALLABLE(inet_gro_complete); + + static int ipip_gro_complete(struct sk_buff *skb, int nhoff) + { +--- a/net/ipv6/ip6_offload.c ++++ b/net/ipv6/ip6_offload.c +@@ -304,6 +304,7 @@ out: + + return pp; + } ++EXPORT_INDIRECT_CALLABLE(ipv6_gro_receive); + + static struct sk_buff *sit_ip6ip6_gro_receive(struct list_head *head, + struct sk_buff *skb) +@@ -386,6 +387,7 @@ INDIRECT_CALLABLE_SCOPE int ipv6_gro_com + out: + return err; + } ++EXPORT_INDIRECT_CALLABLE(ipv6_gro_complete); + + static int sit_gro_complete(struct sk_buff *skb, int nhoff) + { diff --git a/lede/target/linux/generic/pending-6.6/650-net-pppoe-implement-GRO-support.patch b/lede/target/linux/generic/pending-6.6/650-net-pppoe-implement-GRO-support.patch new file mode 100644 index 0000000000..1c6849bf22 --- /dev/null +++ b/lede/target/linux/generic/pending-6.6/650-net-pppoe-implement-GRO-support.patch @@ -0,0 +1,248 @@ +From: Felix Fietkau +Date: Tue, 15 Jul 2025 12:37:45 +0200 +Subject: [PATCH] net: pppoe: implement GRO support + +Only handles packets where the pppoe header length field matches the exact +packet length. Significantly improves rx throughput. + +When running NAT traffic through a MediaTek MT7621 devices from a host +behind PPPoE to a host directly connected via ethernet, the TCP throughput +that the device is able to handle improves from ~130 Mbit/s to ~630 Mbit/s, +using fraglist GRO. + +Signed-off-by: Felix Fietkau +--- + +--- a/drivers/net/ppp/pppoe.c ++++ b/drivers/net/ppp/pppoe.c +@@ -77,6 +77,7 @@ + #include + #include + #include ++#include + + #include + +@@ -435,7 +436,7 @@ static int pppoe_rcv(struct sk_buff *skb + if (skb->len < len) + goto drop; + +- if (pskb_trim_rcsum(skb, len)) ++ if (!skb_is_gso(skb) && pskb_trim_rcsum(skb, len)) + goto drop; + + ph = pppoe_hdr(skb); +@@ -1173,6 +1174,161 @@ static struct pernet_operations pppoe_ne + .size = sizeof(struct pppoe_net), + }; + ++static u16 ++compare_pppoe_header(struct pppoe_hdr *phdr, struct pppoe_hdr *phdr2) ++{ ++ return (__force __u16)((phdr->sid ^ phdr2->sid) | ++ (phdr->tag[0].tag_type ^ phdr2->tag[0].tag_type)); ++} ++ ++static __be16 pppoe_hdr_proto(struct pppoe_hdr *phdr) ++{ ++ switch (phdr->tag[0].tag_type) { ++ case cpu_to_be16(PPP_IP): ++ return cpu_to_be16(ETH_P_IP); ++ case cpu_to_be16(PPP_IPV6): ++ return cpu_to_be16(ETH_P_IPV6); ++ default: ++ return 0; ++ } ++ ++} ++ ++static struct sk_buff *pppoe_gro_receive(struct list_head *head, ++ struct sk_buff *skb) ++{ ++ const struct packet_offload *ptype; ++ unsigned int hlen, off_pppoe; ++ struct sk_buff *pp = NULL; ++ struct pppoe_hdr *phdr; ++ struct sk_buff *p; ++ int flush = 1; ++ __be16 type; ++ ++ off_pppoe = skb_gro_offset(skb); ++ hlen = off_pppoe + sizeof(*phdr); ++ phdr = skb_gro_header(skb, hlen + 2, off_pppoe); ++ if (unlikely(!phdr)) ++ goto out; ++ ++ /* ignore packets with padding or invalid length */ ++ if (skb_gro_len(skb) != be16_to_cpu(phdr->length) + hlen) ++ goto out; ++ ++ type = pppoe_hdr_proto(phdr); ++ if (!type) ++ goto out; ++ ++ ptype = gro_find_receive_by_type(type); ++ if (!ptype) ++ goto out; ++ ++ flush = 0; ++ ++ list_for_each_entry(p, head, list) { ++ struct pppoe_hdr *phdr2; ++ ++ if (!NAPI_GRO_CB(p)->same_flow) ++ continue; ++ ++ phdr2 = (struct pppoe_hdr *)(p->data + off_pppoe); ++ if (compare_pppoe_header(phdr, phdr2)) ++ NAPI_GRO_CB(p)->same_flow = 0; ++ } ++ ++ skb_gro_pull(skb, sizeof(*phdr) + 2); ++ skb_gro_postpull_rcsum(skb, phdr, sizeof(*phdr) + 2); ++ ++ pp = indirect_call_gro_receive_inet(ptype->callbacks.gro_receive, ++ ipv6_gro_receive, inet_gro_receive, ++ head, skb); ++ ++out: ++ skb_gro_flush_final(skb, pp, flush); ++ ++ return pp; ++} ++ ++static int pppoe_gro_complete(struct sk_buff *skb, int nhoff) ++{ ++ struct pppoe_hdr *phdr = (struct pppoe_hdr *)(skb->data + nhoff); ++ __be16 type = pppoe_hdr_proto(phdr); ++ struct packet_offload *ptype; ++ int len, err; ++ ++ ptype = gro_find_complete_by_type(type); ++ if (!ptype) ++ return -ENOENT; ++ ++ err = INDIRECT_CALL_INET(ptype->callbacks.gro_complete, ++ ipv6_gro_complete, inet_gro_complete, ++ skb, nhoff + sizeof(*phdr) + 2); ++ if (err) ++ return err; ++ ++ len = skb->len - (nhoff + sizeof(*phdr)); ++ phdr->length = cpu_to_be16(len); ++ ++ return 0; ++} ++ ++static struct sk_buff *pppoe_gso_segment(struct sk_buff *skb, ++ netdev_features_t features) ++{ ++ unsigned int pppoe_hlen = sizeof(struct pppoe_hdr) + 2; ++ struct sk_buff *segs = ERR_PTR(-EINVAL); ++ u16 mac_offset = skb->mac_header; ++ struct packet_offload *ptype; ++ u16 mac_len = skb->mac_len; ++ struct pppoe_hdr *phdr; ++ __be16 orig_type, type; ++ int len, nhoff; ++ ++ skb_reset_network_header(skb); ++ nhoff = skb_network_header(skb) - skb_mac_header(skb); ++ ++ if (unlikely(!pskb_may_pull(skb, pppoe_hlen))) ++ goto out; ++ ++ phdr = (struct pppoe_hdr *)skb_network_header(skb); ++ type = pppoe_hdr_proto(phdr); ++ ptype = gro_find_complete_by_type(type); ++ if (!ptype) ++ goto out; ++ ++ orig_type = skb->protocol; ++ __skb_pull(skb, pppoe_hlen); ++ segs = ptype->callbacks.gso_segment(skb, features); ++ if (IS_ERR_OR_NULL(segs)) { ++ skb_gso_error_unwind(skb, orig_type, pppoe_hlen, mac_offset, ++ mac_len); ++ goto out; ++ } ++ ++ skb = segs; ++ do { ++ phdr = (struct pppoe_hdr *)(skb_mac_header(skb) + nhoff); ++ len = skb->len - (nhoff + sizeof(*phdr)); ++ phdr->length = cpu_to_be16(len); ++ skb->network_header = (u8 *)phdr - skb->head; ++ skb->protocol = orig_type; ++ skb_reset_mac_len(skb); ++ } while ((skb = skb->next)); ++ ++out: ++ return segs; ++} ++ ++static struct packet_offload pppoe_packet_offload __read_mostly = { ++ .type = cpu_to_be16(ETH_P_PPP_SES), ++ .priority = 20, ++ .callbacks = { ++ .gro_receive = pppoe_gro_receive, ++ .gro_complete = pppoe_gro_complete, ++ .gso_segment = pppoe_gso_segment, ++ }, ++}; ++ + static int __init pppoe_init(void) + { + int err; +@@ -1189,6 +1345,7 @@ static int __init pppoe_init(void) + if (err) + goto out_unregister_pppoe_proto; + ++ dev_add_offload(&pppoe_packet_offload); + dev_add_pack(&pppoes_ptype); + dev_add_pack(&pppoed_ptype); + register_netdevice_notifier(&pppoe_notifier); +@@ -1208,6 +1365,7 @@ static void __exit pppoe_exit(void) + unregister_netdevice_notifier(&pppoe_notifier); + dev_remove_pack(&pppoed_ptype); + dev_remove_pack(&pppoes_ptype); ++ dev_remove_offload(&pppoe_packet_offload); + unregister_pppox_proto(PX_PROTO_OE); + proto_unregister(&pppoe_sk_proto); + unregister_pernet_device(&pppoe_net_ops); +--- a/net/ipv4/af_inet.c ++++ b/net/ipv4/af_inet.c +@@ -1587,6 +1587,7 @@ out: + + return pp; + } ++EXPORT_INDIRECT_CALLABLE(inet_gro_receive); + + static struct sk_buff *ipip_gro_receive(struct list_head *head, + struct sk_buff *skb) +@@ -1672,6 +1673,7 @@ int inet_gro_complete(struct sk_buff *sk + out: + return err; + } ++EXPORT_INDIRECT_CALLABLE(inet_gro_complete); + + static int ipip_gro_complete(struct sk_buff *skb, int nhoff) + { +--- a/net/ipv6/ip6_offload.c ++++ b/net/ipv6/ip6_offload.c +@@ -319,6 +319,7 @@ out: + + return pp; + } ++EXPORT_INDIRECT_CALLABLE(ipv6_gro_receive); + + static struct sk_buff *sit_ip6ip6_gro_receive(struct list_head *head, + struct sk_buff *skb) +@@ -401,6 +402,7 @@ INDIRECT_CALLABLE_SCOPE int ipv6_gro_com + out: + return err; + } ++EXPORT_INDIRECT_CALLABLE(ipv6_gro_complete); + + static int sit_gro_complete(struct sk_buff *skb, int nhoff) + { diff --git a/mihomo/.github/workflows/build.yml b/mihomo/.github/workflows/build.yml index 64cf15bd37..48858ef500 100644 --- a/mihomo/.github/workflows/build.yml +++ b/mihomo/.github/workflows/build.yml @@ -179,6 +179,7 @@ jobs: - name: Revert Golang1.25 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1 @@ -197,6 +198,7 @@ jobs: - name: Revert Golang1.24 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.24' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1 @@ -215,6 +217,7 @@ jobs: - name: Revert Golang1.23 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.23' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1 @@ -233,6 +236,7 @@ jobs: - name: Revert Golang1.22 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.22' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1 @@ -243,6 +247,7 @@ jobs: - name: Revert Golang1.21 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.21' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1 diff --git a/mihomo/.github/workflows/test.yml b/mihomo/.github/workflows/test.yml index 28af5e76d3..ab046bd17f 100644 --- a/mihomo/.github/workflows/test.yml +++ b/mihomo/.github/workflows/test.yml @@ -60,6 +60,7 @@ jobs: - name: Revert Golang1.25 commit for Windows7/8 if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.25' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1 @@ -78,6 +79,7 @@ jobs: - name: Revert Golang1.24 commit for Windows7/8 if: ${{ runner.os == 'Windows' && matrix.go-version == '1.24' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1 @@ -96,6 +98,7 @@ jobs: - name: Revert Golang1.23 commit for Windows7/8 if: ${{ runner.os == 'Windows' && matrix.go-version == '1.23' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1 @@ -114,6 +117,7 @@ jobs: - name: Revert Golang1.22 commit for Windows7/8 if: ${{ runner.os == 'Windows' && matrix.go-version == '1.22' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1 curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1 @@ -124,6 +128,7 @@ jobs: - name: Revert Golang1.21 commit for Windows7/8 if: ${{ runner.os == 'Windows' && matrix.go-version == '1.21' }} run: | + alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' cd $(go env GOROOT) curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1 diff --git a/mihomo/listener/inbound/vless_test.go b/mihomo/listener/inbound/vless_test.go index c110228db3..f3fcd39cb0 100644 --- a/mihomo/listener/inbound/vless_test.go +++ b/mihomo/listener/inbound/vless_test.go @@ -116,6 +116,11 @@ func TestInboundVless_Encryption(t *testing.T) { Encryption: "8min-xored-mlkem768client-" + 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/transport/vless/encryption/client.go b/mihomo/transport/vless/encryption/client.go index b6e2c19dfd..9d24903886 100644 --- a/mihomo/transport/vless/encryption/client.go +++ b/mihomo/transport/vless/encryption/client.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/cipher" "crypto/rand" - "crypto/sha256" "errors" "fmt" "io" @@ -15,6 +14,7 @@ import ( "time" "github.com/metacubex/utls/mlkem" + "golang.org/x/crypto/sha3" "golang.org/x/sys/cpu" ) @@ -39,6 +39,7 @@ func init() { type ClientInstance struct { sync.RWMutex nfsEKey *mlkem.EncapsulationKey768 + hash11 [11]byte // no more capacity xorKey []byte minutes time.Duration expire time.Time @@ -56,7 +57,7 @@ type ClientConn struct { nonce []byte peerAead cipher.AEAD peerNonce []byte - peerCache []byte + input bytes.Reader // peerCache } func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) { @@ -68,8 +69,10 @@ func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Dura if err != nil { return } + hash256 := sha3.Sum256(nfsEKeyBytes) + copy(i.hash11[:], hash256[:]) if xor > 0 { - xorKey := sha256.Sum256(nfsEKeyBytes) + xorKey := sha3.Sum256(nfsEKeyBytes) i.xorKey = xorKey[:] } i.minutes = minutes @@ -104,13 +107,14 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate() paddingLen := randBetween(100, 1000) - clientHello := make([]byte, 5+1+1184+1088+5+paddingLen) - EncodeHeader(clientHello, 1, 1+1184+1088) - clientHello[5] = ClientCipher - copy(clientHello[5+1:], pfsEKeyBytes) - copy(clientHello[5+1+1184:], encapsulatedNfsKey) - EncodeHeader(clientHello[5+1+1184+1088:], 23, int(paddingLen)) - rand.Read(clientHello[5+1+1184+1088+5:]) + clientHello := make([]byte, 5+11+1+1184+1088+5+paddingLen) + EncodeHeader(clientHello, 1, 11+1+1184+1088) + copy(clientHello[5:], i.hash11[:]) + clientHello[5+11] = ClientCipher + copy(clientHello[5+11+1:], pfsEKeyBytes) + copy(clientHello[5+11+1+1184:], encapsulatedNfsKey) + EncodeHeader(clientHello[5+11+1+1184+1088:], 23, int(paddingLen)) + rand.Read(clientHello[5+11+1+1184+1088+5:]) if _, err := c.Conn.Write(clientHello); err != nil { return nil, err @@ -123,17 +127,17 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { } if t != 1 { - return nil, fmt.Errorf("unexpected type %v, expect random hello", t) + return nil, fmt.Errorf("unexpected type %v, expect server hello", t) } - peerRandomHello := make([]byte, 1088+21) - if l != len(peerRandomHello) { - return nil, fmt.Errorf("unexpected length %v for random hello", l) + peerServerHello := make([]byte, 1088+21) + if l != len(peerServerHello) { + return nil, fmt.Errorf("unexpected length %v for server hello", l) } - if _, err := io.ReadFull(c.Conn, peerRandomHello); err != nil { + if _, err := io.ReadFull(c.Conn, peerServerHello); err != nil { return nil, err } - encapsulatedPfsKey := peerRandomHello[:1088] - c.ticket = peerRandomHello[1088:] + encapsulatedPfsKey := peerServerHello[:1088] + c.ticket = append(i.hash11[:], peerServerHello[1088:]...) pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey) if err != nil { @@ -141,8 +145,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { } c.baseKey = append(pfsKey, nfsKey...) - nonce := [12]byte{ClientCipher} - VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, nonce[:], c.ticket, pfsEKeyBytes) + VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes) if !bytes.Equal(VLESS, []byte("VLESS")) { return nil, errors.New("invalid server") } @@ -170,16 +173,16 @@ func (c *ClientConn) Write(b []byte) (int, error) { } n += len(b) if c.aead == nil { + data = make([]byte, 5+32+32+5+len(b)+16) + EncodeHeader(data, 0, 32+32) + copy(data[5:], c.ticket) c.random = make([]byte, 32) rand.Read(c.random) + copy(data[5+32:], c.random) + EncodeHeader(data[5+32+32:], 23, len(b)+16) c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket) c.nonce = make([]byte, 12) - data = make([]byte, 5+21+32+5+len(b)+16) - EncodeHeader(data, 0, 21+32) - copy(data[5:], c.ticket) - copy(data[5+21:], c.random) - EncodeHeader(data[5+21+32:], 23, len(b)+16) - c.aead.Seal(data[:5+21+32+5], c.nonce, b, data[5+21+32:5+21+32+5]) + c.aead.Seal(data[:5+32+32+5], c.nonce, b, data[5+32+32:5+32+32+5]) } else { data = make([]byte, 5+len(b)+16) EncodeHeader(data, 23, len(b)+16) @@ -216,23 +219,21 @@ func (c *ClientConn) Read(b []byte) (int, error) { if t != 0 { return 0, fmt.Errorf("unexpected type %v, expect server random", t) } - peerRandom := make([]byte, 32) - if l != len(peerRandom) { + peerRandomHello := make([]byte, 32) + if l != len(peerRandomHello) { return 0, fmt.Errorf("unexpected length %v for server random", l) } - if _, err := io.ReadFull(c.Conn, peerRandom); err != nil { + if _, err := io.ReadFull(c.Conn, peerRandomHello); err != nil { return 0, err } if c.random == nil { return 0, errors.New("empty c.random") } - c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandom, c.random) + c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandomHello, c.random) c.peerNonce = make([]byte, 12) } - 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) } h, t, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000 if err != nil { @@ -262,7 +263,7 @@ func (c *ClientConn) 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 diff --git a/mihomo/transport/vless/encryption/common.go b/mihomo/transport/vless/encryption/common.go index 151a48e40a..a67a616927 100644 --- a/mihomo/transport/vless/encryption/common.go +++ b/mihomo/transport/vless/encryption/common.go @@ -5,7 +5,6 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" - "crypto/sha256" "fmt" "io" "math/big" @@ -13,6 +12,7 @@ import ( "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/sha3" ) var MaxNonce = bytes.Repeat([]byte{255}, 12) @@ -75,7 +75,7 @@ func ReadAndDiscardPaddings(conn net.Conn) (h []byte, t byte, l int, err error) func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) { key := make([]byte, 32) - hkdf.New(sha256.New, secret, salt, info).Read(key) + hkdf.New(sha3.New256, secret, salt, info).Read(key) if c&1 == 1 { block, _ := aes.NewCipher(key) aead, _ = cipher.NewGCM(block) diff --git a/mihomo/transport/vless/encryption/doc.go b/mihomo/transport/vless/encryption/doc.go index 900866ec05..9ba7eb113d 100644 --- a/mihomo/transport/vless/encryption/doc.go +++ b/mihomo/transport/vless/encryption/doc.go @@ -11,4 +11,6 @@ // https://github.com/XTLS/Xray-core/commit/09cc92c61d9067e0d65c1cae9124664ecfc78f43 // https://github.com/XTLS/Xray-core/commit/2807ee432a1fbeb301815647189eacd650b12a8b // https://github.com/XTLS/Xray-core/commit/bfe4820f2f086daf639b1957eb23dc13c843cad1 +// https://github.com/XTLS/Xray-core/commit/d1fb48521271251a8c74bd64fcc2fc8700717a3b +// https://github.com/XTLS/Xray-core/commit/49580705f6029648399304b816a2737f991582a8 package encryption diff --git a/mihomo/transport/vless/encryption/server.go b/mihomo/transport/vless/encryption/server.go index f67c9720ce..9c5c102e8c 100644 --- a/mihomo/transport/vless/encryption/server.go +++ b/mihomo/transport/vless/encryption/server.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/cipher" "crypto/rand" - "crypto/sha256" "errors" "fmt" "io" @@ -13,6 +12,7 @@ import ( "time" "github.com/metacubex/utls/mlkem" + "golang.org/x/crypto/sha3" ) type ServerSession struct { @@ -25,9 +25,10 @@ type ServerSession struct { type ServerInstance struct { sync.RWMutex nfsDKey *mlkem.DecapsulationKey768 + hash11 [11]byte // no more capacity xorKey []byte minutes time.Duration - sessions map[[21]byte]*ServerSession + sessions map[[32]byte]*ServerSession closed bool } @@ -39,7 +40,7 @@ type ServerConn struct { peerRandom []byte peerAead cipher.AEAD peerNonce []byte - peerCache []byte + input bytes.Reader // peerCache aead cipher.AEAD nonce []byte } @@ -53,13 +54,15 @@ func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Durat if err != nil { return } + hash256 := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) + copy(i.hash11[:], hash256[:]) if xor > 0 { - xorKey := sha256.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) + xorKey := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) i.xorKey = xorKey[:] } if minutes > 0 { i.minutes = minutes - i.sessions = make(map[[21]byte]*ServerSession) + i.sessions = make(map[[32]byte]*ServerSession) go func() { for { time.Sleep(time.Minute) @@ -106,15 +109,18 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { if i.minutes == 0 { return nil, errors.New("0-RTT is not allowed") } - peerTicketHello := make([]byte, 21+32) + peerTicketHello := make([]byte, 32+32) if l != len(peerTicketHello) { return nil, fmt.Errorf("unexpected length %v for ticket hello", l) } if _, err := io.ReadFull(c.Conn, peerTicketHello); err != nil { return nil, err } + if !bytes.Equal(peerTicketHello[:11], i.hash11[:]) { + return nil, fmt.Errorf("unexpected hash11: %v", peerTicketHello[:11]) + } i.RLock() - s := i.sessions[[21]byte(peerTicketHello)] + s := i.sessions[[32]byte(peerTicketHello)] i.RUnlock() if s == nil { noises := make([]byte, randBetween(100, 1000)) @@ -126,26 +132,29 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { c.Conn.Write(noises) // make client do new handshake return nil, errors.New("expired ticket") } - if _, replay := s.randoms.LoadOrStore([32]byte(peerTicketHello[21:]), true); replay { + if _, replay := s.randoms.LoadOrStore([32]byte(peerTicketHello[32:]), true); replay { return nil, errors.New("replay detected") } c.cipher = s.cipher c.baseKey = s.baseKey - c.ticket = peerTicketHello[:21] - c.peerRandom = peerTicketHello[21:] + c.ticket = peerTicketHello[:32] + c.peerRandom = peerTicketHello[32:] return c, nil } - peerClientHello := make([]byte, 1+1184+1088) + peerClientHello := make([]byte, 11+1+1184+1088) if l != len(peerClientHello) { return nil, fmt.Errorf("unexpected length %v for client hello", l) } if _, err := io.ReadFull(c.Conn, peerClientHello); err != nil { return nil, err } - c.cipher = peerClientHello[0] - pfsEKeyBytes := peerClientHello[1:1185] - encapsulatedNfsKey := peerClientHello[1185:2273] + if !bytes.Equal(peerClientHello[:11], i.hash11[:]) { + return nil, fmt.Errorf("unexpected hash11: %v", peerClientHello[:11]) + } + c.cipher = peerClientHello[11] + pfsEKeyBytes := peerClientHello[11+1 : 11+1+1184] + encapsulatedNfsKey := peerClientHello[11+1+1184:] pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes) if err != nil { @@ -158,15 +167,14 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate() c.baseKey = append(pfsKey, nfsKey...) - nonce := [12]byte{c.cipher} - c.ticket = NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, nonce[:], []byte("VLESS"), pfsEKeyBytes) + c.ticket = append(i.hash11[:], NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, peerClientHello[:12], []byte("VLESS"), pfsEKeyBytes)...) paddingLen := randBetween(100, 1000) serverHello := make([]byte, 5+1088+21+5+paddingLen) EncodeHeader(serverHello, 1, 1088+21) copy(serverHello[5:], encapsulatedPfsKey) - copy(serverHello[5+1088:], c.ticket) + copy(serverHello[5+1088:], c.ticket[11:]) EncodeHeader(serverHello[5+1088+21:], 23, int(paddingLen)) rand.Read(serverHello[5+1088+21+5:]) @@ -177,7 +185,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { if i.minutes > 0 { i.Lock() - i.sessions[[21]byte(c.ticket)] = &ServerSession{ + i.sessions[[32]byte(c.ticket)] = &ServerSession{ expire: time.Now().Add(i.minutes), cipher: c.cipher, baseKey: c.baseKey, @@ -201,25 +209,23 @@ func (c *ServerConn) Read(b []byte) (int, error) { if t != 0 { return 0, fmt.Errorf("unexpected type %v, expect ticket hello", t) } - peerTicketHello := make([]byte, 21+32) + peerTicketHello := make([]byte, 32+32) if l != len(peerTicketHello) { return 0, fmt.Errorf("unexpected length %v for ticket hello", l) } if _, err := io.ReadFull(c.Conn, peerTicketHello); err != nil { return 0, err } - if !bytes.Equal(peerTicketHello[:21], c.ticket) { + if !bytes.Equal(peerTicketHello[:32], c.ticket) { return 0, errors.New("naughty boy") } - c.peerRandom = peerTicketHello[21:] + c.peerRandom = peerTicketHello[32:] } c.peerAead = NewAead(c.cipher, c.baseKey, c.peerRandom, c.ticket) c.peerNonce = make([]byte, 12) } - 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) } h, t, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000 if err != nil { @@ -249,7 +255,7 @@ func (c *ServerConn) 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 @@ -273,9 +279,9 @@ func (c *ServerConn) Write(b []byte) (int, error) { data = make([]byte, 5+32+5+len(b)+16) EncodeHeader(data, 0, 32) rand.Read(data[5 : 5+32]) + EncodeHeader(data[5+32:], 23, len(b)+16) c.aead = NewAead(c.cipher, c.baseKey, data[5:5+32], c.peerRandom) c.nonce = make([]byte, 12) - EncodeHeader(data[5+32:], 23, len(b)+16) c.aead.Seal(data[:5+32+5], c.nonce, b, data[5+32:5+32+5]) } else { data = make([]byte, 5+len(b)+16) diff --git a/mihomo/transport/vless/encryption/xor.go b/mihomo/transport/vless/encryption/xor.go index 64828eaa26..69ff15780f 100644 --- a/mihomo/transport/vless/encryption/xor.go +++ b/mihomo/transport/vless/encryption/xor.go @@ -15,6 +15,14 @@ type XorConn struct { peerCtr cipher.Stream isHeader bool skipNext bool + + out_after0 bool + out_header []byte + out_skip int + + in_after0 bool + in_header []byte + in_skip int } func NewXorConn(conn net.Conn, key []byte) *XorConn { @@ -26,29 +34,56 @@ func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records if len(b) == 0 { return 0, nil } - var iv []byte - if c.ctr == nil { - block, _ := aes.NewCipher(c.key) - iv = make([]byte, 16) - rand.Read(iv) - c.ctr = cipher.NewCTR(block, iv) + if !c.out_after0 { + var iv []byte + if c.ctr == nil { + block, _ := aes.NewCipher(c.key) + iv = make([]byte, 16) + rand.Read(iv) + c.ctr = cipher.NewCTR(block, iv) + } + t, l, _ := DecodeHeader(b) + if t == 23 { // single 23 + l = 5 + } else { // 1/0 + 23, or noises only + l += 10 + if t == 0 { + c.out_after0 = true + } + } + c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard b + if iv != nil { + b = append(iv, b...) + } + if _, err := c.Conn.Write(b); err != nil { + return 0, err + } + if iv != nil { + b = b[16:] // for len(b) + } + return len(b), nil } - t, l, _ := DecodeHeader(b) - if t == 23 { // single 23 - l = 5 - } else { // 1/0 + 23, or noises only - l += 10 - } - c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard b - if iv != nil { - b = append(iv, b...) + for p := b; ; { // for XTLS + if len(p) <= c.out_skip { + c.out_skip -= len(p) + break + } + p = p[c.out_skip:] + c.out_skip = 0 + need := 5 - len(c.out_header) + if len(p) < need { + c.out_header = append(c.out_header, p...) + c.ctr.XORKeyStream(p, p) + break + } + _, c.out_skip, _ = DecodeHeader(append(c.out_header, p[:need]...)) + c.out_header = make([]byte, 0, 5) // DO NOT CHANGE + c.ctr.XORKeyStream(p[:need], p[:need]) + p = p[need:] } if _, err := c.Conn.Write(b); err != nil { return 0, err } - if iv != nil { - b = b[16:] // for len(b) - } return len(b), nil } @@ -56,31 +91,56 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes... if len(b) == 0 { return 0, nil } - if c.peerCtr == nil { - peerIv := make([]byte, 16) - if _, err := io.ReadFull(c.Conn, peerIv); err != nil { + if !c.in_after0 || !c.isHeader { + if c.peerCtr == nil { + peerIv := make([]byte, 16) + if _, err := io.ReadFull(c.Conn, peerIv); err != nil { + return 0, err + } + block, _ := aes.NewCipher(c.key) + c.peerCtr = cipher.NewCTR(block, peerIv) + c.isHeader = true + } + if _, err := io.ReadFull(c.Conn, b); err != nil { return 0, err } - block, _ := aes.NewCipher(c.key) - c.peerCtr = cipher.NewCTR(block, peerIv) - c.isHeader = true - } - if _, err := io.ReadFull(c.Conn, b); err != nil { - return 0, err - } - if c.skipNext { - c.skipNext = false + if c.skipNext { + c.skipNext = false + return len(b), nil + } + c.peerCtr.XORKeyStream(b, b) + if c.isHeader { // always 5-bytes + if t, _, _ := DecodeHeader(b); t == 23 { + c.skipNext = true + } else { + c.isHeader = false + if t == 0 { + c.in_after0 = true + } + } + } else { + c.isHeader = true + } return len(b), nil } - c.peerCtr.XORKeyStream(b, b) - if c.isHeader { // always 5-bytes - if t, _, _ := DecodeHeader(b); t == 23 { - c.skipNext = true - } else { - c.isHeader = false + n, err := c.Conn.Read(b) + for p := b[:n]; ; { // for XTLS + if len(p) <= c.in_skip { + c.in_skip -= len(p) + break } - } else { - c.isHeader = true + p = p[c.in_skip:] + c.in_skip = 0 + need := 5 - len(c.in_header) + if len(p) < need { + c.peerCtr.XORKeyStream(p, p) + c.in_header = append(c.in_header, p...) + break + } + c.peerCtr.XORKeyStream(p[:need], p[:need]) + _, c.in_skip, _ = DecodeHeader(append(c.in_header, p[:need]...)) + c.in_header = make([]byte, 0, 5) // DO NOT CHANGE + p = p[need:] } - return len(b), nil + return n, err } diff --git a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm index 9d53f97553..373372c636 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm +++ b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm @@ -963,7 +963,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_allowInsecure', true); - if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { opt.set(dom_prefix + 'tls_allowInsecure', false); } if (queryParam.fp && queryParam.fp.trim() != "") { @@ -1206,9 +1206,10 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin tls_serverName = tls_serverName || ""; opt.set(dom_prefix + 'tls_serverName', tls_serverName); opt.set(dom_prefix + 'flow', (queryParam.flow || '').replace('-udp443', '')); + opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); } opt.set(dom_prefix + 'tls_allowInsecure', true); - if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { opt.set(dom_prefix + 'tls_allowInsecure', false); } @@ -1346,7 +1347,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_allowInsecure', true); - if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { opt.set(dom_prefix + 'tls_allowInsecure', false); } if (queryParam.fp && queryParam.fp.trim() != "") { @@ -1542,7 +1543,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin opt.set(dom_prefix + 'tuic_alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_allowInsecure', true); - if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { opt.set(dom_prefix + 'tls_allowInsecure', false); } if (hash) { @@ -1585,7 +1586,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_allowInsecure', true); - if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { opt.set(dom_prefix + 'tls_allowInsecure', false); } if (queryParam.fp && queryParam.fp.trim() != "") { diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/subscribe.lua b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/subscribe.lua index 0e68bc13ac..a30608c2e3 100755 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/subscribe.lua +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/subscribe.lua @@ -1036,8 +1036,8 @@ local function processData(szType, content, add_mode, add_from) end result.encryption = params.encryption or "none" - result.flow = params.flow and params.flow:gsub("-udp443", "") or nil + result.alpn = params.alpn if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp" or result.transport == "splithttp") then log("跳过节点:" .. result.remarks ..",因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式,需更换Xray。") diff --git a/shadowsocks-rust/.github/workflows/build-and-test.yml b/shadowsocks-rust/.github/workflows/build-and-test.yml index 52f1f2c4a1..9568de947a 100644 --- a/shadowsocks-rust/.github/workflows/build-and-test.yml +++ b/shadowsocks-rust/.github/workflows/build-and-test.yml @@ -20,7 +20,7 @@ jobs: - if: ${{ matrix.platform == 'ubuntu-latest' }} name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: Swatinem/rust-cache@v2 - if: ${{ runner.os == 'Windows' }} uses: ilammy/setup-nasm@v1 diff --git a/shadowsocks-rust/.github/workflows/build-docker-image.yml b/shadowsocks-rust/.github/workflows/build-docker-image.yml index 8a02be915f..60ea88584f 100644 --- a/shadowsocks-rust/.github/workflows/build-docker-image.yml +++ b/shadowsocks-rust/.github/workflows/build-docker-image.yml @@ -15,7 +15,7 @@ jobs: - sslocal steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry diff --git a/shadowsocks-rust/.github/workflows/build-msrv.yml b/shadowsocks-rust/.github/workflows/build-msrv.yml index 13eb1d3048..928d694252 100644 --- a/shadowsocks-rust/.github/workflows/build-msrv.yml +++ b/shadowsocks-rust/.github/workflows/build-msrv.yml @@ -23,7 +23,7 @@ jobs: - if: ${{ matrix.platform == 'ubuntu-latest' }} name: Install LLVM and Clang run: sudo apt update && sudo apt install -y clang - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: Swatinem/rust-cache@v2 - if: ${{ runner.os == 'Windows' }} uses: ilammy/setup-nasm@v1 @@ -56,7 +56,7 @@ jobs: - if: ${{ matrix.platform == 'ubuntu-latest' }} name: Install LLVM and Clang run: sudo apt update && sudo apt install -y clang - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: Swatinem/rust-cache@v2 - if: ${{ runner.os == 'Windows' }} uses: ilammy/setup-nasm@v1 @@ -85,7 +85,7 @@ jobs: - if: ${{ matrix.platform == 'ubuntu-latest' }} name: Install LLVM and Clang run: sudo apt update && sudo apt install -y clang - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: Swatinem/rust-cache@v2 - if: ${{ runner.os == 'Windows' }} uses: ilammy/setup-nasm@v1 diff --git a/shadowsocks-rust/.github/workflows/build-nightly-release.yml b/shadowsocks-rust/.github/workflows/build-nightly-release.yml index c6e2cbdb71..867c933b60 100644 --- a/shadowsocks-rust/.github/workflows/build-nightly-release.yml +++ b/shadowsocks-rust/.github/workflows/build-nightly-release.yml @@ -60,7 +60,7 @@ jobs: - name: Install LLVM and Clang run: sudo apt update && sudo apt install -y clang - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Rust run: | @@ -115,7 +115,7 @@ jobs: - x86_64-apple-darwin - aarch64-apple-darwin steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install GNU tar if: runner.os == 'macOS' @@ -149,7 +149,7 @@ jobs: RUSTFLAGS: "-C target-feature=+crt-static" RUST_BACKTRACE: full steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ilammy/setup-nasm@v1 - name: Install Rust diff --git a/shadowsocks-rust/.github/workflows/build-release.yml b/shadowsocks-rust/.github/workflows/build-release.yml index 0113001598..a7d8d95128 100644 --- a/shadowsocks-rust/.github/workflows/build-release.yml +++ b/shadowsocks-rust/.github/workflows/build-release.yml @@ -94,7 +94,7 @@ jobs: - name: Install LLVM and Clang run: sudo apt update && sudo apt install -y clang - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Rust run: | @@ -153,7 +153,7 @@ jobs: - x86_64-apple-darwin - aarch64-apple-darwin steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install GNU tar if: runner.os == 'macOS' @@ -190,7 +190,7 @@ jobs: RUSTFLAGS: "-C target-feature=+crt-static" RUST_BACKTRACE: full steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ilammy/setup-nasm@v1 - name: Install Rust diff --git a/shadowsocks-rust/.github/workflows/clippy-check.yml b/shadowsocks-rust/.github/workflows/clippy-check.yml index 4ae180ecc9..70be3b1507 100644 --- a/shadowsocks-rust/.github/workflows/clippy-check.yml +++ b/shadowsocks-rust/.github/workflows/clippy-check.yml @@ -17,7 +17,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: Swatinem/rust-cache@v2 - if: ${{ runner.os == 'Windows' }} uses: ilammy/setup-nasm@v1 diff --git a/shadowsocks-rust/.github/workflows/deny-check.yml b/shadowsocks-rust/.github/workflows/deny-check.yml index ece405af39..630e79c6e5 100644 --- a/shadowsocks-rust/.github/workflows/deny-check.yml +++ b/shadowsocks-rust/.github/workflows/deny-check.yml @@ -13,7 +13,7 @@ jobs: continue-on-error: ${{ matrix.checks == 'advisories' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check ${{ matrix.checks }} diff --git a/sing-box/.github/workflows/build.yml b/sing-box/.github/workflows/build.yml index 25114dff51..1964301456 100644 --- a/sing-box/.github/workflows/build.yml +++ b/sing-box/.github/workflows/build.yml @@ -149,7 +149,7 @@ jobs: TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale' echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build - if: matrix.os != 'android' + if: matrix.os != 'darwin' && matrix.os != 'android' run: | set -xeuo pipefail mkdir -p dist @@ -165,6 +165,23 @@ jobs: GOMIPS: ${{ matrix.gomips }} GOMIPS64: ${{ matrix.gomips }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build darwin + if: matrix.os == 'darwin' + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "0" + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + GO386: ${{ matrix.go386 }} + GOARM: ${{ matrix.goarm }} + GOMIPS: ${{ matrix.gomips }} + GOMIPS64: ${{ matrix.gomips }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build Android if: matrix.os == 'android' run: | diff --git a/sing-box/Makefile b/sing-box/Makefile index 43f93c8cef..e8d0a8318f 100644 --- a/sing-box/Makefile +++ b/sing-box/Makefile @@ -6,7 +6,7 @@ GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTARCH = $(shell go env GOHOSTARCH) VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid=" +PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0" MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)" MAIN = ./cmd/sing-box PREFIX ?= $(shell go env GOPATH) diff --git a/sing-box/adapter/network.go b/sing-box/adapter/network.go index 506708314d..1b26bed680 100644 --- a/sing-box/adapter/network.go +++ b/sing-box/adapter/network.go @@ -20,6 +20,7 @@ type NetworkManager interface { DefaultOptions() NetworkOptions RegisterAutoRedirectOutputMark(mark uint32) error AutoRedirectOutputMark() uint32 + AutoRedirectOutputMarkFunc() control.Func NetworkMonitor() tun.NetworkUpdateMonitor InterfaceMonitor() tun.DefaultInterfaceMonitor PackageManager() tun.PackageManager diff --git a/sing-box/adapter/outbound/manager.go b/sing-box/adapter/outbound/manager.go index b58f527707..0fdeb390c2 100644 --- a/sing-box/adapter/outbound/manager.go +++ b/sing-box/adapter/outbound/manager.go @@ -56,6 +56,14 @@ func (m *Manager) Start(stage adapter.StartStage) error { m.started = true m.stage = stage if stage == adapter.StartStateStart { + if m.defaultTag != "" && m.defaultOutbound == nil { + defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag) + if !loaded { + m.access.Unlock() + return E.New("default outbound not found: ", m.defaultTag) + } + m.defaultOutbound = defaultEndpoint + } if m.defaultOutbound == nil { directOutbound, err := m.defaultOutboundFallback() if err != nil { @@ -66,14 +74,6 @@ func (m *Manager) Start(stage adapter.StartStage) error { m.outboundByTag[directOutbound.Tag()] = directOutbound m.defaultOutbound = directOutbound } - if m.defaultTag != "" && m.defaultOutbound == nil { - defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag) - if !loaded { - m.access.Unlock() - return E.New("default outbound not found: ", m.defaultTag) - } - m.defaultOutbound = defaultEndpoint - } outbounds := m.outbounds m.access.Unlock() return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...)) diff --git a/sing-box/box.go b/sing-box/box.go index 8a38f6aead..d43a3c9579 100644 --- a/sing-box/box.go +++ b/sing-box/box.go @@ -323,13 +323,14 @@ func New(options Options) (*Box, error) { option.DirectOutboundOptions{}, ) }) - dnsTransportManager.Initialize(common.Must1( - local.NewTransport( + dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) { + return local.NewTransport( ctx, logFactory.NewLogger("dns/local"), "local", option.LocalDNSServerOptions{}, - ))) + ) + }) if platformInterface != nil { err = platformInterface.Initialize(networkManager) if err != nil { diff --git a/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/Application.kt b/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/Application.kt index 51cfa5dd5b..8c3144bb5f 100644 --- a/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/Application.kt +++ b/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/Application.kt @@ -12,11 +12,14 @@ import android.os.PowerManager import androidx.core.content.getSystemService import go.Seq import io.nekohasekai.libbox.Libbox +import io.nekohasekai.libbox.SetupOptions import io.nekohasekai.sfa.bg.AppChangeReceiver import io.nekohasekai.sfa.bg.UpdateProfileWork +import io.nekohasekai.sfa.constant.Bugs import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import java.io.File import java.util.Locale import io.nekohasekai.sfa.Application as BoxApplication @@ -35,6 +38,7 @@ class Application : Application() { @Suppress("OPT_IN_USAGE") GlobalScope.launch(Dispatchers.IO) { + initialize() UpdateProfileWork.reconfigureUpdater() } @@ -42,6 +46,23 @@ class Application : Application() { addAction(Intent.ACTION_PACKAGE_ADDED) addDataScheme("package") }) + + } + + private fun initialize() { + val baseDir = filesDir + baseDir.mkdirs() + val workingDir = getExternalFilesDir(null) ?: return + workingDir.mkdirs() + val tempDir = cacheDir + tempDir.mkdirs() + Libbox.setup(SetupOptions().also { + it.basePath = baseDir.path + it.workingPath = workingDir.path + it.tempPath = tempDir.path + it.fixAndroidStack = Bugs.fixAndroidStack + }) + Libbox.redirectStderr(File(workingDir, "stderr.log").path) } companion object { diff --git a/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt b/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt index fadfad603d..1206ea5230 100644 --- a/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt +++ b/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt @@ -24,13 +24,11 @@ import io.nekohasekai.libbox.CommandServerHandler import io.nekohasekai.libbox.Libbox import io.nekohasekai.libbox.Notification import io.nekohasekai.libbox.PlatformInterface -import io.nekohasekai.libbox.SetupOptions import io.nekohasekai.libbox.SystemProxyStatus import io.nekohasekai.sfa.Application import io.nekohasekai.sfa.R import io.nekohasekai.sfa.constant.Action import io.nekohasekai.sfa.constant.Alert -import io.nekohasekai.sfa.constant.Bugs import io.nekohasekai.sfa.constant.Status import io.nekohasekai.sfa.database.ProfileManager import io.nekohasekai.sfa.database.Settings @@ -50,26 +48,6 @@ class BoxService( companion object { - private var initializeOnce = false - private fun initialize() { - if (initializeOnce) return - val baseDir = Application.application.filesDir - baseDir.mkdirs() - val workingDir = Application.application.getExternalFilesDir(null) ?: return - workingDir.mkdirs() - val tempDir = Application.application.cacheDir - tempDir.mkdirs() - Libbox.setup(SetupOptions().also { - it.basePath = baseDir.path - it.workingPath = workingDir.path - it.tempPath = tempDir.path - it.fixAndroidStack = Bugs.fixAndroidStack - }) - Libbox.redirectStderr(File(workingDir, "stderr.log").path) - initializeOnce = true - return - } - fun start() { val intent = runBlocking { withContext(Dispatchers.IO) { @@ -310,7 +288,6 @@ class BoxService( GlobalScope.launch(Dispatchers.IO) { Settings.startedByUser = true - initialize() try { startCommandServer() } catch (e: Exception) { diff --git a/sing-box/clients/android/gradle.properties b/sing-box/clients/android/gradle.properties index b822cc753e..bf35f247d9 100644 --- a/sing-box/clients/android/gradle.properties +++ b/sing-box/clients/android/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx8192m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/sing-box/clients/android/version.properties b/sing-box/clients/android/version.properties index dc64133669..646d608330 100644 --- a/sing-box/clients/android/version.properties +++ b/sing-box/clients/android/version.properties @@ -1,3 +1,3 @@ -VERSION_CODE=550 -VERSION_NAME=1.12.1 -GO_VERSION=go1.24.6 +VERSION_CODE=552 +VERSION_NAME=1.12.2 +GO_VERSION=go1.25.0 diff --git a/sing-box/cmd/internal/build_libbox/main.go b/sing-box/cmd/internal/build_libbox/main.go index c7bdf6cf99..71df1ae402 100644 --- a/sing-box/cmd/internal/build_libbox/main.go +++ b/sing-box/cmd/internal/build_libbox/main.go @@ -59,8 +59,8 @@ func init() { if err != nil { currentTag = "unknown" } - sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=") - debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag) + sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0") + debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+"-s -w -buildid= -checklinkname=0") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack") darwinTags = append(darwinTags, "with_dhcp") @@ -106,19 +106,17 @@ func buildAndroid() { "-libname=box", } - if !debugEnabled { - sharedFlags[3] = sharedFlags[3] + " -checklinkname=0" - args = append(args, sharedFlags...) - } else { - debugFlags[1] = debugFlags[1] + " -checklinkname=0" - args = append(args, debugFlags...) - } - tags := append(sharedTags, memcTags...) if debugEnabled { tags = append(tags, debugTags...) } + if !debugEnabled { + args = append(args, sharedFlags...) + } else { + args = append(args, debugFlags...) + } + args = append(args, "-tags", strings.Join(tags, ",")) args = append(args, "./experimental/libbox") diff --git a/sing-box/common/dialer/default.go b/sing-box/common/dialer/default.go index 5337934afa..4afa00911e 100644 --- a/sing-box/common/dialer/default.go +++ b/sing-box/common/dialer/default.go @@ -121,11 +121,16 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial listener.Control = control.Append(listener.Control, bindFunc) } } + if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 { + dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) + listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) + } } - if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 { - dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) - listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) - } + } + if networkManager != nil { + markFunc := networkManager.AutoRedirectOutputMarkFunc() + dialer.Control = control.Append(dialer.Control, markFunc) + listener.Control = control.Append(listener.Control, markFunc) } if options.ReuseAddr { listener.Control = control.Append(listener.Control, control.ReuseAddr()) diff --git a/sing-box/common/srs/binary.go b/sing-box/common/srs/binary.go index 96b578f5dd..0c93c28424 100644 --- a/sing-box/common/srs/binary.go +++ b/sing-box/common/srs/binary.go @@ -235,7 +235,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea case ruleItemNetworkIsConstrained: rule.NetworkIsConstrained = true case ruleItemNetworkInterfaceAddress: - rule.NetworkInterfaceAddress = new(badjson.TypedMap[option.InterfaceType, badoption.Listable[badoption.Prefixable]]) + rule.NetworkInterfaceAddress = new(badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]]) var size uint64 size, err = binary.ReadUvarint(reader) if err != nil { @@ -247,7 +247,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea if err != nil { return } - var value []badoption.Prefixable + var value []*badoption.Prefixable var prefixCount uint64 prefixCount, err = binary.ReadUvarint(reader) if err != nil { @@ -259,12 +259,12 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea if err != nil { return } - value = append(value, badoption.Prefixable(prefix)) + value = append(value, common.Ptr(badoption.Prefixable(prefix))) } rule.NetworkInterfaceAddress.Put(option.InterfaceType(key), value) } case ruleItemDefaultInterfaceAddress: - var value []badoption.Prefixable + var value []*badoption.Prefixable var prefixCount uint64 prefixCount, err = binary.ReadUvarint(reader) if err != nil { @@ -276,7 +276,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea if err != nil { return } - value = append(value, badoption.Prefixable(prefix)) + value = append(value, common.Ptr(badoption.Prefixable(prefix))) } rule.DefaultInterfaceAddress = value case ruleItemFinal: @@ -437,6 +437,10 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen if err != nil { return err } + _, err = varbin.WriteUvarint(writer, uint64(len(entry.Value))) + if err != nil { + return err + } for _, rawPrefix := range entry.Value { err = writePrefix(writer, rawPrefix.Build(netip.Prefix{})) if err != nil { diff --git a/sing-box/dns/transport/local/local.go b/sing-box/dns/transport/local/local.go index a7d370dfb6..74c9c7021e 100644 --- a/sing-box/dns/transport/local/local.go +++ b/sing-box/dns/transport/local/local.go @@ -20,6 +20,10 @@ import ( mDNS "github.com/miekg/dns" ) +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport) +} + var _ adapter.DNSTransport = (*Transport)(nil) type Transport struct { @@ -28,10 +32,14 @@ type Transport struct { logger logger.ContextLogger hosts *hosts.File dialer N.Dialer + preferGo bool resolved ResolvedResolver } func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) { + if C.IsDarwin && !options.PreferGo { + return NewResolvTransport(ctx, logger, tag) + } transportDialer, err := dns.NewLocalDialer(ctx, options) if err != nil { return nil, err @@ -42,19 +50,22 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt logger: logger, hosts: hosts.NewFile(hosts.DefaultPath), dialer: transportDialer, + preferGo: options.PreferGo, }, nil } func (t *Transport) Start(stage adapter.StartStage) error { switch stage { case adapter.StartStateInitialize: - resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger) - if err == nil { - err = resolvedResolver.Start() + if !t.preferGo { + resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger) if err == nil { - t.resolved = resolvedResolver - } else { - t.logger.Warn(E.Cause(err, "initialize resolved resolver")) + err = resolvedResolver.Start() + if err == nil { + t.resolved = resolvedResolver + } else { + t.logger.Warn(E.Cause(err, "initialize resolved resolver")) + } } } } diff --git a/sing-box/dns/transport/local/local_fallback.go b/sing-box/dns/transport/local/local_fallback.go deleted file mode 100644 index e584ddce53..0000000000 --- a/sing-box/dns/transport/local/local_fallback.go +++ /dev/null @@ -1,209 +0,0 @@ -package local - -import ( - "context" - "errors" - "net" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/dns" - "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/service" - - mDNS "github.com/miekg/dns" -) - -func RegisterTransport(registry *dns.TransportRegistry) { - dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewFallbackTransport) -} - -type FallbackTransport struct { - adapter.DNSTransport - ctx context.Context - fallback bool - resolver net.Resolver -} - -func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) { - transport, err := NewTransport(ctx, logger, tag, options) - if err != nil { - return nil, err - } - platformInterface := service.FromContext[platform.Interface](ctx) - if platformInterface == nil { - return transport, nil - } - return &FallbackTransport{ - DNSTransport: transport, - ctx: ctx, - }, nil -} - -func (f *FallbackTransport) Start(stage adapter.StartStage) error { - err := f.DNSTransport.Start(stage) - if err != nil { - return err - } - if stage != adapter.StartStatePostStart { - return nil - } - inboundManager := service.FromContext[adapter.InboundManager](f.ctx) - for _, inbound := range inboundManager.Inbounds() { - if inbound.Type() == C.TypeTun { - // platform tun hijacks DNS, so we can only use cgo resolver here - f.fallback = true - break - } - } - return nil -} - -func (f *FallbackTransport) Close() error { - return f.DNSTransport.Close() -} - -func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - if !f.fallback { - return f.DNSTransport.Exchange(ctx, message) - } - question := message.Question[0] - domain := dns.FqdnToDomain(question.Name) - if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { - var network string - if question.Qtype == mDNS.TypeA { - network = "ip4" - } else { - network = "ip6" - } - addresses, err := f.resolver.LookupNetIP(ctx, network, domain) - if err != nil { - var dnsError *net.DNSError - if errors.As(err, &dnsError) && dnsError.IsNotFound { - return nil, dns.RcodeRefused - } - return nil, err - } - return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil - } else if question.Qtype == mDNS.TypeNS { - records, err := f.resolver.LookupNS(ctx, domain) - if err != nil { - var dnsError *net.DNSError - if errors.As(err, &dnsError) && dnsError.IsNotFound { - return nil, dns.RcodeRefused - } - return nil, err - } - response := &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: message.Id, - Rcode: mDNS.RcodeSuccess, - Response: true, - }, - Question: []mDNS.Question{question}, - } - for _, record := range records { - response.Answer = append(response.Answer, &mDNS.NS{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeNS, - Class: mDNS.ClassINET, - Ttl: C.DefaultDNSTTL, - }, - Ns: record.Host, - }) - } - return response, nil - } else if question.Qtype == mDNS.TypeCNAME { - cname, err := f.resolver.LookupCNAME(ctx, domain) - if err != nil { - var dnsError *net.DNSError - if errors.As(err, &dnsError) && dnsError.IsNotFound { - return nil, dns.RcodeRefused - } - return nil, err - } - return &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: message.Id, - Rcode: mDNS.RcodeSuccess, - Response: true, - }, - Question: []mDNS.Question{question}, - Answer: []mDNS.RR{ - &mDNS.CNAME{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeCNAME, - Class: mDNS.ClassINET, - Ttl: C.DefaultDNSTTL, - }, - Target: cname, - }, - }, - }, nil - } else if question.Qtype == mDNS.TypeTXT { - records, err := f.resolver.LookupTXT(ctx, domain) - if err != nil { - var dnsError *net.DNSError - if errors.As(err, &dnsError) && dnsError.IsNotFound { - return nil, dns.RcodeRefused - } - return nil, err - } - return &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: message.Id, - Rcode: mDNS.RcodeSuccess, - Response: true, - }, - Question: []mDNS.Question{question}, - Answer: []mDNS.RR{ - &mDNS.TXT{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeCNAME, - Class: mDNS.ClassINET, - Ttl: C.DefaultDNSTTL, - }, - Txt: records, - }, - }, - }, nil - } else if question.Qtype == mDNS.TypeMX { - records, err := f.resolver.LookupMX(ctx, domain) - if err != nil { - var dnsError *net.DNSError - if errors.As(err, &dnsError) && dnsError.IsNotFound { - return nil, dns.RcodeRefused - } - return nil, err - } - response := &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: message.Id, - Rcode: mDNS.RcodeSuccess, - Response: true, - }, - Question: []mDNS.Question{question}, - } - for _, record := range records { - response.Answer = append(response.Answer, &mDNS.MX{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeA, - Class: mDNS.ClassINET, - Ttl: C.DefaultDNSTTL, - }, - Preference: record.Pref, - Mx: record.Host, - }) - } - return response, nil - } else { - return nil, E.New("only A, AAAA, NS, CNAME, TXT, MX queries are supported on current platform when using TUN, please switch to a fixed DNS server.") - } -} diff --git a/sing-box/dns/transport/local/local_resolv.go b/sing-box/dns/transport/local/local_resolv.go new file mode 100644 index 0000000000..cf7bcfbab8 --- /dev/null +++ b/sing-box/dns/transport/local/local_resolv.go @@ -0,0 +1,46 @@ +//go:build darwin + +package local + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/logger" + + mDNS "github.com/miekg/dns" +) + +var _ adapter.DNSTransport = (*ResolvTransport)(nil) + +type ResolvTransport struct { + dns.TransportAdapter + ctx context.Context + logger logger.ContextLogger +} + +func NewResolvTransport(ctx context.Context, logger log.ContextLogger, tag string) (adapter.DNSTransport, error) { + return &ResolvTransport{ + TransportAdapter: dns.NewTransportAdapter(C.DNSTypeLocal, tag, nil), + ctx: ctx, + logger: logger, + }, nil +} + +func (t *ResolvTransport) Start(stage adapter.StartStage) error { + return nil +} + +func (t *ResolvTransport) Close() error { + return nil +} + +func (t *ResolvTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + question := message.Question[0] + return doBlockingWithCtx(ctx, func() (*mDNS.Msg, error) { + return cgoResSearch(question.Name, int(question.Qtype), int(question.Qclass)) + }) +} diff --git a/sing-box/dns/transport/local/local_resolv_linkname.go b/sing-box/dns/transport/local/local_resolv_linkname.go new file mode 100644 index 0000000000..1495ae1d7b --- /dev/null +++ b/sing-box/dns/transport/local/local_resolv_linkname.go @@ -0,0 +1,170 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin + +package local + +import ( + "context" + "errors" + "runtime" + "syscall" + "unsafe" + _ "unsafe" + + E "github.com/sagernet/sing/common/exceptions" + + mDNS "github.com/miekg/dns" +) + +type ( + _C_char = byte + _C_int = int32 + _C_uchar = byte + _C_ushort = uint16 + _C_uint = uint32 + _C_ulong = uint64 + _C_struct___res_state = ResState + _C_struct_sockaddr = syscall.RawSockaddr +) + +func _C_free(p unsafe.Pointer) { runtime.KeepAlive(p) } + +func _C_malloc(n uintptr) unsafe.Pointer { + if n <= 0 { + n = 1 + } + return unsafe.Pointer(&make([]byte, n)[0]) +} + +const ( + MAXNS = 3 + MAXDNSRCH = 6 +) + +type ResState struct { + Retrans _C_int + Retry _C_int + Options _C_ulong + Nscount _C_int + Nsaddrlist [MAXNS]_C_struct_sockaddr + Id _C_ushort + Dnsrch [MAXDNSRCH + 1]*_C_char + Defname [256]_C_char + Pfcode _C_ulong + Ndots _C_uint + Nsort _C_uint + stub [128]byte +} + +//go:linkname ResNinit internal/syscall/unix.ResNinit +func ResNinit(state *_C_struct___res_state) error + +//go:linkname ResNsearch internal/syscall/unix.ResNsearch +func ResNsearch(state *_C_struct___res_state, dname *byte, class, typ int, ans *byte, anslen int) (int, error) + +//go:linkname ResNclose internal/syscall/unix.ResNclose +func ResNclose(state *_C_struct___res_state) + +//go:linkname GoString internal/syscall/unix.GoString +func GoString(p *byte) string + +// doBlockingWithCtx executes a blocking function in a separate goroutine when the provided +// context is cancellable. It is intended for use with calls that don't support context +// cancellation (cgo, syscalls). blocking func may still be running after this function finishes. +// For the duration of the execution of the blocking function, the thread is 'acquired' using [acquireThread], +// blocking might not be executed when the context gets canceled early. +func doBlockingWithCtx[T any](ctx context.Context, blocking func() (T, error)) (T, error) { + if err := acquireThread(ctx); err != nil { + var zero T + return zero, err + } + + if ctx.Done() == nil { + defer releaseThread() + return blocking() + } + + type result struct { + res T + err error + } + + res := make(chan result, 1) + go func() { + defer releaseThread() + var r result + r.res, r.err = blocking() + res <- r + }() + + select { + case r := <-res: + return r.res, r.err + case <-ctx.Done(): + var zero T + return zero, ctx.Err() + } +} + +//go:linkname acquireThread net.acquireThread +func acquireThread(ctx context.Context) error + +//go:linkname releaseThread net.releaseThread +func releaseThread() + +func cgoResSearch(hostname string, rtype, class int) (*mDNS.Msg, error) { + resStateSize := unsafe.Sizeof(_C_struct___res_state{}) + var state *_C_struct___res_state + if resStateSize > 0 { + mem := _C_malloc(resStateSize) + defer _C_free(mem) + memSlice := unsafe.Slice((*byte)(mem), resStateSize) + clear(memSlice) + state = (*_C_struct___res_state)(unsafe.Pointer(&memSlice[0])) + } + if err := ResNinit(state); err != nil { + return nil, errors.New("res_ninit failure: " + err.Error()) + } + defer ResNclose(state) + + bufSize := maxDNSPacketSize + buf := (*_C_uchar)(_C_malloc(uintptr(bufSize))) + defer _C_free(unsafe.Pointer(buf)) + + s, err := syscall.BytePtrFromString(hostname) + if err != nil { + return nil, err + } + + var size int + for { + size, _ = ResNsearch(state, s, class, rtype, buf, bufSize) + if size <= bufSize || size > 0xffff { + break + } + + // Allocate a bigger buffer to fit the entire msg. + _C_free(unsafe.Pointer(buf)) + bufSize = size + buf = (*_C_uchar)(_C_malloc(uintptr(bufSize))) + } + + var msg mDNS.Msg + if size == -1 { + // macOS's libresolv seems to directly return -1 for responses that are not success responses but are exchanged. + // However, we still need the response, so we fall back to parsing the entire buffer. + err = msg.Unpack(unsafe.Slice(buf, bufSize)) + if err != nil { + return nil, E.New("res_nsearch failure") + } + } else { + err = msg.Unpack(unsafe.Slice(buf, size)) + if err != nil { + return nil, err + } + } + return &msg, nil +} diff --git a/sing-box/dns/transport/local/local_resolv_stub.go b/sing-box/dns/transport/local/local_resolv_stub.go new file mode 100644 index 0000000000..8486b87a31 --- /dev/null +++ b/sing-box/dns/transport/local/local_resolv_stub.go @@ -0,0 +1,15 @@ +//go:build !darwin + +package local + +import ( + "context" + "os" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" +) + +func NewResolvTransport(ctx context.Context, logger log.ContextLogger, tag string) (adapter.DNSTransport, error) { + return nil, os.ErrInvalid +} diff --git a/sing-box/dns/transport/local/resolv_darwin.go b/sing-box/dns/transport/local/resolv_darwin.go new file mode 100644 index 0000000000..396e40deec --- /dev/null +++ b/sing-box/dns/transport/local/resolv_darwin.go @@ -0,0 +1,72 @@ +package local + +import ( + "context" + "net/netip" + "syscall" + "time" + "unsafe" + + E "github.com/sagernet/sing/common/exceptions" + + "github.com/miekg/dns" +) + +func dnsReadConfig(_ context.Context, _ string) *dnsConfig { + resStateSize := unsafe.Sizeof(_C_struct___res_state{}) + var state *_C_struct___res_state + if resStateSize > 0 { + mem := _C_malloc(resStateSize) + defer _C_free(mem) + memSlice := unsafe.Slice((*byte)(mem), resStateSize) + clear(memSlice) + state = (*_C_struct___res_state)(unsafe.Pointer(&memSlice[0])) + } + if err := ResNinit(state); err != nil { + return &dnsConfig{ + servers: defaultNS, + search: dnsDefaultSearch(), + ndots: 1, + timeout: 5 * time.Second, + attempts: 2, + err: E.Cause(err, "libresolv initialization failed"), + } + } + defer ResNclose(state) + conf := &dnsConfig{ + ndots: 1, + timeout: 5 * time.Second, + attempts: int(state.Retry), + } + for i := 0; i < int(state.Nscount); i++ { + addr := parseRawSockaddr(&state.Nsaddrlist[i]) + if addr.IsValid() { + conf.servers = append(conf.servers, addr.String()) + } + } + for i := 0; ; i++ { + search := state.Dnsrch[i] + if search == nil { + break + } + name := dns.Fqdn(GoString(search)) + if name == "" { + continue + } + conf.search = append(conf.search, name) + } + return conf +} + +func parseRawSockaddr(rawSockaddr *syscall.RawSockaddr) netip.Addr { + switch rawSockaddr.Family { + case syscall.AF_INET: + sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(rawSockaddr)) + return netip.AddrFrom4(sa.Addr) + case syscall.AF_INET6: + sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(rawSockaddr)) + return netip.AddrFrom16(sa.Addr) + default: + return netip.Addr{} + } +} diff --git a/sing-box/dns/transport/local/resolv_darwin_cgo.go b/sing-box/dns/transport/local/resolv_darwin_cgo.go deleted file mode 100644 index bbe4ccfefe..0000000000 --- a/sing-box/dns/transport/local/resolv_darwin_cgo.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:build darwin && cgo - -package local - -/* -#include -#include -#include -#include -*/ -import "C" - -import ( - "context" - "time" - - E "github.com/sagernet/sing/common/exceptions" - - "github.com/miekg/dns" -) - -func dnsReadConfig(_ context.Context, _ string) *dnsConfig { - var state C.struct___res_state - if C.res_ninit(&state) != 0 { - return &dnsConfig{ - servers: defaultNS, - search: dnsDefaultSearch(), - ndots: 1, - timeout: 5 * time.Second, - attempts: 2, - err: E.New("libresolv initialization failed"), - } - } - conf := &dnsConfig{ - ndots: 1, - timeout: 5 * time.Second, - attempts: int(state.retry), - } - for i := 0; i < int(state.nscount); i++ { - ns := state.nsaddr_list[i] - addr := C.inet_ntoa(ns.sin_addr) - if addr == nil { - continue - } - conf.servers = append(conf.servers, C.GoString(addr)) - } - for i := 0; ; i++ { - search := state.dnsrch[i] - if search == nil { - break - } - conf.search = append(conf.search, dns.Fqdn(C.GoString(search))) - } - return conf -} diff --git a/sing-box/dns/transport/local/resolv_unix.go b/sing-box/dns/transport/local/resolv_unix.go index f77f35536c..99eb71e6e2 100644 --- a/sing-box/dns/transport/local/resolv_unix.go +++ b/sing-box/dns/transport/local/resolv_unix.go @@ -1,4 +1,4 @@ -//go:build !windows && !(darwin && cgo) +//go:build !windows && !darwin package local diff --git a/sing-box/dns/transport_manager.go b/sing-box/dns/transport_manager.go index f41c9f9eb9..e289ccea0c 100644 --- a/sing-box/dns/transport_manager.go +++ b/sing-box/dns/transport_manager.go @@ -30,7 +30,7 @@ type TransportManager struct { transportByTag map[string]adapter.DNSTransport dependByTag map[string][]string defaultTransport adapter.DNSTransport - defaultTransportFallback adapter.DNSTransport + defaultTransportFallback func() (adapter.DNSTransport, error) fakeIPTransport adapter.FakeIPTransport } @@ -45,7 +45,7 @@ func NewTransportManager(logger logger.ContextLogger, registry adapter.DNSTransp } } -func (m *TransportManager) Initialize(defaultTransportFallback adapter.DNSTransport) { +func (m *TransportManager) Initialize(defaultTransportFallback func() (adapter.DNSTransport, error)) { m.defaultTransportFallback = defaultTransportFallback } @@ -56,14 +56,27 @@ func (m *TransportManager) Start(stage adapter.StartStage) error { } m.started = true m.stage = stage - transports := m.transports - m.access.Unlock() if stage == adapter.StartStateStart { if m.defaultTag != "" && m.defaultTransport == nil { + m.access.Unlock() return E.New("default DNS server not found: ", m.defaultTag) } - return m.startTransports(m.transports) + if m.defaultTransport == nil { + defaultTransport, err := m.defaultTransportFallback() + if err != nil { + m.access.Unlock() + return E.Cause(err, "default DNS server fallback") + } + m.transports = append(m.transports, defaultTransport) + m.transportByTag[defaultTransport.Tag()] = defaultTransport + m.defaultTransport = defaultTransport + } + transports := m.transports + m.access.Unlock() + return m.startTransports(transports) } else { + transports := m.transports + m.access.Unlock() for _, outbound := range transports { err := adapter.LegacyStart(outbound, stage) if err != nil { @@ -172,11 +185,7 @@ func (m *TransportManager) Transport(tag string) (adapter.DNSTransport, bool) { func (m *TransportManager) Default() adapter.DNSTransport { m.access.RLock() defer m.access.RUnlock() - if m.defaultTransport != nil { - return m.defaultTransport - } else { - return m.defaultTransportFallback - } + return m.defaultTransport } func (m *TransportManager) FakeIP() adapter.FakeIPTransport { diff --git a/sing-box/docs/changelog.md b/sing-box/docs/changelog.md index ef753064e3..736aff6b10 100644 --- a/sing-box/docs/changelog.md +++ b/sing-box/docs/changelog.md @@ -2,6 +2,18 @@ icon: material/alert-decagram --- +#### 1.13.0-alpha.3 + +* Improve `local` DNS server **1** +* Fixes and improvements + +**1**: + +On Apple platforms, Windows, and Linux (when using systemd-resolved), +`local` DNS server now works with Tun inbound which overrides system DNS servers. + +See [Local DNS Server](/configuration/dns/server/local/). + #### 1.13.0-alpha.2 * Add `preferred_by` rule item **1** diff --git a/sing-box/docs/configuration/dns/server/local.md b/sing-box/docs/configuration/dns/server/local.md index debcba9847..361c6c5b1d 100644 --- a/sing-box/docs/configuration/dns/server/local.md +++ b/sing-box/docs/configuration/dns/server/local.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [prefer_go](#prefer_go) + !!! question "Since sing-box 1.12.0" # Local @@ -15,6 +19,7 @@ icon: material/new-box { "type": "local", "tag": "", + "prefer_go": false // Dial Fields } @@ -24,10 +29,31 @@ icon: material/new-box ``` !!! info "Difference from legacy local server" - + * The old legacy local server only handles IP requests; the new one handles all types of requests and supports concurrent for IP requests. * The old local server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default. +### Fields + +#### prefer_go + +!!! question "Since sing-box 1.13.0" + +When enabled, `local` DNS server will resolve DNS by dialing itself whenever possible. + +Specifically, it disables following behaviors which was added as features in sing-box 1.13.0: + +* On Apple platforms: Use `libresolv` for resolution, as it is the only one that works properly with NetworkExtension + that overrides DNS servers (DHCP is also possible but is not considered). +* On Linux: Resolve through `systemd-resolvd`'s DBus interface when available. + +As a sole exception, it cannot disable the following behavior: + +In the Android graphical client, the `local` DNS server will always resolve DNS through the platform interface, +as there is no other way to obtain upstream DNS servers. + +On devices running Android versions lower than 10, this interface can only resolve IP queries. + ### Dial Fields See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/sing-box/docs/configuration/endpoint/index.md b/sing-box/docs/configuration/endpoint/index.md index 59101e759b..b409a7839f 100644 --- a/sing-box/docs/configuration/endpoint/index.md +++ b/sing-box/docs/configuration/endpoint/index.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "Since sing-box 1.11.0" # Endpoint diff --git a/sing-box/docs/configuration/endpoint/index.zh.md b/sing-box/docs/configuration/endpoint/index.zh.md index 6f31d3d636..f7e71b7574 100644 --- a/sing-box/docs/configuration/endpoint/index.zh.md +++ b/sing-box/docs/configuration/endpoint/index.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "自 sing-box 1.11.0 起" # 端点 diff --git a/sing-box/docs/configuration/endpoint/wireguard.md b/sing-box/docs/configuration/endpoint/wireguard.md index 65bb692910..dc3b82289a 100644 --- a/sing-box/docs/configuration/endpoint/wireguard.md +++ b/sing-box/docs/configuration/endpoint/wireguard.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "Since sing-box 1.11.0" ### Structure diff --git a/sing-box/docs/configuration/endpoint/wireguard.zh.md b/sing-box/docs/configuration/endpoint/wireguard.zh.md index cf82058001..1935135f87 100644 --- a/sing-box/docs/configuration/endpoint/wireguard.zh.md +++ b/sing-box/docs/configuration/endpoint/wireguard.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "自 sing-box 1.11.0 起" ### 结构 diff --git a/sing-box/docs/configuration/outbound/hysteria2.md b/sing-box/docs/configuration/outbound/hysteria2.md index 77063fb481..dc0a496500 100644 --- a/sing-box/docs/configuration/outbound/hysteria2.md +++ b/sing-box/docs/configuration/outbound/hysteria2.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "Changes in sing-box 1.11.0" :material-plus: [server_ports](#server_ports) diff --git a/sing-box/docs/configuration/outbound/hysteria2.zh.md b/sing-box/docs/configuration/outbound/hysteria2.zh.md index 0c5a631e10..d2a8598fb7 100644 --- a/sing-box/docs/configuration/outbound/hysteria2.zh.md +++ b/sing-box/docs/configuration/outbound/hysteria2.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "sing-box 1.11.0 中的更改" :material-plus: [server_ports](#server_ports) diff --git a/sing-box/option/dns.go b/sing-box/option/dns.go index 422d7b3ba9..4c1ac208bf 100644 --- a/sing-box/option/dns.go +++ b/sing-box/option/dns.go @@ -190,7 +190,7 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error { } } remoteOptions := RemoteDNSServerOptions{ - LocalDNSServerOptions: LocalDNSServerOptions{ + RawLocalDNSServerOptions: RawLocalDNSServerOptions{ DialerOptions: DialerOptions{ Detour: options.Detour, DomainResolver: &DomainResolveOptions{ @@ -211,7 +211,9 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error { switch serverType { case C.DNSTypeLocal: o.Type = C.DNSTypeLocal - o.Options = &remoteOptions.LocalDNSServerOptions + o.Options = &LocalDNSServerOptions{ + RawLocalDNSServerOptions: remoteOptions.RawLocalDNSServerOptions, + } case C.DNSTypeUDP: o.Type = C.DNSTypeUDP o.Options = &remoteOptions @@ -363,7 +365,7 @@ type HostsDNSServerOptions struct { Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"` } -type LocalDNSServerOptions struct { +type RawLocalDNSServerOptions struct { DialerOptions Legacy bool `json:"-"` LegacyStrategy DomainStrategy `json:"-"` @@ -371,8 +373,13 @@ type LocalDNSServerOptions struct { LegacyClientSubnet netip.Prefix `json:"-"` } +type LocalDNSServerOptions struct { + RawLocalDNSServerOptions + PreferGo bool `json:"prefer_go,omitempty"` +} + type RemoteDNSServerOptions struct { - LocalDNSServerOptions + RawLocalDNSServerOptions DNSServerAddressOptions LegacyAddressResolver string `json:"-"` LegacyAddressStrategy DomainStrategy `json:"-"` diff --git a/sing-box/option/rule.go b/sing-box/option/rule.go index 44927e3af5..3e7fd8771b 100644 --- a/sing-box/option/rule.go +++ b/sing-box/option/rule.go @@ -67,46 +67,46 @@ func (r Rule) IsValid() bool { } type RawDefaultRule struct { - Inbound badoption.Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` - Protocol badoption.Listable[string] `json:"protocol,omitempty"` - Client badoption.Listable[string] `json:"client,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - Geosite badoption.Listable[string] `json:"geosite,omitempty"` - SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` - GeoIP badoption.Listable[string] `json:"geoip,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - User badoption.Listable[string] `json:"user,omitempty"` - UserID badoption.Listable[int32] `json:"user_id,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"` - NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"` - DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"` - PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"` - RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` - Invert bool `json:"invert,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Client badoption.Listable[string] `json:"client,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"` + PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + Invert bool `json:"invert,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` diff --git a/sing-box/option/rule_dns.go b/sing-box/option/rule_dns.go index bbab993c6b..dbc1657898 100644 --- a/sing-box/option/rule_dns.go +++ b/sing-box/option/rule_dns.go @@ -68,48 +68,48 @@ func (r DNSRule) IsValid() bool { } type RawDefaultDNSRule struct { - Inbound badoption.Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` - Protocol badoption.Listable[string] `json:"protocol,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - Geosite badoption.Listable[string] `json:"geosite,omitempty"` - SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` - GeoIP badoption.Listable[string] `json:"geoip,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - IPAcceptAny bool `json:"ip_accept_any,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - User badoption.Listable[string] `json:"user,omitempty"` - UserID badoption.Listable[int32] `json:"user_id,omitempty"` - Outbound badoption.Listable[string] `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"` - NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"` - DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"` - RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` - RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` - Invert bool `json:"invert,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + IPAcceptAny bool `json:"ip_accept_any,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + Outbound badoption.Listable[string] `json:"outbound,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` + Invert bool `json:"invert,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` diff --git a/sing-box/option/rule_set.go b/sing-box/option/rule_set.go index fa839e3505..b06342280b 100644 --- a/sing-box/option/rule_set.go +++ b/sing-box/option/rule_set.go @@ -182,29 +182,29 @@ func (r HeadlessRule) IsValid() bool { } type DefaultHeadlessRule struct { - QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"` - DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"` Invert bool `json:"invert,omitempty"` diff --git a/sing-box/route/network.go b/sing-box/route/network.go index 090e4c0ded..6a45abc6a6 100644 --- a/sing-box/route/network.go +++ b/sing-box/route/network.go @@ -312,7 +312,7 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { if r.interfaceMonitor == nil { return nil } - bindFunc := control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { + return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { remoteAddr := M.ParseSocksaddr(address).Addr if remoteAddr.IsValid() { iif, err := r.interfaceFinder.ByAddr(remoteAddr) @@ -326,16 +326,6 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { } return defaultInterface.Name, defaultInterface.Index, nil }) - return func(network, address string, conn syscall.RawConn) error { - err := bindFunc(network, address, conn) - if err != nil { - return err - } - if r.autoRedirectOutputMark > 0 { - return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn) - } - return nil - } } } @@ -366,6 +356,15 @@ func (r *NetworkManager) AutoRedirectOutputMark() uint32 { return r.autoRedirectOutputMark } +func (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func { + return func(network, address string, conn syscall.RawConn) error { + if r.autoRedirectOutputMark == 0 { + return nil + } + return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn) + } +} + func (r *NetworkManager) NetworkMonitor() tun.NetworkUpdateMonitor { return r.networkMonitor } diff --git a/sing-box/route/rule/rule_default_interface_address.go b/sing-box/route/rule/rule_default_interface_address.go index 940d3a064f..2d7fdebe68 100644 --- a/sing-box/route/rule/rule_default_interface_address.go +++ b/sing-box/route/rule/rule_default_interface_address.go @@ -17,7 +17,7 @@ type DefaultInterfaceAddressItem struct { interfaceAddresses []netip.Prefix } -func NewDefaultInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses badoption.Listable[badoption.Prefixable]) *DefaultInterfaceAddressItem { +func NewDefaultInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses badoption.Listable[*badoption.Prefixable]) *DefaultInterfaceAddressItem { item := &DefaultInterfaceAddressItem{ interfaceMonitor: networkManager.InterfaceMonitor(), interfaceAddresses: make([]netip.Prefix, 0, len(interfaceAddresses)), diff --git a/sing-box/route/rule/rule_interface_address.go b/sing-box/route/rule/rule_interface_address.go index 53ece6834c..d4c75d38d4 100644 --- a/sing-box/route/rule/rule_interface_address.go +++ b/sing-box/route/rule/rule_interface_address.go @@ -19,7 +19,7 @@ type InterfaceAddressItem struct { description string } -func NewInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]]) *InterfaceAddressItem { +func NewInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]]) *InterfaceAddressItem { item := &InterfaceAddressItem{ networkManager: networkManager, interfaceAddresses: make(map[string][]netip.Prefix, interfaceAddresses.Size()), diff --git a/sing-box/route/rule/rule_network_interface_address.go b/sing-box/route/rule/rule_network_interface_address.go index c365be3bed..c699c593fa 100644 --- a/sing-box/route/rule/rule_network_interface_address.go +++ b/sing-box/route/rule/rule_network_interface_address.go @@ -20,7 +20,7 @@ type NetworkInterfaceAddressItem struct { description string } -func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[option.InterfaceType, badoption.Listable[badoption.Prefixable]]) *NetworkInterfaceAddressItem { +func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]]) *NetworkInterfaceAddressItem { item := &NetworkInterfaceAddressItem{ networkManager: networkManager, interfaceAddresses: make(map[C.InterfaceType][]netip.Prefix, interfaceAddresses.Size()), diff --git a/small/luci-app-momo/Makefile b/small/luci-app-momo/Makefile index 1505cdb468..3fd48bc264 100644 --- a/small/luci-app-momo/Makefile +++ b/small/luci-app-momo/Makefile @@ -1,6 +1,6 @@ include $(TOPDIR)/rules.mk -PKG_VERSION:=1.0.4 +PKG_VERSION:=1.0.5 LUCI_TITLE:=LuCI Support for momo LUCI_DEPENDS:=+luci-base +momo diff --git a/small/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js b/small/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js index 7d9b8e41e8..6c26eec298 100644 --- a/small/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js +++ b/small/luci-app-momo/htdocs/luci-static/resources/view/momo/editor.js @@ -32,7 +32,7 @@ return view.extend({ }; for (const subscription of subscriptions) { - o.value(paths.subscriptions_dir + '/' + subscription['.name'] + '.yaml', _('Subscription:') + subscription.name); + o.value(paths.subscriptions_dir + '/' + subscription['.name'] + '.json', _('Subscription:') + subscription.name); }; o.value(paths.run_profile_path, _('Profile for Startup')); diff --git a/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm b/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm index 9d53f97553..373372c636 100644 --- a/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm +++ b/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm @@ -963,7 +963,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_allowInsecure', true); - if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { opt.set(dom_prefix + 'tls_allowInsecure', false); } if (queryParam.fp && queryParam.fp.trim() != "") { @@ -1206,9 +1206,10 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin tls_serverName = tls_serverName || ""; opt.set(dom_prefix + 'tls_serverName', tls_serverName); opt.set(dom_prefix + 'flow', (queryParam.flow || '').replace('-udp443', '')); + opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); } opt.set(dom_prefix + 'tls_allowInsecure', true); - if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { opt.set(dom_prefix + 'tls_allowInsecure', false); } @@ -1346,7 +1347,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_allowInsecure', true); - if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { opt.set(dom_prefix + 'tls_allowInsecure', false); } if (queryParam.fp && queryParam.fp.trim() != "") { @@ -1542,7 +1543,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin opt.set(dom_prefix + 'tuic_alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_allowInsecure', true); - if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { opt.set(dom_prefix + 'tls_allowInsecure', false); } if (hash) { @@ -1585,7 +1586,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_allowInsecure', true); - if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { opt.set(dom_prefix + 'tls_allowInsecure', false); } if (queryParam.fp && queryParam.fp.trim() != "") { diff --git a/small/luci-app-passwall/root/usr/share/passwall/subscribe.lua b/small/luci-app-passwall/root/usr/share/passwall/subscribe.lua index 0e68bc13ac..a30608c2e3 100755 --- a/small/luci-app-passwall/root/usr/share/passwall/subscribe.lua +++ b/small/luci-app-passwall/root/usr/share/passwall/subscribe.lua @@ -1036,8 +1036,8 @@ local function processData(szType, content, add_mode, add_from) end result.encryption = params.encryption or "none" - result.flow = params.flow and params.flow:gsub("-udp443", "") or nil + result.alpn = params.alpn if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp" or result.transport == "splithttp") then log("跳过节点:" .. result.remarks ..",因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式,需更换Xray。") diff --git a/small/momo/Makefile b/small/momo/Makefile index 16fa276acb..8963db5d3a 100644 --- a/small/momo/Makefile +++ b/small/momo/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=momo PKG_VERSION:=2025.08.11 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_LICENSE:=GPL-3.0+ PKG_MAINTAINER:=Joseph Mory diff --git a/small/momo/files/ucode/hijack.ut b/small/momo/files/ucode/hijack.ut index 166c6af94e..d966cfb491 100644 --- a/small/momo/files/ucode/hijack.ut +++ b/small/momo/files/ucode/hijack.ut @@ -252,6 +252,7 @@ table inet momo { } {% endif %} + {% if (tcp_mode == 'redirect'): %} chain router_redirect { {% for (let access_control in router_access_control): %} {% if (access_control['enabled']): %} @@ -277,7 +278,9 @@ table inet momo { {% endif %} {% endfor %} } + {% endif %} + {% if (tcp_mode == 'tproxy' || udp_mode == 'tproxy'): %} chain router_tproxy { {% for (let access_control in router_access_control): %} {% if (access_control['enabled']): %} @@ -303,7 +306,9 @@ table inet momo { {% endif %} {% endfor %} } + {% endif %} + {% if (tcp_mode == 'tun' || udp_mode == 'tun'): %} chain router_tun { {% for (let access_control in router_access_control): %} {% if (access_control['enabled']): %} @@ -330,6 +335,7 @@ table inet momo { {% endfor %} } {% endif %} + {% endif %} {% if (lan_proxy): %} {% if (length(dns_hijack_nfproto) > 0): %} @@ -358,6 +364,7 @@ table inet momo { } {% endif %} + {% if (tcp_mode == 'redirect'): %} chain lan_redirect { {% for (let access_control in lan_access_control): %} {% if (access_control['enabled']): %} @@ -381,7 +388,9 @@ table inet momo { {% endif %} {% endfor %} } + {% endif %} + {% if (tcp_mode == 'tproxy' || udp_mode == 'tproxy'): %} chain lan_tproxy { {% for (let access_control in lan_access_control): %} {% if (access_control['enabled']): %} @@ -405,7 +414,9 @@ table inet momo { {% endif %} {% endfor %} } + {% endif %} + {% if (tcp_mode == 'tun' || udp_mode == 'tun'): %} chain lan_tun { {% for (let access_control in lan_access_control): %} {% if (access_control['enabled']): %} @@ -430,6 +441,7 @@ table inet momo { {% endfor %} } {% endif %} + {% endif %} {% if (router_proxy): %} chain nat_output { diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index 269cf96812..b456a2701f 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -30,7 +30,7 @@ define Download/geosite HASH:=01dae2a9c31b5c74ba7e54d8d51e0060688ed22da493eaf09f6eeeec89db395e endef -GEOSITE_IRAN_VER:=202508110046 +GEOSITE_IRAN_VER:=202508180044 GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER) define Download/geosite-ir URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/ diff --git a/v2rayn/.gitignore b/v2rayn/.gitignore index 524d236bee..7d5416b6f7 100644 --- a/v2rayn/.gitignore +++ b/v2rayn/.gitignore @@ -397,4 +397,5 @@ FodyWeavers.xsd *.msp # JetBrains Rider -*.sln.iml \ No newline at end of file +.idea/ +*.sln.iml diff --git a/v2rayn/package-rhel.sh b/v2rayn/package-rhel.sh new file mode 100644 index 0000000000..2bfe1ad221 --- /dev/null +++ b/v2rayn/package-rhel.sh @@ -0,0 +1,511 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ===== Require Red Hat Enterprise Linux / Rocky Linux / AlmaLinux / CentOS Stream ======= +if [[ -r /etc/os-release ]]; then + . /etc/os-release + case "$ID" in + rhel|rocky|almalinux|centos) + echo "[OK] Detected supported system: $NAME $VERSION_ID" + ;; + *) + echo "[ERROR] Unsupported system: $NAME ($ID)." + echo "This script only supports Red Hat Enterprise Linux / Rocky Linux / AlmaLinux / CentOS Stream." + exit 1 + ;; + esac +else + echo "[ERROR] Cannot detect system (missing /etc/os-release)." + exit 1 +fi + +# ===== Config & Parse arguments ========================================================= +VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty +WITH_CORE="both" # Default: bundle both xray+sing-box +AUTOSTART=0 # 1 = enable system-wide autostart (/etc/xdg/autostart) +FORCE_NETCORE=0 # --netcore => skip archive bundle, use separate downloads + +# If the first argument starts with --, don’t treat it as version number +if [[ "${VERSION_ARG:-}" == --* ]]; then + VERSION_ARG="" +fi +# Take the first non --* argument as version, discard it +if [[ -n "${VERSION_ARG:-}" ]]; then shift || true; fi + +# Parse remaining optional arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --with-core) WITH_CORE="${2:-both}"; shift 2;; + --autostart) AUTOSTART=1; shift;; + --xray-ver) XRAY_VER="${2:-}"; shift 2;; # Specify xray version (optional) + --singbox-ver) SING_VER="${2:-}"; shift 2;; # Specify sing-box version (optional) + --netcore) FORCE_NETCORE=1; shift;; # NEW: force old mode (no bundle zip) + *) + if [[ -z "${VERSION_ARG:-}" ]]; then VERSION_ARG="$1"; fi + shift;; + esac +done + +# ===== Environment check =============================================================== +arch="$(uname -m)" +[[ "$arch" == "aarch64" || "$arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; } + +# Dependencies (packaging shouldn’t be run as root, but this line needs sudo) +sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar || sudo dnf -y install dotnet-sdk +command -v curl >/dev/null + +# Root directory = the script's location +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Git submodules (tolerant) +if [[ -f .gitmodules ]]; then + git submodule sync --recursive || true + git submodule update --init --recursive || true +fi + +# ===== Locate project ================================================================ +PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj" +if [[ ! -f "$PROJECT" ]]; then + PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)" +fi +[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; } + +# ===== Resolve GUI version & auto checkout ============================================ +# Rules: +# - If VERSION_ARG provided: try to checkout that tag (vX.Y.Z or X.Y.Z). If not found, ask which channel (Latest vs Pre-release), +# default to Latest, then fetch the chosen channel's latest tag and checkout. +# - If VERSION_ARG not provided: ask the channel first (default Latest), then checkout to that tag. +# - If not a git repo, warn and continue without switching (keep current branch). +VERSION="" # final GUI version string without 'v' prefix + +choose_channel() { + # Print menu to stderr first, then read from stdin; only echo the chosen token to stdout. + local ch="latest" sel="" + if [[ -t 0 ]]; then + >&2 echo "[?] Choose v2rayN release channel:" + >&2 echo " 1) Latest (stable) [default]" + >&2 echo " 2) Pre-release (preview)" + read -r -p "Enter 1 or 2 (default 1): " sel + case "${sel:-}" in + 2) ch="prerelease" ;; + *) ch="latest" ;; + esac + else + ch="latest" + fi + echo "$ch" +} + +get_latest_tag_latest() { + # Use GitHub API: /releases/latest → tag_name + curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases/latest" \ + | grep -Eo '"tag_name":\s*"v?[^"]+"' \ + | head -n1 \ + | sed -E 's/.*"tag_name":\s*"v?([^"]+)".*/\1/' +} + +get_latest_tag_prerelease() { + # Use GitHub API: /releases?per_page=20 and pick the newest prerelease=true's tag_name + local json + json="$(curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20")" || return 1 + echo "$json" \ + | awk -v RS='},' '/"prerelease":[[:space:]]*true/ { if (match($0, /"tag_name":[[:space:]]*"v?[^"]+"/, m)) { t=m[0]; sub(/.*"tag_name":[[:space:]]*"?v?/, "", t); sub(/".*/, "", t); print t; exit } }' +} + +git_try_checkout() { + # Try a series of refs and checkout when found. + # Args: version-like string (may contain leading 'v') + local want="$1" ref="" + if git rev-parse --git-dir >/dev/null 2>&1; then + git fetch --tags --force --prune --depth=1 || true + if git rev-parse "refs/tags/v${want}" >/dev/null 2>&1; then + ref="v${want}" + elif git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then + ref="${want}" + elif git rev-parse --verify "${want}" >/dev/null 2>&1; then + ref="${want}" + fi + if [[ -n "$ref" ]]; then + echo "[OK] Found ref '${ref}', checking out..." + git checkout -f "${ref}" + if [[ -f .gitmodules ]]; then + git submodule sync --recursive || true + git submodule update --init --recursive || true + fi + return 0 + fi + fi + return 1 +} + +if git rev-parse --git-dir >/dev/null 2>&1; then + if [[ -n "${VERSION_ARG:-}" ]]; then + echo "[*] Trying to switch v2rayN repo to version: ${VERSION_ARG}" + if git_try_checkout "${VERSION_ARG#v}"; then + VERSION="${VERSION_ARG#v}" + else + echo "[WARN] Tag '${VERSION_ARG}' not found." + ch="$(choose_channel)" + echo "[*] Resolving ${ch} tag from GitHub releases..." + tag="" + if [[ "$ch" == "prerelease" ]]; then + tag="$(get_latest_tag_prerelease || true)" + else + tag="$(get_latest_tag_latest || true)" + fi + [[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; } + echo "[*] Latest tag for '${ch}': ${tag}" + git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; } + VERSION="${tag#v}" + fi + else + # No explicit GUI version passed: ask channel first + ch="$(choose_channel)" + echo "[*] Resolving ${ch} tag from GitHub releases..." + tag="" + if [[ "$ch" == "prerelease" ]]; then + tag="$(get_latest_tag_prerelease || true)" + else + tag="$(get_latest_tag_latest || true)" + fi + [[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; } + echo "[*] Latest tag for '${ch}': ${tag}" + git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; } + VERSION="${tag#v}" + fi +else + echo "[WARN] Current directory is not a git repo; cannot checkout version. Proceeding on current tree." + VERSION="${VERSION_ARG:-}" + if [[ -z "$VERSION" ]]; then + if git describe --tags --abbrev=0 >/dev/null 2>&1; then + VERSION="$(git describe --tags --abbrev=0)" + else + VERSION="0.0.0+git" + fi + fi + VERSION="${VERSION#v}" +fi +echo "[*] GUI version resolved as: ${VERSION}" + +# ===== .NET publish (non-single file, self-contained) =========================================== +dotnet clean "$PROJECT" -c Release +rm -rf "$(dirname "$PROJECT")/bin/Release/net8.0" || true + +dotnet restore "$PROJECT" +dotnet publish "$PROJECT" \ + -c Release -r "$( [[ "$arch" == "aarch64" ]] && echo linux-arm64 || echo linux-x64 )" \ + -p:PublishSingleFile=false \ + -p:SelfContained=true \ + -p:IncludeNativeLibrariesForSelfExtract=true + +RID_DIR="$( [[ "$arch" == "aarch64" ]] && echo linux-arm64 || echo linux-x64 )" +PUBDIR="$(dirname "$PROJECT")/bin/Release/net8.0/${RID_DIR}/publish" +[[ -d "$PUBDIR" ]] + +# ===== Download Core(Optional) ======================================================== +download_xray() { + local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip" + mkdir -p "$outdir" + if [[ -z "$ver" ]]; then + # Latest version + ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \ + | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true + fi + [[ -n "$ver" ]] || { echo "[xray] Failed to get version"; return 1; } + + if [[ "$arch" == "aarch64" ]]; then + url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-arm64-v8a.zip" + else + url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-64.zip" + fi + echo "[+] Download xray: $url" + tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN + curl -fL "$url" -o "$tmp/$zipname" + unzip -q "$tmp/$zipname" -d "$tmp" + install -Dm755 "$tmp/xray" "$outdir/xray" +} + +download_singbox() { + local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin + mkdir -p "$outdir" + if [[ -z "$ver" ]]; then + ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \ + | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true + fi + [[ -n "$ver" ]] || { echo "[sing-box] Failed to get version"; return 1; } + + if [[ "$arch" == "aarch64" ]]; then + url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-arm64.tar.gz" + else + url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-amd64.tar.gz" + fi + echo "[+] Download sing-box: $url" + tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN + curl -fL "$url" -o "$tmp/$tarname" + tar -C "$tmp" -xzf "$tmp/$tarname" + bin="$(find "$tmp" -type f -name 'sing-box' | head -n1 || true)" + [[ -n "$bin" ]] || { echo "[!] sing-box unpack failed"; return 1; } + install -Dm755 "$bin" "$outdir/sing-box" +} + +# === Geo rule download (ZIP-LIKE LAYOUT for netcore mode) ===================== +# Make netcore output match the ZIP bundle structure: +# - All geo databases at outroot/bin/ (geosite.dat, geoip.dat, Country.mmdb, geoip-only-cn-private.dat, geoip.metadb) +# - All *.srs rule-sets at outroot/bin/srss/ +# (Binaries stay under outroot/bin/xray and outroot/bin/sing_box as before.) +download_geo_assets() { + local outroot="$1" + local bin_dir="$outroot/bin" + local srss_dir="$bin_dir/srss" + mkdir -p "$bin_dir" "$srss_dir" + + echo "[+] Download Xray Geo (geosite/geoip/...) to ${bin_dir}" + curl -fsSL -o "$bin_dir/geosite.dat" \ + "https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geosite.dat" + curl -fsSL -o "$bin_dir/geoip.dat" \ + "https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geoip.dat" + curl -fsSL -o "$bin_dir/geoip-only-cn-private.dat" \ + "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat" + curl -fsSL -o "$bin_dir/Country.mmdb" \ + "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb" + + echo "[+] Download sing-box rule DB & rule-sets to ZIP-like paths" + curl -fsSL -o "$bin_dir/geoip.metadb" \ + "https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.metadb" || true + + for f in \ + geoip-private.srs geoip-cn.srs geoip-facebook.srs geoip-fastly.srs \ + geoip-google.srs geoip-netflix.srs geoip-telegram.srs geoip-twitter.srs; do + curl -fsSL -o "$srss_dir/$f" \ + "https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true + done + + for f in \ + geosite-cn.srs geosite-gfw.srs geosite-greatfire.srs \ + geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do + curl -fsSL -o "$srss_dir/$f" \ + "https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true + done +} + +# === NEW: Prefer the all-in-one v2rayN bundle zip first ======================= +download_v2rayn_bundle() { + local outroot="$1" + local url="" + if [[ "$arch" == "aarch64" ]]; then + url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64.zip" + else + url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64.zip" + fi + echo "[+] Try v2rayN bundle archive: $url" + local tmp zipname + tmp="$(mktemp -d)"; zipname="$tmp/v2rayn.zip" + curl -fL "$url" -o "$zipname" || { echo "[!] Bundle download failed"; return 1; } + unzip -q "$zipname" -d "$tmp" || { echo "[!] Bundle unzip failed"; return 1; } + + # Normalize layout: copy 'bin/' if present; otherwise copy all + if [[ -d "$tmp/bin" ]]; then + mkdir -p "$outroot/bin" + rsync -a "$tmp/bin/" "$outroot/bin/" + else + rsync -a "$tmp/" "$outroot/" + fi + + # --- CLEANUPS (keep bundle-only adjustments) ------------------------------- + rm -f "$outroot/v2rayn.zip" 2>/dev/null || true + find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true + + local nested_dir + nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)" + if [[ -n "${nested_dir:-}" && -d "$nested_dir/bin" ]]; then + mkdir -p "$outroot/bin" + rsync -a "$nested_dir/bin/" "$outroot/bin/" + rm -rf "$nested_dir" + fi + # --------------------------------------------------------------------------- + + echo "[+] Bundle extracted to $outroot" +} + +# ===== Copy publish files to RPM build root ================================================== +rpmdev-setuptree +TOPDIR="${HOME}/rpmbuild" +SPECDIR="${TOPDIR}/SPECS" +SOURCEDIR="${TOPDIR}/SOURCES" + +PKGROOT="v2rayN-publish" +WORKDIR="$(mktemp -d)" +trap 'rm -rf "$WORKDIR"' EXIT + +mkdir -p "$WORKDIR/$PKGROOT" +cp -a "$PUBDIR/." "$WORKDIR/$PKGROOT/" + +# icon(Optional) +ICON_CANDIDATE="$(dirname "$PROJECT")/../v2rayN.Desktop/v2rayN.png" +[[ -f "$ICON_CANDIDATE" ]] && cp "$ICON_CANDIDATE" "$WORKDIR/$PKGROOT/v2rayn.png" || true + +# bin directory structure +mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box" + +# ====== NEW decision: prefer bundle zip unless --netcore, else fall back ====== +if [[ "$FORCE_NETCORE" -eq 0 ]]; then + if download_v2rayn_bundle "$WORKDIR/$PKGROOT"; then + echo "[*] Using v2rayN bundle archive." + else + echo "[*] Bundle failed, fallback to separate core + rules." + if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then + download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)" + fi + if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then + download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)" + fi + download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)" + fi +else + echo "[*] --netcore specified: use separate core + rules." + if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then + download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)" + fi + if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then + download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)" + fi + download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)" +fi + +tar -C "$WORKDIR" -czf "$SOURCEDIR/$PKGROOT.tar.gz" "$PKGROOT" + +# ===== Generate SPEC (heredoc with placeholders) =================================== +SPECFILE="$SPECDIR/v2rayN.spec" +cat > "$SPECFILE" <<'SPEC' +%global debug_package %{nil} +%undefine _debuginfo_subpackages +%undefine _debugsource_packages +# Ignore outdated LTTng dependencies incorrectly reported by the .NET runtime (to avoid installation failures) +%global __requires_exclude ^liblttng-ust\.so\..*$ + +Name: v2rayN +Version: __VERSION__ +Release: 1%{?dist} +Summary: v2rayN (Avalonia) GUI client for Linux (x86_64/aarch64) +License: GPL-3.0-only +URL: https://github.com/2dust/v2rayN +ExclusiveArch: aarch64 x86_64 +Source0: __PKGROOT__.tar.gz + +# Runtime dependencies (Avalonia / X11 / Fonts / GL) +Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon +Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL + +%description +v2rayN GUI client built with Avalonia. +Installs self-contained publish under /opt/v2rayN and a launcher 'v2rayn'. +Cores (if bundled): /opt/v2rayN/bin/xray, /opt/v2rayN/bin/sing_box. +Geo files for Xray are placed at /opt/v2rayN/bin/xray; launcher will symlink them into user's XDG data dir on first run. + +%prep +%setup -q -n __PKGROOT__ + +%build +# no build + +%install +install -dm0755 %{buildroot}/opt/v2rayN +cp -a * %{buildroot}/opt/v2rayN/ + +# Launcher (prioritize ELF first, then fall back to DLL; also create Geo symlinks for the user) +install -dm0755 %{buildroot}%{_bindir} +cat > %{buildroot}%{_bindir}/v2rayn << 'EOF' +#!/usr/bin/bash +set -euo pipefail +DIR="/opt/v2rayN" + +# --- SYMLINK GEO into user's XDG dir (new) --- +XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" +USR_GEO_DIR="$XDG_DATA_HOME/v2rayN/bin" +SYS_XRAY_DIR="$DIR/bin/xray" +mkdir -p "$USR_GEO_DIR" +for f in geosite.dat geoip.dat geoip-only-cn-private.dat Country.mmdb; do + if [[ -f "$SYS_XRAY_DIR/$f" && ! -e "$USR_GEO_DIR/$f" ]]; then + ln -s "$SYS_XRAY_DIR/$f" "$USR_GEO_DIR/$f" || true + fi +done +# --- end GEO --- + +# Prefer native ELF(apphost) +if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi + +# DLL fallback (for framework-dependent publish) +for dll in v2rayN.Desktop.dll v2rayN.dll; do + if [[ -f "$DIR/$dll" ]]; then exec /usr/bin/dotnet "$DIR/$dll" "$@"; fi +done + +echo "v2rayN launcher: no executable found in $DIR" >&2 +ls -l "$DIR" >&2 || true +exit 1 +EOF +chmod 0755 %{buildroot}%{_bindir}/v2rayn + +# Desktop File +install -dm0755 %{buildroot}%{_datadir}/applications +cat > %{buildroot}%{_datadir}/applications/v2rayn.desktop << 'EOF' +[Desktop Entry] +Type=Application +Name=v2rayN +Comment=GUI client for Xray / sing-box +Exec=v2rayn +Icon=v2rayn +Terminal=false +Categories=Network; +EOF + +# icon +if [ -f "%{_builddir}/__PKGROOT__/v2rayn.png" ]; then + install -dm0755 %{buildroot}%{_datadir}/icons/hicolor/256x256/apps + install -m0644 %{_builddir}/__PKGROOT__/v2rayn.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png +fi + +%post +/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true +/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true + +%postun +/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true +/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true + +%files +%{_bindir}/v2rayn +/opt/v2rayN +%{_datadir}/applications/v2rayn.desktop +%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png +SPEC + +# Optional: system-wide autostart (append block, keep original logic unchanged) +if [[ "$AUTOSTART" -eq 1 ]]; then +cat >> "$SPECFILE" <<'SPEC' +# System-wide autostart entry +%install +install -dm0755 %{buildroot}/etc/xdg/autostart +cat > %{buildroot}/etc/xdg/autostart/v2rayn.desktop << 'EOF' +[Desktop Entry] +Type=Application +Name=v2rayN (Autostart) +Exec=v2rayn +X-GNOME-Autostart-enabled=true +NoDisplay=false +EOF + +%files +%config(noreplace) /etc/xdg/autostart/v2rayn.desktop +SPEC +fi + +# Injecting version/package root placeholders +sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE" +sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE" + +# ===== Build RPM ================================================================ +rpmbuild -ba "$SPECFILE" + +echo "Build done. RPM at:" +ls -1 "${TOPDIR}/RPMS/$( [[ "$arch" == "aarch64" ]] && echo aarch64 || echo x86_64 )/v2rayN-${VERSION}-1"*.rpm diff --git a/v2rayn/v2rayN/ServiceLib/Global.cs b/v2rayn/v2rayN/ServiceLib/Global.cs index 615c0d3820..733c569d44 100644 --- a/v2rayn/v2rayN/ServiceLib/Global.cs +++ b/v2rayn/v2rayN/ServiceLib/Global.cs @@ -289,6 +289,31 @@ public class Global "sing_box" ]; + public static readonly HashSet XraySupportConfigType = + [ + EConfigType.VMess, + EConfigType.VLESS, + EConfigType.Shadowsocks, + EConfigType.Trojan, + EConfigType.WireGuard, + EConfigType.SOCKS, + EConfigType.HTTP, + ]; + + public static readonly HashSet SingboxSupportConfigType = + [ + EConfigType.VMess, + EConfigType.VLESS, + EConfigType.Shadowsocks, + EConfigType.Trojan, + EConfigType.Hysteria2, + EConfigType.TUIC, + EConfigType.Anytls, + EConfigType.WireGuard, + EConfigType.SOCKS, + EConfigType.HTTP, + ]; + public static readonly List DomainStrategies = [ AsIs, diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index da0ee7730d..54fc88ba41 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -10,8 +10,8 @@ namespace ServiceLib.Resx { using System; - - + + /// /// 一个强类型的资源类,用于查找本地化的字符串等。 /// @@ -23,15 +23,15 @@ namespace ServiceLib.Resx { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class ResUI { - + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal ResUI() { } - + /// /// 返回此类使用的缓存的 ResourceManager 实例。 /// @@ -45,7 +45,7 @@ namespace ServiceLib.Resx { return resourceMan; } } - + /// /// 重写当前线程的 CurrentUICulture 属性,对 /// 使用此强类型资源类的所有资源查找执行重写。 @@ -59,7 +59,7 @@ namespace ServiceLib.Resx { resourceCulture = value; } } - + /// /// 查找类似 Do you want to append rules? Choose yes to append, no to replace. 的本地化字符串。 /// @@ -68,7 +68,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("AddBatchRoutingRulesYesNo", resourceCulture); } } - + /// /// 查找类似 All 的本地化字符串。 /// @@ -77,7 +77,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("AllGroupServers", resourceCulture); } } - + /// /// 查找类似 Export share link to clipboard successfully 的本地化字符串。 /// @@ -86,7 +86,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("BatchExportURLSuccessfully", resourceCulture); } } - + /// /// 查找类似 Please check the Configuration settings first. 的本地化字符串。 /// @@ -95,7 +95,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("CheckServerSettings", resourceCulture); } } - + /// /// 查找类似 Invalid configuration format. 的本地化字符串。 /// @@ -104,7 +104,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("ConfigurationFormatIncorrect", resourceCulture); } } - + /// /// 查找类似 Host filter 的本地化字符串。 /// @@ -113,7 +113,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("ConnectionsHostFilterTitle", resourceCulture); } } - + /// /// 查找类似 Note that custom configuration relies entirely on your own configuration and does not work with all settings. If you want to use the system proxy, please modify the listening port manually. 的本地化字符串。 /// @@ -122,7 +122,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("CustomServerTips", resourceCulture); } } - + /// /// 查找类似 Downloading... 的本地化字符串。 /// @@ -131,7 +131,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("Downloading", resourceCulture); } } - + /// /// 查找类似 Failed to convert configuration file 的本地化字符串。 /// @@ -140,7 +140,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FailedConversionConfiguration", resourceCulture); } } - + /// /// 查找类似 Failed to generate default configuration file 的本地化字符串。 /// @@ -149,7 +149,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FailedGenDefaultConfiguration", resourceCulture); } } - + /// /// 查找类似 Failed to get the default configuration 的本地化字符串。 /// @@ -158,7 +158,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FailedGetDefaultConfiguration", resourceCulture); } } - + /// /// 查找类似 Failed to import custom configuration Configuration 的本地化字符串。 /// @@ -167,7 +167,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FailedImportedCustomServer", resourceCulture); } } - + /// /// 查找类似 Failed to read configuration file 的本地化字符串。 /// @@ -176,7 +176,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FailedReadConfiguration", resourceCulture); } } - + /// /// 查找类似 Failed to run Core, please check the prompt information 的本地化字符串。 /// @@ -185,7 +185,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FailedToRunCore", resourceCulture); } } - + /// /// 查找类似 Please fill in the correct config template 的本地化字符串。 /// @@ -194,7 +194,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FillCorrectConfigTemplateText", resourceCulture); } } - + /// /// 查找类似 Please fill in the correct custom DNS 的本地化字符串。 /// @@ -203,7 +203,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FillCorrectDNSText", resourceCulture); } } - + /// /// 查找类似 Please enter the correct port format. 的本地化字符串。 /// @@ -212,7 +212,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FillCorrectServerPort", resourceCulture); } } - + /// /// 查找类似 Please enter the local listening port. 的本地化字符串。 /// @@ -221,7 +221,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FillLocalListeningPort", resourceCulture); } } - + /// /// 查找类似 Please enter the password. 的本地化字符串。 /// @@ -230,7 +230,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FillPassword", resourceCulture); } } - + /// /// 查找类似 Please enter the address. 的本地化字符串。 /// @@ -239,7 +239,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FillServerAddress", resourceCulture); } } - + /// /// 查找类似 Please browse to import Configuration configuration 的本地化字符串。 /// @@ -248,7 +248,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FillServerAddressCustom", resourceCulture); } } - + /// /// 查找类似 Please enter the user ID. 的本地化字符串。 /// @@ -257,7 +257,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("FillUUID", resourceCulture); } } - + /// /// 查找类似 Transport 的本地化字符串。 /// @@ -266,7 +266,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("GbTransport", resourceCulture); } } - + /// /// 查找类似 This is not the correct configuration, please check 的本地化字符串。 /// @@ -275,7 +275,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("Incorrectconfiguration", resourceCulture); } } - + /// /// 查找类似 Initial Configuration 的本地化字符串。 /// @@ -284,7 +284,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("InitialConfiguration", resourceCulture); } } - + /// /// 查找类似 Please do not use the insecure HTTP protocol subscription address 的本地化字符串。 /// @@ -293,7 +293,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("InsecureUrlProtocol", resourceCulture); } } - + /// /// 查找类似 Invalid address (URL) 的本地化字符串。 /// @@ -302,7 +302,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("InvalidUrlTip", resourceCulture); } } - + /// /// 查找类似 {0} {1} already up to date. 的本地化字符串。 /// @@ -311,7 +311,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("IsLatestCore", resourceCulture); } } - + /// /// 查找类似 {0} {1} already up to date. 的本地化字符串。 /// @@ -320,7 +320,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("IsLatestN", resourceCulture); } } - + /// /// 查找类似 LAN 的本地化字符串。 /// @@ -329,7 +329,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LabLAN", resourceCulture); } } - + /// /// 查找类似 Local 的本地化字符串。 /// @@ -338,7 +338,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LabLocal", resourceCulture); } } - + /// /// 查找类似 Invalid backup file 的本地化字符串。 /// @@ -347,7 +347,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LocalRestoreInvalidZipTips", resourceCulture); } } - + /// /// 查找类似 Address 的本地化字符串。 /// @@ -356,7 +356,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvAddress", resourceCulture); } } - + /// /// 查找类似 Automatic update interval (minutes) 的本地化字符串。 /// @@ -365,7 +365,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvAutoUpdateInterval", resourceCulture); } } - + /// /// 查找类似 Convert target type 的本地化字符串。 /// @@ -374,7 +374,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvConvertTarget", resourceCulture); } } - + /// /// 查找类似 Please leave blank if no conversion is required 的本地化字符串。 /// @@ -383,7 +383,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvConvertTargetTip", resourceCulture); } } - + /// /// 查找类似 Count 的本地化字符串。 /// @@ -392,7 +392,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvCount", resourceCulture); } } - + /// /// 查找类似 Custom icon 的本地化字符串。 /// @@ -401,7 +401,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvCustomIcon", resourceCulture); } } - + /// /// 查找类似 Customize the rule-set of sing-box 的本地化字符串。 /// @@ -410,7 +410,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvCustomRulesetPath4Singbox", resourceCulture); } } - + /// /// 查找类似 Enable update 的本地化字符串。 /// @@ -419,7 +419,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvEnabled", resourceCulture); } } - + /// /// 查找类似 Security 的本地化字符串。 /// @@ -428,7 +428,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvEncryptionMethod", resourceCulture); } } - + /// /// 查找类似 Remarks regular filter 的本地化字符串。 /// @@ -437,7 +437,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvFilter", resourceCulture); } } - + /// /// 查找类似 Remarks Memo 的本地化字符串。 /// @@ -446,7 +446,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvMemo", resourceCulture); } } - + /// /// 查找类似 More URLs, separated by commas; Subscription conversion will be invalid 的本地化字符串。 /// @@ -455,7 +455,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvMoreUrl", resourceCulture); } } - + /// /// 查找类似 Next proxy Configuration remarks 的本地化字符串。 /// @@ -464,7 +464,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvNextProfile", resourceCulture); } } - + /// /// 查找类似 Port 的本地化字符串。 /// @@ -473,7 +473,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvPort", resourceCulture); } } - + /// /// 查找类似 Previous proxy Configuration remarks 的本地化字符串。 /// @@ -482,7 +482,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvPrevProfile", resourceCulture); } } - + /// /// 查找类似 Please make sure the Configuration remarks exist and are unique 的本地化字符串。 /// @@ -491,7 +491,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvPrevProfileTip", resourceCulture); } } - + /// /// 查找类似 Remarks 的本地化字符串。 /// @@ -500,7 +500,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvRemarks", resourceCulture); } } - + /// /// 查找类似 Type 的本地化字符串。 /// @@ -509,7 +509,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvServiceType", resourceCulture); } } - + /// /// 查找类似 Sort 的本地化字符串。 /// @@ -518,7 +518,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvSort", resourceCulture); } } - + /// /// 查找类似 Subs group 的本地化字符串。 /// @@ -527,7 +527,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvSubscription", resourceCulture); } } - + /// /// 查找类似 Delay (ms) 的本地化字符串。 /// @@ -536,7 +536,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvTestDelay", resourceCulture); } } - + /// /// 查找类似 Speed (M/s) 的本地化字符串。 /// @@ -545,7 +545,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvTestSpeed", resourceCulture); } } - + /// /// 查找类似 TLS 的本地化字符串。 /// @@ -554,7 +554,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvTLS", resourceCulture); } } - + /// /// 查找类似 Download traffic today 的本地化字符串。 /// @@ -563,7 +563,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvTodayDownloadDataAmount", resourceCulture); } } - + /// /// 查找类似 Upload traffic today 的本地化字符串。 /// @@ -572,7 +572,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvTodayUploadDataAmount", resourceCulture); } } - + /// /// 查找类似 Total download traffic 的本地化字符串。 /// @@ -581,7 +581,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvTotalDownloadDataAmount", resourceCulture); } } - + /// /// 查找类似 Total upload traffic 的本地化字符串。 /// @@ -590,7 +590,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvTotalUploadDataAmount", resourceCulture); } } - + /// /// 查找类似 Transport 的本地化字符串。 /// @@ -599,7 +599,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvTransportProtocol", resourceCulture); } } - + /// /// 查找类似 URL (optional) 的本地化字符串。 /// @@ -608,7 +608,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvUrl", resourceCulture); } } - + /// /// 查找类似 User Agent 的本地化字符串。 /// @@ -617,7 +617,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvUserAgent", resourceCulture); } } - + /// /// 查找类似 WebDAV Check 的本地化字符串。 /// @@ -626,7 +626,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvWebDavCheck", resourceCulture); } } - + /// /// 查找类似 Remote folder name (optional) 的本地化字符串。 /// @@ -635,7 +635,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvWebDavDirName", resourceCulture); } } - + /// /// 查找类似 WebDAV Password 的本地化字符串。 /// @@ -644,7 +644,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvWebDavPassword", resourceCulture); } } - + /// /// 查找类似 WebDAV URL 的本地化字符串。 /// @@ -653,7 +653,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvWebDavUrl", resourceCulture); } } - + /// /// 查找类似 WebDAV User Name 的本地化字符串。 /// @@ -662,7 +662,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("LvWebDavUserName", resourceCulture); } } - + /// /// 查找类似 Add [Anytls] Configuration 的本地化字符串。 /// @@ -671,7 +671,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddAnytlsServer", resourceCulture); } } - + /// /// 查找类似 Add a custom configuration Configuration 的本地化字符串。 /// @@ -680,7 +680,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddCustomServer", resourceCulture); } } - + /// /// 查找类似 Add [HTTP] Configuration 的本地化字符串。 /// @@ -689,7 +689,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddHttpServer", resourceCulture); } } - + /// /// 查找类似 Add [Hysteria2] Configuration 的本地化字符串。 /// @@ -698,7 +698,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddHysteria2Server", resourceCulture); } } - + /// /// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。 /// @@ -707,7 +707,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddServerViaClipboard", resourceCulture); } } - + /// /// 查找类似 Scan QR code in the image 的本地化字符串。 /// @@ -716,7 +716,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddServerViaImage", resourceCulture); } } - + /// /// 查找类似 Scan QR code on the screen (Ctrl+S) 的本地化字符串。 /// @@ -725,7 +725,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddServerViaScan", resourceCulture); } } - + /// /// 查找类似 Add [Shadowsocks] Configuration 的本地化字符串。 /// @@ -734,7 +734,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddShadowsocksServer", resourceCulture); } } - + /// /// 查找类似 Add [SOCKS] Configuration 的本地化字符串。 /// @@ -743,7 +743,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddSocksServer", resourceCulture); } } - + /// /// 查找类似 Add [Trojan] Configuration 的本地化字符串。 /// @@ -752,7 +752,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddTrojanServer", resourceCulture); } } - + /// /// 查找类似 Add [TUIC] Configuration 的本地化字符串。 /// @@ -761,7 +761,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddTuicServer", resourceCulture); } } - + /// /// 查找类似 Add [VLESS] Configuration 的本地化字符串。 /// @@ -770,7 +770,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddVlessServer", resourceCulture); } } - + /// /// 查找类似 Add [VMess] Configuration 的本地化字符串。 /// @@ -779,7 +779,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddVmessServer", resourceCulture); } } - + /// /// 查找类似 Add [WireGuard] Configuration 的本地化字符串。 /// @@ -788,7 +788,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuAddWireguardServer", resourceCulture); } } - + /// /// 查找类似 Backup and Restore 的本地化字符串。 /// @@ -797,7 +797,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuBackupAndRestore", resourceCulture); } } - + /// /// 查找类似 Check Update 的本地化字符串。 /// @@ -806,7 +806,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuCheckUpdate", resourceCulture); } } - + /// /// 查找类似 Clear all service statistics 的本地化字符串。 /// @@ -815,7 +815,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuClearServerStatistics", resourceCulture); } } - + /// /// 查找类似 Close 的本地化字符串。 /// @@ -824,7 +824,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuClose", resourceCulture); } } - + /// /// 查找类似 Close Connection 的本地化字符串。 /// @@ -833,7 +833,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuConnectionClose", resourceCulture); } } - + /// /// 查找类似 Close All Connections 的本地化字符串。 /// @@ -842,7 +842,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuConnectionCloseAll", resourceCulture); } } - + /// /// 查找类似 Copy proxy command to clipboard 的本地化字符串。 /// @@ -851,7 +851,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuCopyProxyCmdToClipboard", resourceCulture); } } - + /// /// 查找类似 Clone selected Configuration 的本地化字符串。 /// @@ -860,7 +860,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuCopyServer", resourceCulture); } } - + /// /// 查找类似 DNS Settings 的本地化字符串。 /// @@ -869,7 +869,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuDNSSetting", resourceCulture); } } - + /// /// 查找类似 Edit Configuration (Ctrl+D) 的本地化字符串。 /// @@ -878,7 +878,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuEditServer", resourceCulture); } } - + /// /// 查找类似 Exit 的本地化字符串。 /// @@ -887,7 +887,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuExit", resourceCulture); } } - + /// /// 查找类似 Are you sure you want to exit? 的本地化字符串。 /// @@ -896,7 +896,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuExitTips", resourceCulture); } } - + /// /// 查找类似 Export selected Configuration for complete configuration 的本地化字符串。 /// @@ -905,7 +905,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuExport2ClientConfig", resourceCulture); } } - + /// /// 查找类似 Export selected Configuration for complete configuration to clipboard 的本地化字符串。 /// @@ -914,7 +914,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuExport2ClientConfigClipboard", resourceCulture); } } - + /// /// 查找类似 Export Share Link to Clipboard (Ctrl+C) 的本地化字符串。 /// @@ -923,7 +923,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuExport2ShareUrl", resourceCulture); } } - + /// /// 查找类似 Export Base64-encoded Share Links to Clipboard 的本地化字符串。 /// @@ -932,7 +932,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuExport2ShareUrlBase64", resourceCulture); } } - + /// /// 查找类似 Export Configuration 的本地化字符串。 /// @@ -941,7 +941,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuExportConfig", resourceCulture); } } - + /// /// 查找类似 Full Config Template Setting 的本地化字符串。 /// @@ -950,7 +950,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuFullConfigTemplate", resourceCulture); } } - + /// /// 查找类似 Global Hotkey Setting 的本地化字符串。 /// @@ -959,7 +959,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuGlobalHotkeySetting", resourceCulture); } } - + /// /// 查找类似 Help 的本地化字符串。 /// @@ -968,7 +968,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuHelp", resourceCulture); } } - + /// /// 查找类似 Import Rules From Clipboard 的本地化字符串。 /// @@ -977,7 +977,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuImportRulesFromClipboard", resourceCulture); } } - + /// /// 查找类似 Import Rules From File 的本地化字符串。 /// @@ -986,7 +986,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuImportRulesFromFile", resourceCulture); } } - + /// /// 查找类似 Import Rules From Subscription URL 的本地化字符串。 /// @@ -995,7 +995,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuImportRulesFromUrl", resourceCulture); } } - + /// /// 查找类似 Backup to local 的本地化字符串。 /// @@ -1004,7 +1004,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuLocalBackup", resourceCulture); } } - + /// /// 查找类似 Local 的本地化字符串。 /// @@ -1013,7 +1013,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuLocalBackupAndRestore", resourceCulture); } } - + /// /// 查找类似 Restore from local 的本地化字符串。 /// @@ -1022,7 +1022,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuLocalRestore", resourceCulture); } } - + /// /// 查找类似 One-click multi-test latency and speed (Ctrl+E) 的本地化字符串。 /// @@ -1031,7 +1031,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuMixedTestServer", resourceCulture); } } - + /// /// 查找类似 Direct 的本地化字符串。 /// @@ -1040,7 +1040,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuModeDirect", resourceCulture); } } - + /// /// 查找类似 Global 的本地化字符串。 /// @@ -1049,7 +1049,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuModeGlobal", resourceCulture); } } - + /// /// 查找类似 Do not change 的本地化字符串。 /// @@ -1058,7 +1058,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuModeNothing", resourceCulture); } } - + /// /// 查找类似 Rule 的本地化字符串。 /// @@ -1067,7 +1067,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuModeRule", resourceCulture); } } - + /// /// 查找类似 Move to bottom (B) 的本地化字符串。 /// @@ -1076,7 +1076,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuMoveBottom", resourceCulture); } } - + /// /// 查找类似 Down (D) 的本地化字符串。 /// @@ -1085,7 +1085,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuMoveDown", resourceCulture); } } - + /// /// 查找类似 Move up and down 的本地化字符串。 /// @@ -1094,7 +1094,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuMoveTo", resourceCulture); } } - + /// /// 查找类似 Move to group 的本地化字符串。 /// @@ -1103,7 +1103,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuMoveToGroup", resourceCulture); } } - + /// /// 查找类似 Move to top (T) 的本地化字符串。 /// @@ -1112,7 +1112,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuMoveTop", resourceCulture); } } - + /// /// 查找类似 Up (U) 的本地化字符串。 /// @@ -1121,7 +1121,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuMoveUp", resourceCulture); } } - + /// /// 查找类似 Clear all 的本地化字符串。 /// @@ -1130,7 +1130,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuMsgViewClear", resourceCulture); } } - + /// /// 查找类似 Copy (Ctrl+C) 的本地化字符串。 /// @@ -1139,7 +1139,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuMsgViewCopy", resourceCulture); } } - + /// /// 查找类似 Copy all 的本地化字符串。 /// @@ -1148,7 +1148,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuMsgViewCopyAll", resourceCulture); } } - + /// /// 查找类似 Select all (Ctrl+A) 的本地化字符串。 /// @@ -1157,7 +1157,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuMsgViewSelectAll", resourceCulture); } } - + /// /// 查找类似 Open the storage location 的本地化字符串。 /// @@ -1166,7 +1166,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuOpenTheFileLocation", resourceCulture); } } - + /// /// 查找类似 Option Setting 的本地化字符串。 /// @@ -1175,7 +1175,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuOptionSetting", resourceCulture); } } - + /// /// 查找类似 Auto column width adjustment 的本地化字符串。 /// @@ -1184,7 +1184,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuProfileAutofitColumnWidth", resourceCulture); } } - + /// /// 查找类似 Promotion 的本地化字符串。 /// @@ -1193,7 +1193,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuPromotion", resourceCulture); } } - + /// /// 查找类似 Latency Test 的本地化字符串。 /// @@ -1202,7 +1202,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuProxiesDelaytest", resourceCulture); } } - + /// /// 查找类似 Part Node Latency Test 的本地化字符串。 /// @@ -1211,7 +1211,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuProxiesDelaytestPart", resourceCulture); } } - + /// /// 查找类似 Refresh Proxies 的本地化字符串。 /// @@ -1220,7 +1220,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuProxiesReload", resourceCulture); } } - + /// /// 查找类似 Select active node (Enter) 的本地化字符串。 /// @@ -1229,7 +1229,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuProxiesSelectActivity", resourceCulture); } } - + /// /// 查找类似 Test Configurations real delay (Ctrl+R) 的本地化字符串。 /// @@ -1238,7 +1238,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRealPingServer", resourceCulture); } } - + /// /// 查找类似 Restart as Administrator 的本地化字符串。 /// @@ -1247,7 +1247,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRebootAsAdmin", resourceCulture); } } - + /// /// 查找类似 Regional presets setting 的本地化字符串。 /// @@ -1256,7 +1256,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRegionalPresets", resourceCulture); } } - + /// /// 查找类似 Default 的本地化字符串。 /// @@ -1265,7 +1265,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRegionalPresetsDefault", resourceCulture); } } - + /// /// 查找类似 Iran 的本地化字符串。 /// @@ -1274,7 +1274,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRegionalPresetsIran", resourceCulture); } } - + /// /// 查找类似 Russia 的本地化字符串。 /// @@ -1283,7 +1283,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRegionalPresetsRussia", resourceCulture); } } - + /// /// 查找类似 Reload 的本地化字符串。 /// @@ -1292,7 +1292,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuReload", resourceCulture); } } - + /// /// 查找类似 Backup to remote (WebDAV) 的本地化字符串。 /// @@ -1301,7 +1301,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRemoteBackup", resourceCulture); } } - + /// /// 查找类似 Remote (WebDAV) 的本地化字符串。 /// @@ -1310,7 +1310,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRemoteBackupAndRestore", resourceCulture); } } - + /// /// 查找类似 Restore from remote (WebDAV) 的本地化字符串。 /// @@ -1319,7 +1319,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRemoteRestore", resourceCulture); } } - + /// /// 查找类似 Remove duplicate Configurations 的本地化字符串。 /// @@ -1328,7 +1328,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRemoveDuplicateServer", resourceCulture); } } - + /// /// 查找类似 Remove invalid by test results 的本地化字符串。 /// @@ -1337,7 +1337,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRemoveInvalidServerResult", resourceCulture); } } - + /// /// 查找类似 Remove selected Configurations (Delete) 的本地化字符串。 /// @@ -1346,7 +1346,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRemoveServer", resourceCulture); } } - + /// /// 查找类似 Routing 的本地化字符串。 /// @@ -1355,7 +1355,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRouting", resourceCulture); } } - + /// /// 查找类似 Add 的本地化字符串。 /// @@ -1364,7 +1364,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRoutingAdvancedAdd", resourceCulture); } } - + /// /// 查找类似 Import Rules 的本地化字符串。 /// @@ -1373,7 +1373,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRoutingAdvancedImportRules", resourceCulture); } } - + /// /// 查找类似 Remove selected (Delete) 的本地化字符串。 /// @@ -1382,7 +1382,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRoutingAdvancedRemove", resourceCulture); } } - + /// /// 查找类似 Set as active rule (Enter) 的本地化字符串。 /// @@ -1391,7 +1391,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRoutingAdvancedSetDefault", resourceCulture); } } - + /// /// 查找类似 Routing Rule Details Setting 的本地化字符串。 /// @@ -1400,7 +1400,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRoutingRuleDetailsSetting", resourceCulture); } } - + /// /// 查找类似 Rule Settings 的本地化字符串。 /// @@ -1409,7 +1409,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRoutingRuleSetting", resourceCulture); } } - + /// /// 查找类似 Routing Setting 的本地化字符串。 /// @@ -1418,7 +1418,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRoutingSetting", resourceCulture); } } - + /// /// 查找类似 Add Rule 的本地化字符串。 /// @@ -1427,7 +1427,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRuleAdd", resourceCulture); } } - + /// /// 查找类似 Export Selected Rules 的本地化字符串。 /// @@ -1436,7 +1436,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRuleExportSelected", resourceCulture); } } - + /// /// 查找类似 Rule List 的本地化字符串。 /// @@ -1445,7 +1445,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRuleList", resourceCulture); } } - + /// /// 查找类似 Rule mode 的本地化字符串。 /// @@ -1454,7 +1454,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRulemode", resourceCulture); } } - + /// /// 查找类似 Remove Rule (Delete) 的本地化字符串。 /// @@ -1463,7 +1463,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuRuleRemove", resourceCulture); } } - + /// /// 查找类似 Select all (Ctrl+A) 的本地化字符串。 /// @@ -1472,7 +1472,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSelectAll", resourceCulture); } } - + /// /// 查找类似 Configurations 的本地化字符串。 /// @@ -1481,7 +1481,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuServers", resourceCulture); } } - + /// /// 查找类似 Multi-Configuration to custom configuration 的本地化字符串。 /// @@ -1490,7 +1490,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSetDefaultMultipleServer", resourceCulture); } } - + /// /// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。 /// @@ -1499,7 +1499,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSetDefaultMultipleServerSingBoxLeastPing", resourceCulture); } } - + /// /// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。 /// @@ -1508,7 +1508,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSetDefaultMultipleServerXrayLeastLoad", resourceCulture); } } - + /// /// 查找类似 Multi-Configuration LeastPing by Xray 的本地化字符串。 /// @@ -1517,7 +1517,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSetDefaultMultipleServerXrayLeastPing", resourceCulture); } } - + /// /// 查找类似 Multi-Configuration Random by Xray 的本地化字符串。 /// @@ -1526,7 +1526,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSetDefaultMultipleServerXrayRandom", resourceCulture); } } - + /// /// 查找类似 Multi-Configuration RoundRobin by Xray 的本地化字符串。 /// @@ -1535,7 +1535,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSetDefaultMultipleServerXrayRoundRobin", resourceCulture); } } - + /// /// 查找类似 Set as active Configuration (Enter) 的本地化字符串。 /// @@ -1544,7 +1544,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSetDefaultServer", resourceCulture); } } - + /// /// 查找类似 Settings 的本地化字符串。 /// @@ -1553,7 +1553,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSetting", resourceCulture); } } - + /// /// 查找类似 Share Configuration (Ctrl+F) 的本地化字符串。 /// @@ -1562,7 +1562,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuShareServer", resourceCulture); } } - + /// /// 查找类似 Show or hide the main window 的本地化字符串。 /// @@ -1571,7 +1571,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuShowOrHideMainWindow", resourceCulture); } } - + /// /// 查找类似 Sort by test result 的本地化字符串。 /// @@ -1580,7 +1580,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSortServerResult", resourceCulture); } } - + /// /// 查找类似 Test Configurations download speed (Ctrl+T) 的本地化字符串。 /// @@ -1589,7 +1589,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSpeedServer", resourceCulture); } } - + /// /// 查找类似 Add 的本地化字符串。 /// @@ -1598,7 +1598,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSubAdd", resourceCulture); } } - + /// /// 查找类似 Delete 的本地化字符串。 /// @@ -1607,7 +1607,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSubDelete", resourceCulture); } } - + /// /// 查找类似 Edit 的本地化字符串。 /// @@ -1616,7 +1616,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSubEdit", resourceCulture); } } - + /// /// 查找类似 Update current subscription without proxy 的本地化字符串。 /// @@ -1625,7 +1625,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSubGroupUpdate", resourceCulture); } } - + /// /// 查找类似 Update current subscription with proxy 的本地化字符串。 /// @@ -1634,7 +1634,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSubGroupUpdateViaProxy", resourceCulture); } } - + /// /// 查找类似 Subscription Group 的本地化字符串。 /// @@ -1643,7 +1643,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSubscription", resourceCulture); } } - + /// /// 查找类似 Subscription group settings 的本地化字符串。 /// @@ -1652,7 +1652,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSubSetting", resourceCulture); } } - + /// /// 查找类似 Share 的本地化字符串。 /// @@ -1661,7 +1661,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSubShare", resourceCulture); } } - + /// /// 查找类似 Update subscriptions without proxy 的本地化字符串。 /// @@ -1670,7 +1670,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSubUpdate", resourceCulture); } } - + /// /// 查找类似 Update subscriptions with proxy 的本地化字符串。 /// @@ -1679,7 +1679,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSubUpdateViaProxy", resourceCulture); } } - + /// /// 查找类似 System proxy 的本地化字符串。 /// @@ -1688,7 +1688,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSystemproxy", resourceCulture); } } - + /// /// 查找类似 Clear system proxy 的本地化字符串。 /// @@ -1697,7 +1697,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSystemProxyClear", resourceCulture); } } - + /// /// 查找类似 Do not change system proxy 的本地化字符串。 /// @@ -1706,7 +1706,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSystemProxyNothing", resourceCulture); } } - + /// /// 查找类似 PAC mode 的本地化字符串。 /// @@ -1715,7 +1715,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSystemProxyPac", resourceCulture); } } - + /// /// 查找类似 Set system proxy 的本地化字符串。 /// @@ -1724,7 +1724,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuSystemProxySet", resourceCulture); } } - + /// /// 查找类似 Test Configurations with tcping (Ctrl+O) 的本地化字符串。 /// @@ -1733,7 +1733,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuTcpingServer", resourceCulture); } } - + /// /// 查找类似 By test result 的本地化字符串。 /// @@ -1742,7 +1742,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuTestServerResult", resourceCulture); } } - + /// /// 查找类似 {0} Website 的本地化字符串。 /// @@ -1751,7 +1751,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("menuWebsiteItem", resourceCulture); } } - + /// /// 查找类似 Downloaded GeoFile: {0} successfully 的本地化字符串。 /// @@ -1760,7 +1760,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgDownloadGeoFileSuccessfully", resourceCulture); } } - + /// /// 查找类似 Downloaded Core successfully 的本地化字符串。 /// @@ -1769,7 +1769,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgDownloadV2rayCoreSuccessfully", resourceCulture); } } - + /// /// 查找类似 Failed to import subscription content 的本地化字符串。 /// @@ -1778,7 +1778,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgFailedImportSubscription", resourceCulture); } } - + /// /// 查找类似 Filter, supports regular expressions 的本地化字符串。 /// @@ -1787,7 +1787,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgFilterTitle", resourceCulture); } } - + /// /// 查找类似 Got subscription content successfully 的本地化字符串。 /// @@ -1796,7 +1796,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgGetSubscriptionSuccessfully", resourceCulture); } } - + /// /// 查找类似 Information 的本地化字符串。 /// @@ -1805,7 +1805,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgInformationTitle", resourceCulture); } } - + /// /// 查找类似 Please enter the URL 的本地化字符串。 /// @@ -1814,7 +1814,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgNeedUrl", resourceCulture); } } - + /// /// 查找类似 No valid subscriptions set 的本地化字符串。 /// @@ -1823,7 +1823,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgNoValidSubscription", resourceCulture); } } - + /// /// 查找类似 Resolved {0} successfully 的本地化字符串。 /// @@ -1832,7 +1832,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgParsingSuccessfully", resourceCulture); } } - + /// /// 查找类似 Configuration filter, press Enter to execute 的本地化字符串。 /// @@ -1841,7 +1841,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgServerTitle", resourceCulture); } } - + /// /// 查找类似 Updates are not enabled, skip this subscription 的本地化字符串。 /// @@ -1850,7 +1850,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgSkipSubscriptionUpdate", resourceCulture); } } - + /// /// 查找类似 Started getting subscriptions 的本地化字符串。 /// @@ -1859,7 +1859,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgStartGettingSubscriptions", resourceCulture); } } - + /// /// 查找类似 Started updating {0}... 的本地化字符串。 /// @@ -1868,7 +1868,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgStartUpdating", resourceCulture); } } - + /// /// 查找类似 Invalid subscription content 的本地化字符串。 /// @@ -1877,7 +1877,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgSubscriptionDecodingFailed", resourceCulture); } } - + /// /// 查找类似 Unpacking... 的本地化字符串。 /// @@ -1886,7 +1886,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgUnpacking", resourceCulture); } } - + /// /// 查找类似 Subscription update ended 的本地化字符串。 /// @@ -1895,7 +1895,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgUpdateSubscriptionEnd", resourceCulture); } } - + /// /// 查找类似 Subscription update started 的本地化字符串。 /// @@ -1904,7 +1904,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgUpdateSubscriptionStart", resourceCulture); } } - + /// /// 查找类似 Updated Core successfully 的本地化字符串。 /// @@ -1913,7 +1913,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgUpdateV2rayCoreSuccessfully", resourceCulture); } } - + /// /// 查找类似 Updated Core successfully! Restarting service... 的本地化字符串。 /// @@ -1922,7 +1922,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("MsgUpdateV2rayCoreSuccessfullyMore", resourceCulture); } } - + /// /// 查找类似 Successful operation. Click the settings menu to reboot the app. 的本地化字符串。 /// @@ -1931,7 +1931,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("NeedRebootTips", resourceCulture); } } - + /// /// 查找类似 Non-VMess or SS protocol 的本地化字符串。 /// @@ -1940,7 +1940,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("NonvmessOrssProtocol", resourceCulture); } } - + /// /// 查找类似 The Core file (file name: {1}) was not found under the folder ({0}), please download and put it in the folder, download address: {2} 的本地化字符串。 /// @@ -1949,7 +1949,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("NotFoundCore", resourceCulture); } } - + /// /// 查找类似 Not run as Admin 的本地化字符串。 /// @@ -1958,7 +1958,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("NotRunAsAdmin", resourceCulture); } } - + /// /// 查找类似 Scan completed, no valid QR code found 的本地化字符串。 /// @@ -1967,7 +1967,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("NoValidQRcodeFound", resourceCulture); } } - + /// /// 查找类似 Operation failed, please check and retry 的本地化字符串。 /// @@ -1976,7 +1976,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("OperationFailed", resourceCulture); } } - + /// /// 查找类似 Operation successful 的本地化字符串。 /// @@ -1985,7 +1985,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("OperationSuccess", resourceCulture); } } - + /// /// 查找类似 Please fill Remarks 的本地化字符串。 /// @@ -1994,7 +1994,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("PleaseFillRemarks", resourceCulture); } } - + /// /// 查找类似 Please select the encryption method 的本地化字符串。 /// @@ -2003,7 +2003,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("PleaseSelectEncryption", resourceCulture); } } - + /// /// 查找类似 Please select a protocol 的本地化字符串。 /// @@ -2012,7 +2012,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("PleaseSelectProtocol", resourceCulture); } } - + /// /// 查找类似 Please select rules 的本地化字符串。 /// @@ -2021,7 +2021,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("PleaseSelectRules", resourceCulture); } } - + /// /// 查找类似 Please select the Configuration first 的本地化字符串。 /// @@ -2030,7 +2030,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("PleaseSelectServer", resourceCulture); } } - + /// /// 查找类似 Global hotkey {0} registration failed, reason: {1} 的本地化字符串。 /// @@ -2039,7 +2039,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("RegisterGlobalHotkeyFailed", resourceCulture); } } - + /// /// 查找类似 Global hotkey {0} registered successfully 的本地化字符串。 /// @@ -2048,7 +2048,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("RegisterGlobalHotkeySuccessfully", resourceCulture); } } - + /// /// 查找类似 Configurations deduplication completed. Old: {0}, New: {1}. 的本地化字符串。 /// @@ -2057,7 +2057,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("RemoveDuplicateServerResult", resourceCulture); } } - + /// /// 查找类似 Removed {0} invalid test results. 的本地化字符串。 /// @@ -2066,7 +2066,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("RemoveInvalidServerResultTip", resourceCulture); } } - + /// /// 查找类似 Are you sure you want to remove the rules? 的本地化字符串。 /// @@ -2075,7 +2075,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("RemoveRules", resourceCulture); } } - + /// /// 查找类似 Are you sure you want to remove the Configuration? 的本地化字符串。 /// @@ -2084,7 +2084,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("RemoveServer", resourceCulture); } } - + /// /// 查找类似 {0}, one of the required fields. 的本地化字符串。 /// @@ -2093,7 +2093,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("RoutingRuleDetailRequiredTips", resourceCulture); } } - + /// /// 查找类似 Run as Admin 的本地化字符串。 /// @@ -2102,7 +2102,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("RunAsAdmin", resourceCulture); } } - + /// /// 查找类似 The client configuration file is saved at: {0} 的本地化字符串。 /// @@ -2111,7 +2111,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SaveClientConfigurationIn", resourceCulture); } } - + /// /// 查找类似 {0} : {1}/s↑ | {2}/s↓ 的本地化字符串。 /// @@ -2120,7 +2120,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SpeedDisplayText", resourceCulture); } } - + /// /// 查找类似 Testing... 的本地化字符串。 /// @@ -2129,7 +2129,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("Speedtesting", resourceCulture); } } - + /// /// 查找类似 Test completed 的本地化字符串。 /// @@ -2138,7 +2138,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SpeedtestingCompleted", resourceCulture); } } - + /// /// 查找类似 Skip test 的本地化字符串。 /// @@ -2147,7 +2147,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SpeedtestingSkip", resourceCulture); } } - + /// /// 查找类似 Test terminating... 的本地化字符串。 /// @@ -2156,7 +2156,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SpeedtestingStop", resourceCulture); } } - + /// /// 查找类似 Starting retesting failed parts, {0} remaining. Press ESC to terminate... 的本地化字符串。 /// @@ -2165,7 +2165,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SpeedtestingTestFailedPart", resourceCulture); } } - + /// /// 查找类似 Waiting for testing (press ESC to terminate)... 的本地化字符串。 /// @@ -2174,7 +2174,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SpeedtestingWait", resourceCulture); } } - + /// /// 查找类似 Starting service ({0})... 的本地化字符串。 /// @@ -2183,7 +2183,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("StartService", resourceCulture); } } - + /// /// 查找类似 For group please leave blank here 的本地化字符串。 /// @@ -2192,7 +2192,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SubUrlTips", resourceCulture); } } - + /// /// 查找类似 Configuration successful. {0} 的本地化字符串。 /// @@ -2201,7 +2201,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SuccessfulConfiguration", resourceCulture); } } - + /// /// 查找类似 Custom configuration Configuration imported successfully 的本地化字符串。 /// @@ -2210,7 +2210,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SuccessfullyImportedCustomServer", resourceCulture); } } - + /// /// 查找类似 {0} Configurations have been imported from clipboard 的本地化字符串。 /// @@ -2219,7 +2219,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SuccessfullyImportedServerViaClipboard", resourceCulture); } } - + /// /// 查找类似 Successfully scanned and imported the shared link 的本地化字符串。 /// @@ -2228,7 +2228,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SuccessfullyImportedServerViaScan", resourceCulture); } } - + /// /// 查找类似 Incorrect password, please try again. 的本地化字符串。 /// @@ -2237,7 +2237,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("SudoIncorrectPasswordTip", resourceCulture); } } - + /// /// 查找类似 Add Common DNS Hosts 的本地化字符串。 /// @@ -2246,7 +2246,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbAddCommonDNSHosts", resourceCulture); } } - + /// /// 查找类似 Do Not Add Non-Proxy Protocol Outbound 的本地化字符串。 /// @@ -2255,7 +2255,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbAddProxyProtocolOutboundOnly", resourceCulture); } } - + /// /// 查找类似 Address 的本地化字符串。 /// @@ -2264,7 +2264,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbAddress", resourceCulture); } } - + /// /// 查找类似 Allow Insecure 的本地化字符串。 /// @@ -2273,7 +2273,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbAllowInsecure", resourceCulture); } } - + /// /// 查找类似 ALPN 的本地化字符串。 /// @@ -2282,7 +2282,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbAlpn", resourceCulture); } } - + /// /// 查找类似 Alter ID 的本地化字符串。 /// @@ -2291,7 +2291,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbAlterId", resourceCulture); } } - + /// /// 查找类似 Apply to Proxy Domains Only 的本地化字符串。 /// @@ -2300,7 +2300,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbApplyProxyDomainsOnly", resourceCulture); } } - + /// /// 查找类似 Auto refresh 的本地化字符串。 /// @@ -2309,7 +2309,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbAutoRefresh", resourceCulture); } } - + /// /// 查找类似 Auto scroll to end 的本地化字符串。 /// @@ -2318,7 +2318,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbAutoScrollToEnd", resourceCulture); } } - + /// /// 查找类似 Domain, IP, process are auto-sorted when saving 的本地化字符串。 /// @@ -2327,7 +2327,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbAutoSort", resourceCulture); } } - + /// /// 查找类似 Block SVCB and HTTPS Queries 的本地化字符串。 /// @@ -2336,7 +2336,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbBlockSVCBHTTPSQueries", resourceCulture); } } - + /// /// 查找类似 Prevent domain-based routing rules from failing 的本地化字符串。 /// @@ -2345,7 +2345,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbBlockSVCBHTTPSQueriesTips", resourceCulture); } } - + /// /// 查找类似 Browse 的本地化字符串。 /// @@ -2354,7 +2354,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbBrowse", resourceCulture); } } - + /// /// 查找类似 Cancel 的本地化字符串。 /// @@ -2363,7 +2363,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbCancel", resourceCulture); } } - + /// /// 查找类似 Clear system proxy 的本地化字符串。 /// @@ -2372,7 +2372,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbClearSystemProxy", resourceCulture); } } - + /// /// 查找类似 Confirm 的本地化字符串。 /// @@ -2381,7 +2381,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbConfirm", resourceCulture); } } - + /// /// 查找类似 Connections 的本地化字符串。 /// @@ -2390,7 +2390,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbConnections", resourceCulture); } } - + /// /// 查找类似 Core Type 的本地化字符串。 /// @@ -2399,7 +2399,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbCoreType", resourceCulture); } } - + /// /// 查找类似 Enable Custom DNS 的本地化字符串。 /// @@ -2408,7 +2408,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbCustomDNSEnable", resourceCulture); } } - + /// /// 查找类似 Custom DNS Enabled, This Page's Settings Invalid 的本地化字符串。 /// @@ -2417,7 +2417,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbCustomDNSEnabledPageInvalid", resourceCulture); } } - + /// /// 查找类似 Display GUI 的本地化字符串。 /// @@ -2426,7 +2426,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbDisplayGUI", resourceCulture); } } - + /// /// 查找类似 Display Log 的本地化字符串。 /// @@ -2435,7 +2435,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbDisplayLog", resourceCulture); } } - + /// /// 查找类似 DNS Hosts: ("domain1 ip1 ip2" per line) 的本地化字符串。 /// @@ -2444,7 +2444,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbDNSHostsConfig", resourceCulture); } } - + /// /// 查找类似 Supports DNS Object; Click to view documentation 的本地化字符串。 /// @@ -2453,7 +2453,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbDnsObjectDoc", resourceCulture); } } - + /// /// 查找类似 Please fill in DNS Structure, Click to view the document 的本地化字符串。 /// @@ -2462,7 +2462,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbDnsSingboxObjectDoc", resourceCulture); } } - + /// /// 查找类似 Domain strategy 的本地化字符串。 /// @@ -2471,7 +2471,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbdomainStrategy", resourceCulture); } } - + /// /// 查找类似 sing-box domain strategy 的本地化字符串。 /// @@ -2480,7 +2480,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbdomainStrategy4Singbox", resourceCulture); } } - + /// /// 查找类似 Domestic DNS 的本地化字符串。 /// @@ -2489,7 +2489,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbDomesticDNS", resourceCulture); } } - + /// /// 查找类似 Edit 的本地化字符串。 /// @@ -2498,7 +2498,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbEdit", resourceCulture); } } - + /// /// 查找类似 Enable Tun 的本地化字符串。 /// @@ -2507,7 +2507,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbEnableTunAs", resourceCulture); } } - + /// /// 查找类似 FakeIP 的本地化字符串。 /// @@ -2516,7 +2516,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbFakeIP", resourceCulture); } } - + /// /// 查找类似 Fingerprint 的本地化字符串。 /// @@ -2525,7 +2525,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbFingerprint", resourceCulture); } } - + /// /// 查找类似 Flow 的本地化字符串。 /// @@ -2534,7 +2534,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbFlow5", resourceCulture); } } - + /// /// 查找类似 This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. 的本地化字符串。 /// @@ -2543,7 +2543,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbFullConfigTemplateDesc", resourceCulture); } } - + /// /// 查找类似 Enable Full Config Template 的本地化字符串。 /// @@ -2552,7 +2552,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbFullConfigTemplateEnable", resourceCulture); } } - + /// /// 查找类似 Global Hotkey Settings 的本地化字符串。 /// @@ -2561,7 +2561,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbGlobalHotkeySetting", resourceCulture); } } - + /// /// 查找类似 Set directly by pressing the keyboard; takes effect after restart 的本地化字符串。 /// @@ -2570,7 +2570,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbGlobalHotkeySettingTip", resourceCulture); } } - + /// /// 查找类似 Generate 的本地化字符串。 /// @@ -2579,7 +2579,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbGUID", resourceCulture); } } - + /// /// 查找类似 Camouflage type 的本地化字符串。 /// @@ -2588,7 +2588,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbHeaderType", resourceCulture); } } - + /// /// 查找类似 Congestion control 的本地化字符串。 /// @@ -2597,7 +2597,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbHeaderType8", resourceCulture); } } - + /// /// 查找类似 UUID(id) 的本地化字符串。 /// @@ -2606,7 +2606,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbId", resourceCulture); } } - + /// /// 查找类似 Password 的本地化字符串。 /// @@ -2615,7 +2615,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbId3", resourceCulture); } } - + /// /// 查找类似 Password(Optional) 的本地化字符串。 /// @@ -2624,7 +2624,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbId4", resourceCulture); } } - + /// /// 查找类似 UUID(id) 的本地化字符串。 /// @@ -2633,7 +2633,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbId5", resourceCulture); } } - + /// /// 查找类似 Address (IPv4, IPv6) 的本地化字符串。 /// @@ -2642,7 +2642,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbLocalAddress", resourceCulture); } } - + /// /// 查找类似 Mldsa65Verify 的本地化字符串。 /// @@ -2651,7 +2651,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbMldsa65Verify", resourceCulture); } } - + /// /// 查找类似 Transport protocol(network) 的本地化字符串。 /// @@ -2660,7 +2660,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbNetwork", resourceCulture); } } - + /// /// 查找类似 Do not change system proxy 的本地化字符串。 /// @@ -2669,7 +2669,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbNotChangeSystemProxy", resourceCulture); } } - + /// /// 查找类似 Path 的本地化字符串。 /// @@ -2678,7 +2678,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbPath", resourceCulture); } } - + /// /// 查找类似 obfs password 的本地化字符串。 /// @@ -2687,7 +2687,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbPath7", resourceCulture); } } - + /// /// 查找类似 Port 的本地化字符串。 /// @@ -2696,7 +2696,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbPort", resourceCulture); } } - + /// /// 查找类似 Configuration port range 的本地化字符串。 /// @@ -2705,7 +2705,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbPorts7", resourceCulture); } } - + /// /// 查找类似 Will cover the port, separate with commas (,) 的本地化字符串。 /// @@ -2714,7 +2714,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbPorts7Tips", resourceCulture); } } - + /// /// 查找类似 Socks port 的本地化字符串。 /// @@ -2723,7 +2723,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbPreSocksPort", resourceCulture); } } - + /// /// 查找类似 Custom config socks port 的本地化字符串。 /// @@ -2732,7 +2732,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbPreSocksPort4Sub", resourceCulture); } } - + /// /// 查找类似 Private Key 的本地化字符串。 /// @@ -2741,7 +2741,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbPrivateKey", resourceCulture); } } - + /// /// 查找类似 Proxies 的本地化字符串。 /// @@ -2750,7 +2750,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbProxies", resourceCulture); } } - + /// /// 查找类似 Public Key 的本地化字符串。 /// @@ -2759,7 +2759,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbPublicKey", resourceCulture); } } - + /// /// 查找类似 v2ray Full Config Template 的本地化字符串。 /// @@ -2768,7 +2768,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRayFullConfigTemplate", resourceCulture); } } - + /// /// 查找类似 Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document 的本地化字符串。 /// @@ -2777,7 +2777,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRayFullConfigTemplateDesc", resourceCulture); } } - + /// /// 查找类似 Alias (remarks) 的本地化字符串。 /// @@ -2786,7 +2786,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRemarks", resourceCulture); } } - + /// /// 查找类似 Remote DNS 的本地化字符串。 /// @@ -2795,7 +2795,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRemoteDNS", resourceCulture); } } - + /// /// 查找类似 Camouflage domain(host) 的本地化字符串。 /// @@ -2804,7 +2804,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRequestHost", resourceCulture); } } - + /// /// 查找类似 Reserved (2,3,4) 的本地化字符串。 /// @@ -2813,7 +2813,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbReserved", resourceCulture); } } - + /// /// 查找类似 Reset 的本地化字符串。 /// @@ -2822,7 +2822,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbReset", resourceCulture); } } - + /// /// 查找类似 socks: local port, socks2: second local port, socks3: LAN port 的本地化字符串。 /// @@ -2831,7 +2831,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRoutingInboundTagTips", resourceCulture); } } - + /// /// 查找类似 Domain 的本地化字符串。 /// @@ -2840,7 +2840,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRoutingRuleDomain", resourceCulture); } } - + /// /// 查找类似 IP or IP CIDR 的本地化字符串。 /// @@ -2849,7 +2849,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRoutingRuleIP", resourceCulture); } } - + /// /// 查找类似 Full process name (Tun mode) 的本地化字符串。 /// @@ -2858,7 +2858,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRoutingRuleProcess", resourceCulture); } } - + /// /// 查找类似 Pre-defined Rule Set List 的本地化字符串。 /// @@ -2867,7 +2867,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRoutingTabRuleList", resourceCulture); } } - + /// /// 查找类似 *Separate rules by commas (,); For a literal comma use <COMMA>; Prefix # to ignore a rule 的本地化字符串。 /// @@ -2876,7 +2876,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRoutingTips", resourceCulture); } } - + /// /// 查找类似 (Domain or IP or Proc Name) and Port and Protocol and Inbound Tag => Outbound Tag 的本地化字符串。 /// @@ -2885,7 +2885,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRuleMatchingTips", resourceCulture); } } - + /// /// 查找类似 Rule object Doc 的本地化字符串。 /// @@ -2894,7 +2894,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRuleobjectDoc", resourceCulture); } } - + /// /// 查找类似 Can fill in the configuration remarks, please make sure it exist and are unique 的本地化字符串。 /// @@ -2903,7 +2903,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbRuleOutboundTagTip", resourceCulture); } } - + /// /// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。 /// @@ -2912,7 +2912,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSBDirectResolveStrategy", resourceCulture); } } - + /// /// 查找类似 The sing-box DoH resolution server can be overwritten 的本地化字符串。 /// @@ -2921,7 +2921,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSBDoHOverride", resourceCulture); } } - + /// /// 查找类似 sing-box DoH Resolver Server 的本地化字符串。 /// @@ -2930,7 +2930,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSBDoHResolverServer", resourceCulture); } } - + /// /// 查找类似 Fallback DNS Resolution, Suggest IP 的本地化字符串。 /// @@ -2939,7 +2939,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSBFallbackDNSResolve", resourceCulture); } } - + /// /// 查找类似 sing-box Full Config Template 的本地化字符串。 /// @@ -2948,7 +2948,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSBFullConfigTemplate", resourceCulture); } } - + /// /// 查找类似 Add Outbound and Endpoint Config Only, Click to view the document 的本地化字符串。 /// @@ -2957,7 +2957,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSBFullConfigTemplateDesc", resourceCulture); } } - + /// /// 查找类似 Resolve Outbound Domains 的本地化字符串。 /// @@ -2966,7 +2966,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSBOutboundDomainResolve", resourceCulture); } } - + /// /// 查找类似 Outbound DNS Resolution (sing-box) 的本地化字符串。 /// @@ -2975,7 +2975,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSBOutboundsResolverDNS", resourceCulture); } } - + /// /// 查找类似 sing-box Remote Resolution Strategy 的本地化字符串。 /// @@ -2984,7 +2984,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSBRemoteResolveStrategy", resourceCulture); } } - + /// /// 查找类似 Encryption method (security) 的本地化字符串。 /// @@ -2993,7 +2993,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSecurity", resourceCulture); } } - + /// /// 查找类似 Encryption 的本地化字符串。 /// @@ -3002,7 +3002,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSecurity3", resourceCulture); } } - + /// /// 查找类似 User(Optional) 的本地化字符串。 /// @@ -3011,7 +3011,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSecurity4", resourceCulture); } } - + /// /// 查找类似 Encryption 的本地化字符串。 /// @@ -3020,7 +3020,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSecurity5", resourceCulture); } } - + /// /// 查找类似 Set system proxy 的本地化字符串。 /// @@ -3029,7 +3029,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSetSystemProxy", resourceCulture); } } - + /// /// 查找类似 Click to import default DNS config 的本地化字符串。 /// @@ -3038,7 +3038,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingDnsImportDefConfig", resourceCulture); } } - + /// /// 查找类似 Advanced proxy settings, protocol selection (optional) 的本地化字符串。 /// @@ -3047,7 +3047,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsAdvancedProtocol", resourceCulture); } } - + /// /// 查找类似 Allow connections from the LAN 的本地化字符串。 /// @@ -3056,7 +3056,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsAllowLAN", resourceCulture); } } - + /// /// 查找类似 Auto hide on startup 的本地化字符串。 /// @@ -3065,7 +3065,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsAutoHideStartup", resourceCulture); } } - + /// /// 查找类似 Automatic update interval for Geo files (hours) 的本地化字符串。 /// @@ -3074,7 +3074,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsAutoUpdateInterval", resourceCulture); } } - + /// /// 查找类似 Users in China region can ignore this item 的本地化字符串。 /// @@ -3083,7 +3083,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsChinaUserTip", resourceCulture); } } - + /// /// 查找类似 Color 的本地化字符串。 /// @@ -3092,7 +3092,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsColor", resourceCulture); } } - + /// /// 查找类似 Core: basic settings 的本地化字符串。 /// @@ -3101,7 +3101,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsCore", resourceCulture); } } - + /// /// 查找类似 V2ray DNS settings 的本地化字符串。 /// @@ -3110,7 +3110,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsCoreDns", resourceCulture); } } - + /// /// 查找类似 sing-box DNS settings 的本地化字符串。 /// @@ -3119,7 +3119,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsCoreDnsSingbox", resourceCulture); } } - + /// /// 查找类似 Core: KCP settings 的本地化字符串。 /// @@ -3128,7 +3128,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsCoreKcp", resourceCulture); } } - + /// /// 查找类似 Core Type settings 的本地化字符串。 /// @@ -3137,7 +3137,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsCoreType", resourceCulture); } } - + /// /// 查找类似 Font family (requires restart) 的本地化字符串。 /// @@ -3146,7 +3146,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsCurrentFontFamily", resourceCulture); } } - + /// /// 查找类似 Install the font to the system, select or fill in the font name, restart the settings 的本地化字符串。 /// @@ -3155,7 +3155,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsCurrentFontFamilyLinuxTip", resourceCulture); } } - + /// /// 查找类似 Copy the font TTF/TTC file to the directory gui Fonts; Reopen the settings window 的本地化字符串。 /// @@ -3164,7 +3164,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsCurrentFontFamilyTip", resourceCulture); } } - + /// /// 查找类似 Allow Insecure 的本地化字符串。 /// @@ -3173,7 +3173,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsDefAllowInsecure", resourceCulture); } } - + /// /// 查找类似 Default TLS fingerprint 的本地化字符串。 /// @@ -3182,7 +3182,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsDefFingerprint", resourceCulture); } } - + /// /// 查找类似 User-Agent 的本地化字符串。 /// @@ -3191,7 +3191,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsDefUserAgent", resourceCulture); } } - + /// /// 查找类似 This parameter is valid only for tcp/http and ws 的本地化字符串。 /// @@ -3200,7 +3200,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsDefUserAgentTips", resourceCulture); } } - + /// /// 查找类似 Sniffing type 的本地化字符串。 /// @@ -3209,7 +3209,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsDestOverride", resourceCulture); } } - + /// /// 查找类似 Display real-time speed (requires restart) 的本地化字符串。 /// @@ -3218,7 +3218,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsDisplayRealTimeSpeed", resourceCulture); } } - + /// /// 查找类似 Outbound DNS address 的本地化字符串。 /// @@ -3227,7 +3227,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsDomainDNSAddress", resourceCulture); } } - + /// /// 查找类似 Outbound Freedom domain Strategy 的本地化字符串。 /// @@ -3236,7 +3236,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsDomainStrategy4Freedom", resourceCulture); } } - + /// /// 查找类似 Default domain strategy for outbound 的本地化字符串。 /// @@ -3245,7 +3245,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsDomainStrategy4Out", resourceCulture); } } - + /// /// 查找类似 Double-clicking Configuration makes it active 的本地化字符串。 /// @@ -3254,7 +3254,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsDoubleClick2Activate", resourceCulture); } } - + /// /// 查找类似 Automatically adjust column width after subscription update 的本地化字符串。 /// @@ -3263,7 +3263,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsEnableAutoAdjustMainLvColWidth", resourceCulture); } } - + /// /// 查找类似 Enable cache file for sing-box (ruleset files) 的本地化字符串。 /// @@ -3272,7 +3272,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsEnableCacheFile4Sbox", resourceCulture); } } - + /// /// 查找类似 Check for pre-release updates 的本地化字符串。 /// @@ -3281,7 +3281,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsEnableCheckPreReleaseUpdate", resourceCulture); } } - + /// /// 查找类似 Enable sorting Configurations by drag-n-drop (requires restart) 的本地化字符串。 /// @@ -3290,7 +3290,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsEnableDragDropSort", resourceCulture); } } - + /// /// 查找类似 Enable additional Inbound 的本地化字符串。 /// @@ -3299,7 +3299,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsEnableExInbound", resourceCulture); } } - + /// /// 查找类似 Enable fragment 的本地化字符串。 /// @@ -3308,7 +3308,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsEnableFragment", resourceCulture); } } - + /// /// 查找类似 which conflicts with the group previous proxy 的本地化字符串。 /// @@ -3317,7 +3317,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsEnableFragmentTips", resourceCulture); } } - + /// /// 查找类似 Enable hardware acceleration (requires restart) 的本地化字符串。 /// @@ -3326,7 +3326,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsEnableHWA", resourceCulture); } } - + /// /// 查找类似 Enable IPv6 Address 的本地化字符串。 /// @@ -3335,7 +3335,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsEnableIPv6Address", resourceCulture); } } - + /// /// 查找类似 Updating subscription, only determining if remarks exist 的本地化字符串。 /// @@ -3344,7 +3344,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsEnableUpdateSubOnlyRemarksExist", resourceCulture); } } - + /// /// 查找类似 Exception 的本地化字符串。 /// @@ -3353,7 +3353,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsException", resourceCulture); } } - + /// /// 查找类似 Exclusions: Do not use proxy server for addresses beginning with the following. Use semicolon (;) to separate entries. 的本地化字符串。 /// @@ -3362,7 +3362,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsExceptionTip", resourceCulture); } } - + /// /// 查找类似 Exclusions: Do not use proxy server for the following addresses. Use comma (,) to separate entries. 的本地化字符串。 /// @@ -3371,7 +3371,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsExceptionTip2", resourceCulture); } } - + /// /// 查找类似 Font Size 的本地化字符串。 /// @@ -3380,7 +3380,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsFontSize", resourceCulture); } } - + /// /// 查找类似 Geo files source (optional) 的本地化字符串。 /// @@ -3389,7 +3389,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsGeoFilesSource", resourceCulture); } } - + /// /// 查找类似 Hide to tray when closing the window 的本地化字符串。 /// @@ -3398,7 +3398,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsHide2TrayWhenClose", resourceCulture); } } - + /// /// 查找类似 Hysteria Max bandwidth (Up/Down) 的本地化字符串。 /// @@ -3407,7 +3407,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsHysteriaBandwidth", resourceCulture); } } - + /// /// 查找类似 Current connection info test URL 的本地化字符串。 /// @@ -3416,7 +3416,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsIPAPIUrl", resourceCulture); } } - + /// /// 查找类似 Keep older entries when de-duplicating 的本地化字符串。 /// @@ -3425,7 +3425,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsKeepOlderDedupl", resourceCulture); } } - + /// /// 查找类似 Language (Restart) 的本地化字符串。 /// @@ -3434,7 +3434,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsLanguage", resourceCulture); } } - + /// /// 查找类似 System sudo password 的本地化字符串。 /// @@ -3443,7 +3443,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsLinuxSudoPassword", resourceCulture); } } - + /// /// 查找类似 The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart. 的本地化字符串。 /// @@ -3452,7 +3452,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsLinuxSudoPasswordTip", resourceCulture); } } - + /// /// 查找类似 Enable Log 的本地化字符串。 /// @@ -3461,7 +3461,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsLogEnabled", resourceCulture); } } - + /// /// 查找类似 Enable logging to file 的本地化字符串。 /// @@ -3470,7 +3470,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsLogEnabledToFile", resourceCulture); } } - + /// /// 查找类似 Log Level 的本地化字符串。 /// @@ -3479,7 +3479,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsLogLevel", resourceCulture); } } - + /// /// 查找类似 Main layout orientation (requires restart) 的本地化字符串。 /// @@ -3488,7 +3488,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsMainGirdOrientation", resourceCulture); } } - + /// /// 查找类似 The number of concurrent tests during multi-test 的本地化字符串。 /// @@ -3497,7 +3497,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsMixedConcurrencyCount", resourceCulture); } } - + /// /// 查找类似 sing-box Mux Protocol 的本地化字符串。 /// @@ -3506,7 +3506,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsMux4SboxProtocol", resourceCulture); } } - + /// /// 查找类似 Turn on Mux Multiplexing 的本地化字符串。 /// @@ -3515,7 +3515,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsMuxEnabled", resourceCulture); } } - + /// /// 查找类似 v2rayN settings 的本地化字符串。 /// @@ -3524,7 +3524,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsN", resourceCulture); } } - + /// /// 查找类似 New Port for LAN 的本地化字符串。 /// @@ -3533,7 +3533,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsNewPort4LAN", resourceCulture); } } - + /// /// 查找类似 Do not use proxy servers for local (intranet) addresses 的本地化字符串。 /// @@ -3542,7 +3542,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsNotProxyLocalAddress", resourceCulture); } } - + /// /// 查找类似 Auth pass 的本地化字符串。 /// @@ -3551,7 +3551,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsPass", resourceCulture); } } - + /// /// 查找类似 Custom DNS (multiple, separated by commas (,)) 的本地化字符串。 /// @@ -3560,7 +3560,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsRemoteDNS", resourceCulture); } } - + /// /// 查找类似 Route Only 的本地化字符串。 /// @@ -3569,7 +3569,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsRouteOnly", resourceCulture); } } - + /// /// 查找类似 Routing rules source (optional) 的本地化字符串。 /// @@ -3578,7 +3578,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsRoutingRulesSource", resourceCulture); } } - + /// /// 查找类似 Enable second mixed port 的本地化字符串。 /// @@ -3587,7 +3587,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsSecondLocalPortEnabled", resourceCulture); } } - + /// /// 查找类似 Set Win10 UWP Loopback 的本地化字符串。 /// @@ -3596,7 +3596,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsSetUWP", resourceCulture); } } - + /// /// 查找类似 Turn on Sniffing 的本地化字符串。 /// @@ -3605,7 +3605,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsSniffingEnabled", resourceCulture); } } - + /// /// 查找类似 Mixed Port 的本地化字符串。 /// @@ -3614,7 +3614,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsSocksPort", resourceCulture); } } - + /// /// 查找类似 Pac port = +3; Xray API port = +4; mihomo API port = +5; 的本地化字符串。 /// @@ -3623,7 +3623,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsSocksPortTip", resourceCulture); } } - + /// /// 查找类似 Speed Ping Test URL 的本地化字符串。 /// @@ -3632,7 +3632,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsSpeedPingTestUrl", resourceCulture); } } - + /// /// 查找类似 Speed Test Single Timeout Value 的本地化字符串。 /// @@ -3641,7 +3641,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsSpeedTestTimeout", resourceCulture); } } - + /// /// 查找类似 Speed Test URL 的本地化字符串。 /// @@ -3650,7 +3650,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsSpeedTestUrl", resourceCulture); } } - + /// /// 查找类似 sing-box ruleset files source (optional) 的本地化字符串。 /// @@ -3659,7 +3659,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsSrsFilesSource", resourceCulture); } } - + /// /// 查找类似 Start on boot 的本地化字符串。 /// @@ -3668,7 +3668,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsStartBoot", resourceCulture); } } - + /// /// 查找类似 Set this with admin privileges, get admin privileges after startup 的本地化字符串。 /// @@ -3677,7 +3677,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsStartBootTip", resourceCulture); } } - + /// /// 查找类似 Enable traffic statistics (requires restart) 的本地化字符串。 /// @@ -3686,7 +3686,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsStatistics", resourceCulture); } } - + /// /// 查找类似 Subscription conversion URL 的本地化字符串。 /// @@ -3695,7 +3695,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsSubConvert", resourceCulture); } } - + /// /// 查找类似 System proxy settings 的本地化字符串。 /// @@ -3704,7 +3704,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsSystemproxy", resourceCulture); } } - + /// /// 查找类似 Theme 的本地化字符串。 /// @@ -3713,7 +3713,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsTheme", resourceCulture); } } - + /// /// 查找类似 Enable Security Protocol TLS v1.3 (subscription/update) 的本地化字符串。 /// @@ -3722,7 +3722,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsTLS13", resourceCulture); } } - + /// /// 查找类似 Tray right-click menu Configurations display limit 的本地化字符串。 /// @@ -3731,7 +3731,35 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsTrayMenuServersLimit", resourceCulture); } } - + + /// + /// 查找类似 Auto Route 的本地化字符串。 + /// + public static string TbSettingsTunAutoRoute { + get { return ResourceManager.GetString("TbSettingsTunAutoRoute", resourceCulture); } + } + + /// + /// 查找类似 Strict Route 的本地化字符串。 + /// + public static string TbSettingsTunStrictRoute { + get { return ResourceManager.GetString("TbSettingsTunStrictRoute", resourceCulture); } + } + + /// + /// 查找类似 Stack 的本地化字符串。 + /// + public static string TbSettingsTunStack { + get { return ResourceManager.GetString("TbSettingsTunStack", resourceCulture); } + } + + /// + /// 查找类似 MTU 的本地化字符串。 + /// + public static string TbSettingsTunMtu { + get { return ResourceManager.GetString("TbSettingsTunMtu", resourceCulture); } + } + /// /// 查找类似 Tun Mode settings 的本地化字符串。 /// @@ -3740,7 +3768,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsTunMode", resourceCulture); } } - + /// /// 查找类似 Enable UDP 的本地化字符串。 /// @@ -3749,7 +3777,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsUdpEnabled", resourceCulture); } } - + /// /// 查找类似 Auth user 的本地化字符串。 /// @@ -3758,7 +3786,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsUser", resourceCulture); } } - + /// /// 查找类似 Use System Hosts 的本地化字符串。 /// @@ -3767,7 +3795,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsUseSystemHosts", resourceCulture); } } - + /// /// 查找类似 Set Upstream Proxy Tag 的本地化字符串。 /// @@ -3776,7 +3804,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSetUpstreamProxyDetour", resourceCulture); } } - + /// /// 查找类似 Short Id 的本地化字符串。 /// @@ -3785,7 +3813,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbShortId", resourceCulture); } } - + /// /// 查找类似 SNI 的本地化字符串。 /// @@ -3794,7 +3822,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSNI", resourceCulture); } } - + /// /// 查找类似 Sorting 的本地化字符串。 /// @@ -3803,7 +3831,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSorting", resourceCulture); } } - + /// /// 查找类似 Chain 的本地化字符串。 /// @@ -3812,7 +3840,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingChain", resourceCulture); } } - + /// /// 查找类似 Default 的本地化字符串。 /// @@ -3821,7 +3849,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingDefault", resourceCulture); } } - + /// /// 查找类似 Delay 的本地化字符串。 /// @@ -3830,7 +3858,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingDelay", resourceCulture); } } - + /// /// 查找类似 Download Speed 的本地化字符串。 /// @@ -3839,7 +3867,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingDownSpeed", resourceCulture); } } - + /// /// 查找类似 Download Traffic 的本地化字符串。 /// @@ -3848,7 +3876,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingDownTraffic", resourceCulture); } } - + /// /// 查找类似 Host 的本地化字符串。 /// @@ -3857,7 +3885,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingHost", resourceCulture); } } - + /// /// 查找类似 Name 的本地化字符串。 /// @@ -3866,7 +3894,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingName", resourceCulture); } } - + /// /// 查找类似 Network 的本地化字符串。 /// @@ -3875,7 +3903,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingNetwork", resourceCulture); } } - + /// /// 查找类似 Time 的本地化字符串。 /// @@ -3884,7 +3912,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingTime", resourceCulture); } } - + /// /// 查找类似 Type 的本地化字符串。 /// @@ -3893,7 +3921,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingType", resourceCulture); } } - + /// /// 查找类似 Upload Speed 的本地化字符串。 /// @@ -3902,7 +3930,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingUpSpeed", resourceCulture); } } - + /// /// 查找类似 Upload Traffic 的本地化字符串。 /// @@ -3911,7 +3939,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSortingUpTraffic", resourceCulture); } } - + /// /// 查找类似 Spider X 的本地化字符串。 /// @@ -3920,7 +3948,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSpiderX", resourceCulture); } } - + /// /// 查找类似 TLS 的本地化字符串。 /// @@ -3929,7 +3957,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbStreamSecurity", resourceCulture); } } - + /// /// 查找类似 PAC mode 的本地化字符串。 /// @@ -3938,7 +3966,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSystemProxyPac", resourceCulture); } } - + /// /// 查找类似 Validate Regional Domain IPs 的本地化字符串。 /// @@ -3947,7 +3975,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbValidateDirectExpectedIPs", resourceCulture); } } - + /// /// 查找类似 When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs 的本地化字符串。 /// @@ -3956,7 +3984,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbValidateDirectExpectedIPsDesc", resourceCulture); } } - + /// /// 查找类似 xray Freedom Resolution Strategy 的本地化字符串。 /// @@ -3965,7 +3993,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbXrayFreedomResolveStrategy", resourceCulture); } } - + /// /// 查找类似 The delay: {0} ms, {1} 的本地化字符串。 /// @@ -3974,7 +4002,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TestMeOutput", resourceCulture); } } - + /// /// 查找类似 Advanced DNS Settings 的本地化字符串。 /// @@ -3983,7 +4011,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("ThAdvancedDNSSettings", resourceCulture); } } - + /// /// 查找类似 Basic DNS Settings 的本地化字符串。 /// @@ -3992,7 +4020,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("ThBasicDNSSettings", resourceCulture); } } - + /// /// 查找类似 Active 的本地化字符串。 /// @@ -4001,7 +4029,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TipActiveServer", resourceCulture); } } - + /// /// 查找类似 Routing setting has changed 的本地化字符串。 /// @@ -4010,7 +4038,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TipChangeRouting", resourceCulture); } } - + /// /// 查找类似 System proxy setting has changed 的本地化字符串。 /// @@ -4019,7 +4047,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TipChangeSystemProxy", resourceCulture); } } - + /// /// 查找类似 Please turn off when there is an abnormal disconnection 的本地化字符串。 /// @@ -4028,7 +4056,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TipDisplayLog", resourceCulture); } } - + /// /// 查找类似 *Default value tcp 的本地化字符串。 /// @@ -4037,7 +4065,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TipNetwork", resourceCulture); } } - + /// /// 查找类似 * After setting this value, a socks service will be started using Xray/sing-box(Tun) to provide functions such as speed display 的本地化字符串。 /// @@ -4046,7 +4074,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TipPreSocksPort", resourceCulture); } } - + /// /// 查找类似 XHTTP Extra raw JSON, format: { XHTTP Object } 的本地化字符串。 /// @@ -4055,7 +4083,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportExtraTip", resourceCulture); } } - + /// /// 查找类似 *tcp camouflage type 的本地化字符串。 /// @@ -4064,7 +4092,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportHeaderTypeTip1", resourceCulture); } } - + /// /// 查找类似 *kcp camouflage type 的本地化字符串。 /// @@ -4073,7 +4101,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportHeaderTypeTip2", resourceCulture); } } - + /// /// 查找类似 *QUIC camouflage type 的本地化字符串。 /// @@ -4082,7 +4110,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportHeaderTypeTip3", resourceCulture); } } - + /// /// 查找类似 *grpc mode 的本地化字符串。 /// @@ -4091,7 +4119,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportHeaderTypeTip4", resourceCulture); } } - + /// /// 查找类似 *xhttp mode 的本地化字符串。 /// @@ -4100,7 +4128,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportHeaderTypeTip5", resourceCulture); } } - + /// /// 查找类似 *ws/http upgrade/xhttp path 的本地化字符串。 /// @@ -4109,7 +4137,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportPathTip1", resourceCulture); } } - + /// /// 查找类似 *h2 path 的本地化字符串。 /// @@ -4118,7 +4146,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportPathTip2", resourceCulture); } } - + /// /// 查找类似 *QUIC key/KCP seed 的本地化字符串。 /// @@ -4127,7 +4155,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportPathTip3", resourceCulture); } } - + /// /// 查找类似 *grpc service name 的本地化字符串。 /// @@ -4136,7 +4164,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportPathTip4", resourceCulture); } } - + /// /// 查找类似 *kcp seed 的本地化字符串。 /// @@ -4145,7 +4173,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportPathTip5", resourceCulture); } } - + /// /// 查找类似 *http host separated by commas (,) 的本地化字符串。 /// @@ -4154,7 +4182,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportRequestHostTip1", resourceCulture); } } - + /// /// 查找类似 *ws/http upgrade/xhttp host 的本地化字符串。 /// @@ -4163,7 +4191,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportRequestHostTip2", resourceCulture); } } - + /// /// 查找类似 *h2 host separated by commas (,) 的本地化字符串。 /// @@ -4172,7 +4200,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportRequestHostTip3", resourceCulture); } } - + /// /// 查找类似 *QUIC security 的本地化字符串。 /// @@ -4181,7 +4209,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportRequestHostTip4", resourceCulture); } } - + /// /// 查找类似 *grpc Authority 的本地化字符串。 /// @@ -4190,7 +4218,7 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TransportRequestHostTip5", resourceCulture); } } - + /// /// 查找类似 Upgrade App does not exist 的本地化字符串。 /// diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 6873e1c827..cd15f976b9 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1,17 +1,17 @@ - @@ -1059,6 +1059,18 @@ لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند + + مسیریابی خودکار + + + مسیریابی سخت‌گیرانه + + + پشته شبکه + + + MTU + فعال سازی additional Inbound @@ -1497,4 +1509,4 @@ This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. - \ No newline at end of file + diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx index d90ac86cba..8f451953bd 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1,17 +1,17 @@ - @@ -1059,6 +1059,18 @@ Kérjük, győződjön meg arról, hogy a konfigurációs megjegyzések léteznek és egyediek + + Automatikus útválasztás + + + Szigorú útválasztás + + + Hálózati verem + + + MTU + További bejövő engedélyezése @@ -1497,4 +1509,4 @@ This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. - \ No newline at end of file + diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx index 2b2e3abd4c..104a39259a 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1,17 +1,17 @@ - @@ -1059,6 +1059,18 @@ Please make sure the Configuration remarks exist and are unique + + Auto Route + + + Strict Route + + + Stack + + + MTU + Enable additional Inbound @@ -1497,4 +1509,4 @@ This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. - \ No newline at end of file + diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx index bb1f618455..2b02e3edf4 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1,17 +1,17 @@ - @@ -1059,6 +1059,18 @@ Убедитесь, что примечание существует и является уникальным + + Автоматическая маршрутизация + + + Строгая маршрутизация + + + Сетевой стек + + + MTU + Включить дополнительный входящий канал @@ -1497,4 +1509,4 @@ Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную. - \ No newline at end of file + diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index b10ff6414c..ff9035d3ce 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1,17 +1,17 @@ - @@ -1056,6 +1056,18 @@ 请确保配置文件别名存在并唯一 + + 自动路由 + + + 严格路由 + + + 协议栈 + + + MTU + 启用额外监听端口 @@ -1494,4 +1506,4 @@ 此功能供高级用户和有特殊需求的用户使用。 启用此功能后,将忽略 Core 的基础设置,DNS 设置 ,路由设置。你需要保证系统代理的端口和流量统计等功能的配置正确,一切都由你来设置。 - \ No newline at end of file + diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index b3738082c4..34a90e2115 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1,17 +1,17 @@ - @@ -1056,6 +1056,18 @@ 請確保設定檔別名存在並且唯一 + + 自動路由 + + + 嚴格路由 + + + 協定堆疊 + + + MTU + 啟用額外偵聽連接埠 @@ -1494,4 +1506,4 @@ This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. - \ No newline at end of file + diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs index 7c41418c0b..7ee82af10e 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs @@ -133,7 +133,7 @@ public partial class CoreConfigSingboxService(Config config) foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) { continue; } @@ -381,7 +381,7 @@ public partial class CoreConfigSingboxService(Config config) var proxyProfiles = new List(); foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) { continue; } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index 5c48c01433..3f03b93c01 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -372,7 +372,7 @@ public partial class CoreConfigSingboxService var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); string? prevOutboundTag = null; if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom) + && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)) { prevOutboundTag = $"prev-{Global.ProxyTag}"; var prevServer = await GenServer(prevNode); @@ -463,7 +463,7 @@ public partial class CoreConfigSingboxService { var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom) + && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -558,7 +558,7 @@ public partial class CoreConfigSingboxService // Next proxy var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom) + && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)) { nextOutbound ??= await GenServer(nextNode); nextOutbound.tag = outbound.tag; diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs index 69492d9b65..24804c50cb 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs @@ -338,7 +338,7 @@ public partial class CoreConfigSingboxService var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); if (node == null - || node.ConfigType == EConfigType.Custom) + || !Global.SingboxSupportConfigType.Contains(node.ConfigType)) { return Global.ProxyTag; } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs index 7f1ada5d9b..b61485807e 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs @@ -110,11 +110,7 @@ public partial class CoreConfigV2rayService(Config config) var proxyProfiles = new List(); foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) - { - continue; - } - if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) + if (!Global.XraySupportConfigType.Contains(it.ConfigType)) { continue; } @@ -250,7 +246,7 @@ public partial class CoreConfigV2rayService(Config config) foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!Global.XraySupportConfigType.Contains(it.ConfigType)) { continue; } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs index ddb47c99c9..9faf4df340 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs @@ -187,9 +187,9 @@ public partial class CoreConfigV2rayService foreach (var profile in new[] { subItem.PrevProfile, subItem.NextProfile }) { var profileNode = await AppManager.Instance.GetProfileItemViaRemarks(profile); - if (profileNode is not null && - profileNode.ConfigType is not (EConfigType.Custom or EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) && - Utils.IsDomain(profileNode.Address)) + if (profileNode is not null + && Global.XraySupportConfigType.Contains(profileNode.ConfigType) + && Utils.IsDomain(profileNode.Address)) { directDomainList.Add(profileNode.Address); } @@ -217,11 +217,11 @@ public partial class CoreConfigV2rayService AddDnsServers(directDNSAddress, directGeositeList); AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs); - var useDirectDns = rules?.LastOrDefault() is { } lastRule && - lastRule.OutboundTag == Global.DirectTag && - (lastRule.Port == "0-65535" || - lastRule.Network == "tcp,udp" || - lastRule.Ip?.Contains("0.0.0.0/0") == true); + var useDirectDns = rules?.LastOrDefault() is { } lastRule + && lastRule.OutboundTag == Global.DirectTag + && (lastRule.Port == "0-65535" + || lastRule.Network == "tcp,udp" + || lastRule.Ip?.Contains("0.0.0.0/0") == true); var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress; v2rayConfig.dns.servers.AddRange(defaultDnsServers); @@ -391,9 +391,7 @@ public partial class CoreConfigV2rayService // Previous proxy var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom - && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC + && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType) && Utils.IsDomain(prevNode.Address)) { domainList.Add(prevNode.Address); @@ -402,9 +400,7 @@ public partial class CoreConfigV2rayService // Next proxy var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom - && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC + && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType) && Utils.IsDomain(nextNode.Address)) { domainList.Add(nextNode.Address); diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index a40f2d7314..11e8a8fa7c 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -529,10 +529,7 @@ public partial class CoreConfigV2rayService var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); string? prevOutboundTag = null; if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom - && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC - && prevNode.ConfigType != EConfigType.Anytls) + && Global.XraySupportConfigType.Contains(prevNode.ConfigType)) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -605,10 +602,7 @@ public partial class CoreConfigV2rayService { var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom - && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC - && prevNode.ConfigType != EConfigType.Anytls) + && Global.XraySupportConfigType.Contains(prevNode.ConfigType)) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -675,10 +669,7 @@ public partial class CoreConfigV2rayService // Next proxy var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom - && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC - && nextNode.ConfigType != EConfigType.Anytls) + && Global.XraySupportConfigType.Contains(nextNode.ConfigType)) { if (nextOutbound == null) { diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs index fe5d64dc7f..1cb46bf21d 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs @@ -126,10 +126,7 @@ public partial class CoreConfigV2rayService var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); if (node == null - || node.ConfigType == EConfigType.Custom - || node.ConfigType == EConfigType.Hysteria2 - || node.ConfigType == EConfigType.TUIC - || node.ConfigType == EConfigType.Anytls) + || !Global.XraySupportConfigType.Contains(node.ConfigType)) { return Global.ProxyTag; } diff --git a/v2rayn/v2rayN/ServiceLib/Services/SpeedtestService.cs b/v2rayn/v2rayN/ServiceLib/Services/SpeedtestService.cs index 67b9bc94f2..002a625186 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/SpeedtestService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/SpeedtestService.cs @@ -356,8 +356,8 @@ public class SpeedtestService private List> GetTestBatchItem(List lstSelected, int pageSize) { List> lstTest = new(); - var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)).ToList(); - var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls).ToList(); + var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList(); + var lst2 = lstSelected.Where(t => Global.SingboxSupportConfigType.Contains(t.ConfigType) && !Global.XraySupportConfigType.Contains(t.ConfigType)).ToList(); for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++) { diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Handler/HotkeyHandler.cs b/v2rayn/v2rayN/v2rayN.Desktop/Manager/HotkeyManager.cs similarity index 91% rename from v2rayn/v2rayN/v2rayN.Desktop/Handler/HotkeyHandler.cs rename to v2rayn/v2rayN/v2rayN.Desktop/Manager/HotkeyManager.cs index a8d257c8a7..5ce6ff6076 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Handler/HotkeyHandler.cs +++ b/v2rayn/v2rayN/v2rayN.Desktop/Manager/HotkeyManager.cs @@ -4,12 +4,12 @@ using Avalonia.ReactiveUI; using Avalonia.Win32.Input; using GlobalHotKeys; -namespace v2rayN.Desktop.Handler; +namespace v2rayN.Desktop.Manager; -public sealed class HotkeyHandler +public sealed class HotkeyManager { - private static readonly Lazy _instance = new(() => new()); - public static HotkeyHandler Instance = _instance.Value; + private static readonly Lazy _instance = new(() => new()); + public static HotkeyManager Instance = _instance.Value; private readonly Dictionary _hotkeyTriggerDic = new(); private HotKeyManager? _hotKeyManager; diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 56cc82a0f4..9cb462d13d 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -523,7 +523,7 @@ Grid.Column="0" Margin="{StaticResource Margin4}" VerticalAlignment="Center" - Text="Mtu" /> + Text="{x:Static resx:ResUI.TbSettingsTunMtu}" /> HotkeyHandler.Instance.IsPause = false; + HotkeyManager.Instance.IsPause = true; + this.Closing += (s, e) => HotkeyManager.Instance.IsPause = false; btnCancel.Click += (s, e) => this.Close(); this.WhenActivated(disposables => diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs b/v2rayn/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs index e808e54e17..0633e9afec 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs +++ b/v2rayn/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs @@ -13,7 +13,7 @@ using ServiceLib.Manager; using Splat; using v2rayN.Desktop.Base; using v2rayN.Desktop.Common; -using v2rayN.Desktop.Handler; +using v2rayN.Desktop.Manager; namespace v2rayN.Desktop.Views; @@ -143,7 +143,7 @@ public partial class MainWindow : WindowBase this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}"; ThreadPool.RegisterWaitForSingleObject(Program.ProgramStarted, OnProgramStarted, null, -1, false); - HotkeyHandler.Instance.Init(_config, OnHotkeyHandler); + HotkeyManager.Instance.Init(_config, OnHotkeyHandler); } else { @@ -235,7 +235,7 @@ public partial class MainWindow : WindowBase StorageUI(); if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - HotkeyHandler.Instance.Dispose(); + HotkeyManager.Instance.Dispose(); desktop.Shutdown(); } break; diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index ba174a6261..df8ac68a1b 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -736,7 +736,7 @@ Grid.Column="0" Margin="{StaticResource Margin4}" VerticalAlignment="Center" - Text="Auto Route" /> + Text="{x:Static resx:ResUI.TbSettingsTunAutoRoute}" /> + Text="{x:Static resx:ResUI.TbSettingsTunStrictRoute}" /> + Text="{x:Static resx:ResUI.TbSettingsTunStack}" /> + Text="{x:Static resx:ResUI.TbSettingsTunMtu}" /> _instance = new(() => new()); - public static HotkeyHandler Instance = _instance.Value; + private static readonly Lazy _instance = new(() => new()); + public static HotkeyManager Instance = _instance.Value; private const int WmHotkey = 0x0312; private readonly Dictionary> _hotkeyTriggerDic = new(); @@ -21,7 +21,7 @@ public sealed class HotkeyHandler public event Action? HotkeyTriggerEvent; - public HotkeyHandler() + public HotkeyManager() { ComponentDispatcher.ThreadPreprocessMessage += OnThreadPreProcessMessage; Init(); @@ -51,7 +51,7 @@ public sealed class HotkeyHandler modifiers |= KeyModifiers.Alt; } - key = (key << 16) | (int)modifiers; + key = key << 16 | (int)modifiers; if (!_hotkeyTriggerDic.ContainsKey(key)) { _hotkeyTriggerDic.Add(key, new() { item.EGlobalHotkey }); @@ -77,7 +77,7 @@ public sealed class HotkeyHandler Application.Current?.Dispatcher.Invoke(() => { - isSuccess = RegisterHotKey(IntPtr.Zero, _hotkeyCode, hotkeyInfo.fsModifiers, hotkeyInfo.vKey); + isSuccess = RegisterHotKey(nint.Zero, _hotkeyCode, hotkeyInfo.fsModifiers, hotkeyInfo.vKey); }); foreach (var name in hotkeyInfo.Names) { @@ -101,7 +101,7 @@ public sealed class HotkeyHandler { Application.Current?.Dispatcher.Invoke(() => { - UnregisterHotKey(IntPtr.Zero, hotkey); + UnregisterHotKey(nint.Zero, hotkey); }); } Init(); @@ -111,7 +111,7 @@ public sealed class HotkeyHandler private (int fsModifiers, int vKey, string hotkeyStr, List Names) GetHotkeyInfo(int hotkeyCode) { var fsModifiers = hotkeyCode & 0xffff; - var vKey = (hotkeyCode >> 16) & 0xffff; + var vKey = hotkeyCode >> 16 & 0xffff; var hotkeyStr = new StringBuilder(); var names = new List(); @@ -174,10 +174,10 @@ public sealed class HotkeyHandler } [DllImport("user32.dll", SetLastError = true)] - private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc); + private static extern bool RegisterHotKey(nint hWnd, int id, int fsModifiers, int vlc); [DllImport("user32.dll", SetLastError = true)] - private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + private static extern bool UnregisterHotKey(nint hWnd, int id); [Flags] private enum KeyModifiers diff --git a/v2rayn/v2rayN/v2rayN/Handler/WindowsHandler.cs b/v2rayn/v2rayN/v2rayN/Manager/WindowsManager.cs similarity index 89% rename from v2rayn/v2rayN/v2rayN/Handler/WindowsHandler.cs rename to v2rayn/v2rayN/v2rayN/Manager/WindowsManager.cs index 2e4ba33ba0..8e8b116d57 100644 --- a/v2rayn/v2rayN/v2rayN/Handler/WindowsHandler.cs +++ b/v2rayn/v2rayN/v2rayN/Manager/WindowsManager.cs @@ -1,13 +1,14 @@ using System.Drawing; using System.IO; using System.Windows.Media.Imaging; +using v2rayN.Manager; -namespace v2rayN.Handler; +namespace v2rayN.Manager; -public sealed class WindowsHandler +public sealed class WindowsManager { - private static readonly Lazy instance = new(() => new()); - public static WindowsHandler Instance => instance.Value; + private static readonly Lazy instance = new(() => new()); + public static WindowsManager Instance => instance.Value; private static readonly string _tag = "WindowsHandler"; public async Task GetNotifyIcon(Config config) @@ -97,8 +98,8 @@ public sealed class WindowsHandler public void RegisterGlobalHotkey(Config config, Action handler, Action? update) { - HotkeyHandler.Instance.UpdateViewEvent += update; - HotkeyHandler.Instance.HotkeyTriggerEvent += handler; - HotkeyHandler.Instance.Load(); + HotkeyManager.Instance.UpdateViewEvent += update; + HotkeyManager.Instance.HotkeyTriggerEvent += handler; + HotkeyManager.Instance.Load(); } } diff --git a/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml index acea0c28be..7276a1fcdf 100644 --- a/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -696,7 +696,7 @@ Margin="{StaticResource Margin4}" VerticalAlignment="Center" Style="{StaticResource ToolbarTextBlock}" - Text="Mtu" /> + Text="{x:Static resx:ResUI.TbSettingsTunMtu}" /> HotkeyHandler.Instance.IsPause = false; + HotkeyManager.Instance.IsPause = true; + this.Closing += (s, e) => HotkeyManager.Instance.IsPause = false; this.WhenActivated(disposables => { diff --git a/v2rayn/v2rayN/v2rayN/Views/MainWindow.xaml b/v2rayn/v2rayN/v2rayN/Views/MainWindow.xaml index 14fe809a8e..b12b17ecfa 100644 --- a/v2rayn/v2rayN/v2rayN/Views/MainWindow.xaml +++ b/v2rayn/v2rayN/v2rayN/Views/MainWindow.xaml @@ -15,6 +15,7 @@ Height="700" MinWidth="900" x:TypeArguments="vms:MainWindowViewModel" + Icon="/Resources/v2rayN.ico" ResizeMode="CanResizeWithGrip" ShowInTaskbar="True" Style="{StaticResource WindowGlobal}" diff --git a/v2rayn/v2rayN/v2rayN/Views/MainWindow.xaml.cs b/v2rayn/v2rayN/v2rayN/Views/MainWindow.xaml.cs index 95f3b218c7..37c6bba7c3 100644 --- a/v2rayn/v2rayN/v2rayN/Views/MainWindow.xaml.cs +++ b/v2rayn/v2rayN/v2rayN/Views/MainWindow.xaml.cs @@ -10,7 +10,7 @@ using MaterialDesignThemes.Wpf; using ReactiveUI; using ServiceLib.Manager; using Splat; -using v2rayN.Handler; +using v2rayN.Manager; namespace v2rayN.Views; @@ -143,7 +143,7 @@ public partial class MainWindow } AddHelpMenuItem(); - WindowsHandler.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, null); + WindowsManager.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, null); MessageBus.Current.Listen(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI); } @@ -344,7 +344,7 @@ public partial class MainWindow if (Application.Current?.MainWindow is Window window) { - var bytes = QRCodeHelper.CaptureScreen(window); + var bytes = QRCodeUtils.CaptureScreen(window); await ViewModel?.ScanScreenResult(bytes); } diff --git a/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index e3e38992b0..6b92ba2db3 100644 --- a/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -430,7 +430,7 @@ Margin="{StaticResource Margin8}" VerticalAlignment="Center" Style="{StaticResource ToolbarTextBlock}" - Text="mtu" /> + Text="{x:Static resx:ResUI.TbSettingsTunMtu}" /> + Text="{x:Static resx:ResUI.TbSettingsTunAutoRoute}" /> + Text="{x:Static resx:ResUI.TbSettingsTunStrictRoute}" /> + Text="{x:Static resx:ResUI.TbSettingsTunStack}" /> + Text="{x:Static resx:ResUI.TbSettingsTunMtu}" /> { - tbNotify.Icon = await WindowsHandler.Instance.GetNotifyIcon(_config); - Application.Current.MainWindow.Icon = WindowsHandler.Instance.GetAppIcon(_config); + tbNotify.Icon = await WindowsManager.Instance.GetNotifyIcon(_config); + Application.Current.MainWindow.Icon = WindowsManager.Instance.GetAppIcon(_config); }), DispatcherPriority.Normal); break; diff --git a/v2rayn/v2rayN/v2rayN/Views/SubSettingWindow.xaml.cs b/v2rayn/v2rayN/v2rayN/Views/SubSettingWindow.xaml.cs index 797f3cdc1e..c8e694d412 100644 --- a/v2rayn/v2rayN/v2rayN/Views/SubSettingWindow.xaml.cs +++ b/v2rayn/v2rayN/v2rayN/Views/SubSettingWindow.xaml.cs @@ -70,7 +70,7 @@ public partial class SubSettingWindow { return; } - var img = QRCodeHelper.GetQRCode(url); + var img = QRCodeUtils.GetQRCode(url); var dialog = new QrcodeView() { imgQrcode = { Source = img }, diff --git a/xray-core/common/session/session.go b/xray-core/common/session/session.go index aad31cdfeb..7e0c036973 100644 --- a/xray-core/common/session/session.go +++ b/xray-core/common/session/session.go @@ -46,7 +46,7 @@ type Inbound struct { Name string // User is the user that authenticates for the inbound. May be nil if the protocol allows anonymous traffic. User *protocol.MemoryUser - // VlessRoute is the user-sent VLESS UUID's last byte. + // VlessRoute is the user-sent VLESS UUID's 7th<<8 | 8th bytes. VlessRoute net.Port // Used by splice copy. Conn is actually internet.Connection. May be nil. Conn net.Conn diff --git a/xray-core/features/routing/context.go b/xray-core/features/routing/context.go index d876e5bb1f..6b38d175c2 100644 --- a/xray-core/features/routing/context.go +++ b/xray-core/features/routing/context.go @@ -41,7 +41,7 @@ type Context interface { // GetUser returns the user email from the connection content, if exists. GetUser() string - // GetVlessRoute returns the user-sent VLESS UUID's last byte, if exists. + // GetVlessRoute returns the user-sent VLESS UUID's 7th<<8 | 8th bytes, if exists. GetVlessRoute() net.Port // GetAttributes returns extra attributes from the conneciont content. diff --git a/xray-core/proxy/vless/inbound/inbound.go b/xray-core/proxy/vless/inbound/inbound.go index 5359405e8f..dfa8d470aa 100644 --- a/xray-core/proxy/vless/inbound/inbound.go +++ b/xray-core/proxy/vless/inbound/inbound.go @@ -456,7 +456,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s } inbound.Name = "vless" inbound.User = request.User - inbound.VlessRoute = net.Port(userSentID[15]) + inbound.VlessRoute = net.PortFromBytes(userSentID[6:8]) account := request.User.Account.(*vless.MemoryAccount) diff --git a/xray-core/proxy/vless/validator.go b/xray-core/proxy/vless/validator.go index 61d5e0256d..35a4469a0b 100644 --- a/xray-core/proxy/vless/validator.go +++ b/xray-core/proxy/vless/validator.go @@ -18,6 +18,12 @@ type Validator interface { GetCount() int64 } +func ProcessUUID(id [16]byte) [16]byte { + id[6] = 0 + id[7] = 0 + return id +} + // MemoryValidator stores valid VLESS users. type MemoryValidator struct { // Considering email's usage here, map + sync.Mutex/RWMutex may have better performance. @@ -33,7 +39,7 @@ func (v *MemoryValidator) Add(u *protocol.MemoryUser) error { return errors.New("User ", u.Email, " already exists.") } } - v.users.Store([15]byte(u.Account.(*MemoryAccount).ID.Bytes()), u) + v.users.Store(ProcessUUID(u.Account.(*MemoryAccount).ID.UUID()), u) return nil } @@ -48,13 +54,13 @@ func (v *MemoryValidator) Del(e string) error { return errors.New("User ", e, " not found.") } v.email.Delete(le) - v.users.Delete([15]byte(u.(*protocol.MemoryUser).Account.(*MemoryAccount).ID.Bytes())) + v.users.Delete(ProcessUUID(u.(*protocol.MemoryUser).Account.(*MemoryAccount).ID.UUID())) return nil } // Get a VLESS user with UUID, nil if user doesn't exist. func (v *MemoryValidator) Get(id uuid.UUID) *protocol.MemoryUser { - u, _ := v.users.Load([15]byte(id[:])) + u, _ := v.users.Load(ProcessUUID(id)) if u != nil { return u.(*protocol.MemoryUser) } diff --git a/yt-dlp/yt_dlp/extractor/_extractors.py b/yt-dlp/yt_dlp/extractor/_extractors.py index bb595f924b..6c2eb6cddd 100644 --- a/yt-dlp/yt_dlp/extractor/_extractors.py +++ b/yt-dlp/yt_dlp/extractor/_extractors.py @@ -58,13 +58,7 @@ from .adn import ( ADNSeasonIE, ) from .adobeconnect import AdobeConnectIE -from .adobetv import ( - AdobeTVChannelIE, - AdobeTVEmbedIE, - AdobeTVIE, - AdobeTVShowIE, - AdobeTVVideoIE, -) +from .adobetv import AdobeTVVideoIE from .adultswim import AdultSwimIE from .aenetworks import ( AENetworksCollectionIE, @@ -152,7 +146,6 @@ from .ard import ( ARDBetaMediathekIE, ARDMediathekCollectionIE, ) -from .arkena import ArkenaIE from .arnes import ArnesIE from .art19 import ( Art19IE, @@ -1548,7 +1541,6 @@ from .pixivsketch import ( PixivSketchIE, PixivSketchUserIE, ) -from .pladform import PladformIE from .planetmarathi import PlanetMarathiIE from .platzi import ( PlatziCourseIE, @@ -2313,10 +2305,6 @@ 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/adobetv.py b/yt-dlp/yt_dlp/extractor/adobetv.py index 997e1b92cb..7a29a349c1 100644 --- a/yt-dlp/yt_dlp/extractor/adobetv.py +++ b/yt-dlp/yt_dlp/extractor/adobetv.py @@ -1,297 +1,100 @@ -import functools -import re - from .common import InfoExtractor from ..utils import ( ISO639Utils, - OnDemandPagedList, + clean_html, + determine_ext, float_or_none, int_or_none, join_nonempty, - parse_duration, - str_or_none, - str_to_int, - unified_strdate, + url_or_none, ) +from ..utils.traversal import traverse_obj -class AdobeTVBaseIE(InfoExtractor): - def _call_api(self, path, video_id, query, note=None): - return self._download_json( - 'http://tv.adobe.com/api/v4/' + path, - video_id, note, query=query)['data'] - - def _parse_subtitles(self, video_data, url_key): - subtitles = {} - for translation in video_data.get('translations', []): - vtt_path = translation.get(url_key) - if not vtt_path: - continue - lang = translation.get('language_w3c') or ISO639Utils.long2short(translation['language_medium']) - subtitles.setdefault(lang, []).append({ - 'ext': 'vtt', - 'url': vtt_path, - }) - return subtitles - - def _parse_video_data(self, video_data): - video_id = str(video_data['id']) - title = video_data['title'] - - s3_extracted = False - formats = [] - for source in video_data.get('videos', []): - source_url = source.get('url') - if not source_url: - continue - f = { - 'format_id': source.get('quality_level'), - 'fps': int_or_none(source.get('frame_rate')), - 'height': int_or_none(source.get('height')), - 'tbr': int_or_none(source.get('video_data_rate')), - 'width': int_or_none(source.get('width')), - 'url': source_url, - } - original_filename = source.get('original_filename') - if original_filename: - if not (f.get('height') and f.get('width')): - mobj = re.search(r'_(\d+)x(\d+)', original_filename) - if mobj: - f.update({ - 'height': int(mobj.group(2)), - 'width': int(mobj.group(1)), - }) - if original_filename.startswith('s3://') and not s3_extracted: - formats.append({ - 'format_id': 'original', - 'quality': 1, - 'url': original_filename.replace('s3://', 'https://s3.amazonaws.com/'), - }) - s3_extracted = True - formats.append(f) - - return { - 'id': video_id, - 'title': title, - 'description': video_data.get('description'), - 'thumbnail': video_data.get('thumbnail'), - 'upload_date': unified_strdate(video_data.get('start_date')), - 'duration': parse_duration(video_data.get('duration')), - 'view_count': str_to_int(video_data.get('playcount')), - 'formats': formats, - 'subtitles': self._parse_subtitles(video_data, 'vtt'), - } - - -class AdobeTVEmbedIE(AdobeTVBaseIE): - _WORKING = False - IE_NAME = 'adobetv:embed' - _VALID_URL = r'https?://tv\.adobe\.com/embed/\d+/(?P\d+)' - _TESTS = [{ - 'url': 'https://tv.adobe.com/embed/22/4153', - 'md5': 'c8c0461bf04d54574fc2b4d07ac6783a', - 'info_dict': { - 'id': '4153', - 'ext': 'flv', - 'title': 'Creating Graphics Optimized for BlackBerry', - 'description': 'md5:eac6e8dced38bdaae51cd94447927459', - 'thumbnail': r're:https?://.+\.jpg', - 'upload_date': '20091109', - 'duration': 377, - 'view_count': int, - }, - }] - - def _real_extract(self, url): - video_id = self._match_id(url) - - video_data = self._call_api( - 'episode/' + video_id, video_id, {'disclosure': 'standard'})[0] - return self._parse_video_data(video_data) - - -class AdobeTVIE(AdobeTVBaseIE): - _WORKING = False +class AdobeTVVideoIE(InfoExtractor): IE_NAME = 'adobetv' - _VALID_URL = r'https?://tv\.adobe\.com/(?:(?Pfr|de|es|jp)/)?watch/(?P[^/]+)/(?P[^/]+)' - _TESTS = [{ - 'url': 'http://tv.adobe.com/watch/the-complete-picture-with-julieanne-kost/quick-tip-how-to-draw-a-circle-around-an-object-in-photoshop/', - 'md5': '9bc5727bcdd55251f35ad311ca74fa1e', - 'info_dict': { - 'id': '10981', - 'ext': 'mp4', - 'title': 'Quick Tip - How to Draw a Circle Around an Object in Photoshop', - 'description': 'md5:99ec318dc909d7ba2a1f2b038f7d2311', - 'thumbnail': r're:https?://.+\.jpg', - 'upload_date': '20110914', - 'duration': 60, - 'view_count': int, - }, - }] - - def _real_extract(self, url): - language, show_urlname, urlname = self._match_valid_url(url).groups() - if not language: - language = 'en' - - video_data = self._call_api( - 'episode/get', urlname, { - 'disclosure': 'standard', - 'language': language, - 'show_urlname': show_urlname, - 'urlname': urlname, - })[0] - return self._parse_video_data(video_data) - - -class AdobeTVPlaylistBaseIE(AdobeTVBaseIE): - _PAGE_SIZE = 25 - - def _fetch_page(self, display_id, query, page): - page += 1 - query['page'] = page - for element_data in self._call_api( - self._RESOURCE, display_id, query, f'Download Page {page}'): - yield self._process_data(element_data) - - def _extract_playlist_entries(self, display_id, query): - return OnDemandPagedList(functools.partial( - self._fetch_page, display_id, query), self._PAGE_SIZE) - - -class AdobeTVShowIE(AdobeTVPlaylistBaseIE): - _WORKING = False - IE_NAME = 'adobetv:show' - _VALID_URL = r'https?://tv\.adobe\.com/(?:(?Pfr|de|es|jp)/)?show/(?P[^/]+)' - _TESTS = [{ - 'url': 'http://tv.adobe.com/show/the-complete-picture-with-julieanne-kost', - 'info_dict': { - 'id': '36', - 'title': 'The Complete Picture with Julieanne Kost', - 'description': 'md5:fa50867102dcd1aa0ddf2ab039311b27', - }, - 'playlist_mincount': 136, - }] - _RESOURCE = 'episode' - _process_data = AdobeTVBaseIE._parse_video_data - - def _real_extract(self, url): - language, show_urlname = self._match_valid_url(url).groups() - if not language: - language = 'en' - query = { - 'disclosure': 'standard', - 'language': language, - 'show_urlname': show_urlname, - } - - show_data = self._call_api( - 'show/get', show_urlname, query)[0] - - return self.playlist_result( - self._extract_playlist_entries(show_urlname, query), - str_or_none(show_data.get('id')), - show_data.get('show_name'), - show_data.get('show_description')) - - -class AdobeTVChannelIE(AdobeTVPlaylistBaseIE): - _WORKING = False - IE_NAME = 'adobetv:channel' - _VALID_URL = r'https?://tv\.adobe\.com/(?:(?Pfr|de|es|jp)/)?channel/(?P[^/]+)(?:/(?P[^/]+))?' - _TESTS = [{ - 'url': 'http://tv.adobe.com/channel/development', - 'info_dict': { - 'id': 'development', - }, - 'playlist_mincount': 96, - }] - _RESOURCE = 'show' - - def _process_data(self, show_data): - return self.url_result( - show_data['url'], 'AdobeTVShow', str_or_none(show_data.get('id'))) - - def _real_extract(self, url): - language, channel_urlname, category_urlname = self._match_valid_url(url).groups() - if not language: - language = 'en' - query = { - 'channel_urlname': channel_urlname, - 'language': language, - } - if category_urlname: - query['category_urlname'] = category_urlname - - return self.playlist_result( - self._extract_playlist_entries(channel_urlname, query), - channel_urlname) - - -class AdobeTVVideoIE(AdobeTVBaseIE): - IE_NAME = 'adobetv:video' _VALID_URL = r'https?://video\.tv\.adobe\.com/v/(?P\d+)' - _EMBED_REGEX = [r']+src=[\'"](?P(?:https?:)?//video\.tv\.adobe\.com/v/\d+[^"]+)[\'"]'] + _EMBED_REGEX = [r']+src=["\'](?P(?:https?:)?//video\.tv\.adobe\.com/v/\d+)'] _TESTS = [{ - # From https://helpx.adobe.com/acrobat/how-to/new-experience-acrobat-dc.html?set=acrobat--get-started--essential-beginners - 'url': 'https://video.tv.adobe.com/v/2456/', + 'url': 'https://video.tv.adobe.com/v/2456', 'md5': '43662b577c018ad707a63766462b1e87', 'info_dict': { 'id': '2456', 'ext': 'mp4', 'title': 'New experience with Acrobat DC', 'description': 'New experience with Acrobat DC', - 'duration': 248.667, + 'duration': 248.522, + 'thumbnail': r're:https?://images-tv\.adobe\.com/.+\.jpg', + }, + }, { + 'url': 'https://video.tv.adobe.com/v/3463980/adobe-acrobat', + 'info_dict': { + 'id': '3463980', + 'ext': 'mp4', + 'title': 'Adobe Acrobat: How to Customize the Toolbar for Faster PDF Editing', + 'description': 'md5:94368ab95ae24f9c1bee0cb346e03dc3', + 'duration': 97.514, 'thumbnail': r're:https?://images-tv\.adobe\.com/.+\.jpg', }, }] _WEBPAGE_TESTS = [{ - # FIXME: Invalid extension - 'url': 'https://www.adobe.com/learn/acrobat/web/customize-toolbar', + # https://video.tv.adobe.com/v/3442499 + 'url': 'https://business.adobe.com/dx-fragments/summit/2025/marquees/S335/ondemand.live.html', 'info_dict': { - 'id': '3463980', - 'ext': 'm3u8', - 'title': 'Adobe Acrobat: How to Customize the Toolbar for Faster PDF Editing', - 'description': 'md5:94368ab95ae24f9c1bee0cb346e03dc3', - 'duration': 97.557, + 'id': '3442499', + 'ext': 'mp4', + 'title': 'S335 - Beyond Personalization: Creating Intent-Based Experiences at Scale', + 'description': 'Beyond Personalization: Creating Intent-Based Experiences at Scale', + 'duration': 2906.8, + 'thumbnail': r're:https?://images-tv\.adobe\.com/.+\.jpg', }, }] def _real_extract(self, url): video_id = self._match_id(url) webpage = self._download_webpage(url, video_id) - - video_data = self._parse_json(self._search_regex( - r'var\s+bridge\s*=\s*([^;]+);', webpage, 'bridged data'), video_id) - title = video_data['title'] + video_data = self._search_json( + r'var\s+bridge\s*=', webpage, 'bridged data', video_id) formats = [] - sources = video_data.get('sources') or [] - for source in sources: - source_src = source.get('src') - if not source_src: - continue - formats.append({ - 'filesize': int_or_none(source.get('kilobytes') or None, invscale=1000), - 'format_id': join_nonempty(source.get('format'), source.get('label')), - 'height': int_or_none(source.get('height') or None), - 'tbr': int_or_none(source.get('bitrate') or None), - 'width': int_or_none(source.get('width') or None), - 'url': source_src, - }) + for source in traverse_obj(video_data, ( + 'sources', lambda _, v: v['format'] != 'playlist' and url_or_none(v['src']), + )): + source_url = self._proto_relative_url(source['src']) + if determine_ext(source_url) == 'm3u8': + fmts = self._extract_m3u8_formats( + source_url, video_id, 'mp4', m3u8_id='hls', fatal=False) + else: + fmts = [{'url': source_url}] - # For both metadata and downloaded files the duration varies among - # formats. I just pick the max one - duration = max(filter(None, [ - float_or_none(source.get('duration'), scale=1000) - for source in sources])) + for fmt in fmts: + fmt.update(traverse_obj(source, { + 'duration': ('duration', {float_or_none(scale=1000)}), + 'filesize': ('kilobytes', {float_or_none(invscale=1000)}), + 'format_id': (('format', 'label'), {str}, all, {lambda x: join_nonempty(*x)}), + 'height': ('height', {int_or_none}), + 'tbr': ('bitrate', {int_or_none}), + 'width': ('width', {int_or_none}), + })) + formats.extend(fmts) + + subtitles = {} + for translation in traverse_obj(video_data, ( + 'translations', lambda _, v: url_or_none(v['vttPath']), + )): + lang = translation.get('language_w3c') or ISO639Utils.long2short(translation.get('language_medium')) or 'und' + subtitles.setdefault(lang, []).append({ + 'ext': 'vtt', + 'url': self._proto_relative_url(translation['vttPath']), + }) return { 'id': video_id, 'formats': formats, - 'title': title, - 'description': video_data.get('description'), - 'thumbnail': video_data.get('video', {}).get('poster'), - 'duration': duration, - 'subtitles': self._parse_subtitles(video_data, 'vttPath'), + 'subtitles': subtitles, + **traverse_obj(video_data, { + 'title': ('title', {clean_html}), + 'description': ('description', {clean_html}, filter), + 'thumbnail': ('video', 'poster', {self._proto_relative_url}, {url_or_none}), + }), } diff --git a/yt-dlp/yt_dlp/extractor/arkena.py b/yt-dlp/yt_dlp/extractor/arkena.py deleted file mode 100644 index aa6c5ca4d6..0000000000 --- a/yt-dlp/yt_dlp/extractor/arkena.py +++ /dev/null @@ -1,150 +0,0 @@ -from .common import InfoExtractor -from ..utils import ( - ExtractorError, - float_or_none, - int_or_none, - parse_iso8601, - parse_qs, - try_get, -) - - -class ArkenaIE(InfoExtractor): - _VALID_URL = r'''(?x) - https?:// - (?: - video\.(?:arkena|qbrick)\.com/play2/embed/player\?| - play\.arkena\.com/(?:config|embed)/avp/v\d/player/media/(?P[^/]+)/[^/]+/(?P\d+) - ) - ''' - # See https://support.arkena.com/display/PLAY/Ways+to+embed+your+video - _EMBED_REGEX = [r']+src=(["\'])(?P(?:https?:)?//play\.arkena\.com/embed/avp/.+?)\1'] - _TESTS = [{ - 'url': 'https://video.qbrick.com/play2/embed/player?accountId=1034090&mediaId=d8ab4607-00090107-aab86310', - 'md5': '97f117754e5f3c020f5f26da4a44ebaf', - 'info_dict': { - 'id': 'd8ab4607-00090107-aab86310', - 'ext': 'mp4', - 'title': 'EM_HT20_117_roslund_v2.mp4', - 'timestamp': 1608285912, - 'upload_date': '20201218', - 'duration': 1429.162667, - 'subtitles': { - 'sv': 'count:3', - }, - }, - }, { - 'url': 'https://play.arkena.com/embed/avp/v2/player/media/b41dda37-d8e7-4d3f-b1b5-9a9db578bdfe/1/129411', - 'only_matching': True, - }, { - 'url': 'https://play.arkena.com/config/avp/v2/player/media/b41dda37-d8e7-4d3f-b1b5-9a9db578bdfe/1/129411/?callbackMethod=jQuery1111023664739129262213_1469227693893', - 'only_matching': True, - }, { - 'url': 'http://play.arkena.com/config/avp/v1/player/media/327336/darkmatter/131064/?callbackMethod=jQuery1111002221189684892677_1469227595972', - 'only_matching': True, - }, { - 'url': 'http://play.arkena.com/embed/avp/v1/player/media/327336/darkmatter/131064/', - 'only_matching': True, - }, { - 'url': 'http://video.arkena.com/play2/embed/player?accountId=472718&mediaId=35763b3b-00090078-bf604299&pageStyling=styled', - 'only_matching': True, - }] - - def _real_extract(self, url): - mobj = self._match_valid_url(url) - video_id = mobj.group('id') - account_id = mobj.group('account_id') - - # Handle http://video.arkena.com/play2/embed/player URL - if not video_id: - qs = parse_qs(url) - video_id = qs.get('mediaId', [None])[0] - account_id = qs.get('accountId', [None])[0] - if not video_id or not account_id: - raise ExtractorError('Invalid URL', expected=True) - - media = self._download_json( - f'https://video.qbrick.com/api/v1/public/accounts/{account_id}/medias/{video_id}', - video_id, query={ - # https://video.qbrick.com/docs/api/examples/library-api.html - 'fields': 'asset/resources/*/renditions/*(height,id,language,links/*(href,mimeType),type,size,videos/*(audios/*(codec,sampleRate),bitrate,codec,duration,height,width),width),created,metadata/*(title,description),tags', - }) - metadata = media.get('metadata') or {} - title = metadata['title'] - - duration = None - formats = [] - thumbnails = [] - subtitles = {} - for resource in media['asset']['resources']: - for rendition in (resource.get('renditions') or []): - rendition_type = rendition.get('type') - for i, link in enumerate(rendition.get('links') or []): - href = link.get('href') - if not href: - continue - if rendition_type == 'image': - thumbnails.append({ - 'filesize': int_or_none(rendition.get('size')), - 'height': int_or_none(rendition.get('height')), - 'id': rendition.get('id'), - 'url': href, - 'width': int_or_none(rendition.get('width')), - }) - elif rendition_type == 'subtitle': - subtitles.setdefault(rendition.get('language') or 'en', []).append({ - 'url': href, - }) - elif rendition_type == 'video': - f = { - 'filesize': int_or_none(rendition.get('size')), - 'format_id': rendition.get('id'), - 'url': href, - } - video = try_get(rendition, lambda x: x['videos'][i], dict) - if video: - if not duration: - duration = float_or_none(video.get('duration')) - f.update({ - 'height': int_or_none(video.get('height')), - 'tbr': int_or_none(video.get('bitrate'), 1000), - 'vcodec': video.get('codec'), - 'width': int_or_none(video.get('width')), - }) - audio = try_get(video, lambda x: x['audios'][0], dict) - if audio: - f.update({ - 'acodec': audio.get('codec'), - 'asr': int_or_none(audio.get('sampleRate')), - }) - formats.append(f) - elif rendition_type == 'index': - mime_type = link.get('mimeType') - if mime_type == 'application/smil+xml': - formats.extend(self._extract_smil_formats( - href, video_id, fatal=False)) - elif mime_type == 'application/x-mpegURL': - formats.extend(self._extract_m3u8_formats( - href, video_id, 'mp4', 'm3u8_native', - m3u8_id='hls', fatal=False)) - elif mime_type == 'application/hds+xml': - formats.extend(self._extract_f4m_formats( - href, video_id, f4m_id='hds', fatal=False)) - elif mime_type == 'application/dash+xml': - formats.extend(self._extract_mpd_formats( - href, video_id, mpd_id='dash', fatal=False)) - elif mime_type == 'application/vnd.ms-sstr+xml': - formats.extend(self._extract_ism_formats( - href, video_id, ism_id='mss', fatal=False)) - - return { - 'id': video_id, - 'title': title, - 'description': metadata.get('description'), - 'timestamp': parse_iso8601(media.get('created')), - 'thumbnails': thumbnails, - 'subtitles': subtitles, - 'duration': duration, - 'tags': media.get('tags'), - 'formats': formats, - } diff --git a/yt-dlp/yt_dlp/extractor/common.py b/yt-dlp/yt_dlp/extractor/common.py index a96fb9c4cb..fb15a8e8ce 100644 --- a/yt-dlp/yt_dlp/extractor/common.py +++ b/yt-dlp/yt_dlp/extractor/common.py @@ -3107,7 +3107,6 @@ class InfoExtractor: else: # $Number*$ or $Time$ in media template with S list available # Example $Number*$: http://www.svtplay.se/klipp/9023742/stopptid-om-bjorn-borg - # Example $Time$: https://play.arkena.com/embed/avp/v2/player/media/b41dda37-d8e7-4d3f-b1b5-9a9db578bdfe/1/129411 representation_ms_info['fragments'] = [] segment_time = 0 segment_d = None diff --git a/yt-dlp/yt_dlp/extractor/dailymotion.py b/yt-dlp/yt_dlp/extractor/dailymotion.py index d27a027702..017dbd50f9 100644 --- a/yt-dlp/yt_dlp/extractor/dailymotion.py +++ b/yt-dlp/yt_dlp/extractor/dailymotion.py @@ -171,17 +171,6 @@ class DailymotionIE(DailymotionBaseInfoExtractor): 'view_count': int, }, 'skip': 'video gone', - }, { - # Vevo video - 'url': 'http://www.dailymotion.com/video/x149uew_katy-perry-roar-official_musi', - 'info_dict': { - 'title': 'Roar (Official)', - 'id': 'USUV71301934', - 'ext': 'mp4', - 'uploader': 'Katy Perry', - 'upload_date': '20130905', - }, - 'skip': 'Invalid URL', }, { # age-restricted video 'url': 'http://www.dailymotion.com/video/xyh2zz_leanna-decker-cyber-girl-of-the-year-desires-nude-playboy-plus_redband', diff --git a/yt-dlp/yt_dlp/extractor/disney.py b/yt-dlp/yt_dlp/extractor/disney.py index a90f12389e..d5cf6bbb38 100644 --- a/yt-dlp/yt_dlp/extractor/disney.py +++ b/yt-dlp/yt_dlp/extractor/disney.py @@ -90,10 +90,6 @@ 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/lcp.py b/yt-dlp/yt_dlp/extractor/lcp.py index 69148be222..9ebd55dbee 100644 --- a/yt-dlp/yt_dlp/extractor/lcp.py +++ b/yt-dlp/yt_dlp/extractor/lcp.py @@ -1,8 +1,8 @@ -from .arkena import ArkenaIE from .common import InfoExtractor -class LcpPlayIE(ArkenaIE): # XXX: Do not subclass from concrete IE +class LcpPlayIE(InfoExtractor): + _WORKING = False _VALID_URL = r'https?://play\.lcp\.fr/embed/(?P[^/]+)/(?P[^/]+)/[^/]+/[^/]+' _TESTS = [{ 'url': 'http://play.lcp.fr/embed/327336/131064/darkmatter/0', @@ -21,24 +21,9 @@ class LcpPlayIE(ArkenaIE): # XXX: Do not subclass from concrete IE class LcpIE(InfoExtractor): + _WORKING = False _VALID_URL = r'https?://(?:www\.)?lcp\.fr/(?:[^/]+/)*(?P[^/]+)' - _TESTS = [{ - # arkena embed - 'url': 'http://www.lcp.fr/la-politique-en-video/schwartzenberg-prg-preconise-francois-hollande-de-participer-une-primaire', - 'md5': 'b8bd9298542929c06c1c15788b1f277a', - 'info_dict': { - 'id': 'd56d03e9', - 'ext': 'mp4', - 'title': 'Schwartzenberg (PRG) préconise à François Hollande de participer à une primaire à gauche', - 'description': 'md5:96ad55009548da9dea19f4120c6c16a8', - 'timestamp': 1456488895, - 'upload_date': '20160226', - }, - 'params': { - 'skip_download': True, - }, - }, { # dailymotion live stream 'url': 'http://www.lcp.fr/le-direct', 'info_dict': { diff --git a/yt-dlp/yt_dlp/extractor/myspace.py b/yt-dlp/yt_dlp/extractor/myspace.py index fa2ef14e13..701c2f447f 100644 --- a/yt-dlp/yt_dlp/extractor/myspace.py +++ b/yt-dlp/yt_dlp/extractor/myspace.py @@ -111,12 +111,8 @@ 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 vevo_id: - self.to_screen(f'Vevo video detected: {vevo_id}') - return self.url_result(f'vevo:{vevo_id}', ie='Vevo') - elif youtube_id: + if 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/niconico.py b/yt-dlp/yt_dlp/extractor/niconico.py index ecf87ea0ba..391eba2bea 100644 --- a/yt-dlp/yt_dlp/extractor/niconico.py +++ b/yt-dlp/yt_dlp/extractor/niconico.py @@ -873,39 +873,67 @@ class NiconicoLiveIE(NiconicoBaseIE): IE_DESC = 'ニコニコ生放送' _VALID_URL = r'https?://(?:sp\.)?live2?\.nicovideo\.jp/(?:watch|gate)/(?Plv\d+)' _TESTS = [{ - 'note': 'this test case includes invisible characters for title, pasting them as-is', - 'url': 'https://live.nicovideo.jp/watch/lv339533123', + 'url': 'https://live.nicovideo.jp/watch/lv329299587', 'info_dict': { - 'id': 'lv339533123', - 'title': '激辛ペヤング食べます\u202a( ;ᯅ; )\u202c(歌枠オーディション参加中)', - 'view_count': 1526, - 'comment_count': 1772, - 'description': '初めましてもかって言います❕\nのんびり自由に適当に暮らしてます', - 'uploader': 'もか', - 'channel': 'ゲストさんのコミュニティ', - 'channel_id': 'co5776900', - 'channel_url': 'https://com.nicovideo.jp/community/co5776900', - 'timestamp': 1670677328, - 'is_live': True, + 'id': 'lv329299587', + 'ext': 'mp4', + 'title': str, + 'channel': 'ニコニコエンタメチャンネル', + 'channel_id': 'ch2640322', + 'channel_url': 'https://ch.nicovideo.jp/channel/ch2640322', + 'comment_count': int, + 'description': 'md5:281edd7f00309e99ec46a87fb16d7033', + 'live_status': 'is_live', + 'thumbnail': r're:https?://.+', + 'timestamp': 1608803400, + 'upload_date': '20201224', + 'uploader': '株式会社ドワンゴ', + 'view_count': int, }, - 'skip': 'livestream', + 'params': {'skip_download': True}, }, { - 'url': 'https://live2.nicovideo.jp/watch/lv339533123', - 'only_matching': True, - }, { - 'url': 'https://sp.live.nicovideo.jp/watch/lv339533123', - 'only_matching': True, - }, { - 'url': 'https://sp.live2.nicovideo.jp/watch/lv339533123', - 'only_matching': True, + 'url': 'https://live.nicovideo.jp/watch/lv331050399', + 'info_dict': { + 'id': 'lv331050399', + 'ext': 'mp4', + 'title': str, + 'age_limit': 18, + 'channel': 'みんなのおもちゃ REBOOT', + 'channel_id': 'ch2642088', + 'channel_url': 'https://ch.nicovideo.jp/channel/ch2642088', + 'comment_count': int, + 'description': 'md5:8d0bb5beaca73b911725478a1e7c7b91', + 'live_status': 'is_live', + 'thumbnail': r're:https?://.+', + 'timestamp': 1617029400, + 'upload_date': '20210329', + 'uploader': '株式会社ドワンゴ', + 'view_count': int, + }, + 'params': {'skip_download': True}, }] def _real_extract(self, url): video_id = self._match_id(url) - webpage = self._download_webpage(url, video_id, expected_status=404) + webpage, urlh = self._download_webpage_handle(url, video_id, expected_status=404) if err_msg := traverse_obj(webpage, ({find_element(cls='message')}, {clean_html})): raise ExtractorError(err_msg, expected=True) + age_limit = 18 if 'age_auth' in urlh.url else None + if age_limit: + if not self.is_logged_in: + self.raise_login_required('Login is required to access age-restricted content') + + my = self._download_webpage('https://www.nicovideo.jp/my', None, 'Checking age verification') + if traverse_obj(my, ( + {find_element(id='js-initial-userpage-data', html=True)}, {extract_attributes}, + 'data-environment', {json.loads}, 'allowSensitiveContents', {bool}, + )): + self._set_cookie('.nicovideo.jp', 'age_auth', '1') + webpage = self._download_webpage(url, video_id) + else: + raise ExtractorError('Sensitive content setting must be enabled', expected=True) + embedded_data = traverse_obj(webpage, ( {find_element(tag='script', id='embedded-data', html=True)}, {extract_attributes}, 'data-props', {json.loads})) @@ -1008,6 +1036,7 @@ class NiconicoLiveIE(NiconicoBaseIE): return { 'id': video_id, 'title': title, + 'age_limit': age_limit, 'downloader_options': { 'max_quality': traverse_obj(embedded_data, ('program', 'stream', 'maxQuality', {str})) or 'normal', 'ws': ws, diff --git a/yt-dlp/yt_dlp/extractor/odnoklassniki.py b/yt-dlp/yt_dlp/extractor/odnoklassniki.py index 18eba42e60..c9bee9060e 100644 --- a/yt-dlp/yt_dlp/extractor/odnoklassniki.py +++ b/yt-dlp/yt_dlp/extractor/odnoklassniki.py @@ -352,14 +352,6 @@ class OdnoklassnikiIE(InfoExtractor): 'subtitles': subtitles, } - # pladform - if provider == 'OPEN_GRAPH': - info.update({ - '_type': 'url_transparent', - 'url': movie['contentId'], - }) - return info - if provider == 'USER_YOUTUBE': info.update({ '_type': 'url_transparent', diff --git a/yt-dlp/yt_dlp/extractor/pladform.py b/yt-dlp/yt_dlp/extractor/pladform.py deleted file mode 100644 index f4355d0cf5..0000000000 --- a/yt-dlp/yt_dlp/extractor/pladform.py +++ /dev/null @@ -1,135 +0,0 @@ -from .common import InfoExtractor -from ..utils import ( - ExtractorError, - determine_ext, - int_or_none, - parse_qs, - qualities, - xpath_text, -) - - -class PladformIE(InfoExtractor): - _VALID_URL = r'''(?x) - https?:// - (?: - (?: - out\.pladform\.ru/player| - static\.pladform\.ru/player\.swf - ) - \?.*\bvideoid=| - video\.pladform\.ru/catalog/video/videoid/ - ) - (?P\d+) - ''' - _EMBED_REGEX = [r']+src=(["\'])(?P(?:https?:)?//out\.pladform\.ru/player\?.+?)\1'] - _TESTS = [{ - 'url': 'http://out.pladform.ru/player?pl=18079&type=html5&videoid=100231282', - 'info_dict': { - 'id': '6216d548e755edae6e8280667d774791', - 'ext': 'mp4', - 'timestamp': 1406117012, - 'title': 'Гарик Мартиросян и Гарик Харламов - Кастинг на концерт ко Дню милиции', - 'age_limit': 0, - 'upload_date': '20140723', - 'thumbnail': str, - 'view_count': int, - 'description': str, - 'uploader_id': '12082', - 'uploader': 'Comedy Club', - 'duration': 367, - }, - 'expected_warnings': ['HTTP Error 404: Not Found'], - }, { - 'url': 'https://out.pladform.ru/player?pl=64471&videoid=3777899&vk_puid15=0&vk_puid34=0', - 'md5': '53362fac3a27352da20fa2803cc5cd6f', - 'info_dict': { - 'id': '3777899', - 'ext': 'mp4', - 'title': 'СТУДИЯ СОЮЗ • Шоу Студия Союз, 24 выпуск (01.02.2018) Нурлан Сабуров и Слава Комиссаренко', - 'description': 'md5:05140e8bf1b7e2d46e7ba140be57fd95', - 'thumbnail': r're:^https?://.*\.jpg$', - 'duration': 3190, - }, - }, { - 'url': 'http://static.pladform.ru/player.swf?pl=21469&videoid=100183293&vkcid=0', - 'only_matching': True, - }, { - 'url': 'http://video.pladform.ru/catalog/video/videoid/100183293/vkcid/0', - 'only_matching': True, - }] - - def _real_extract(self, url): - video_id = self._match_id(url) - - qs = parse_qs(url) - pl = qs.get('pl', ['1'])[0] - - video = self._download_xml( - 'http://out.pladform.ru/getVideo', video_id, query={ - 'pl': pl, - 'videoid': video_id, - }, fatal=False) - - def fail(text): - raise ExtractorError( - f'{self.IE_NAME} returned error: {text}', - expected=True) - - if not video: - target_url = self._request_webpage(url, video_id, note='Resolving final URL').url - if target_url == url: - raise ExtractorError('Can\'t parse page') - return self.url_result(target_url) - - if video.tag == 'error': - fail(video.text) - - quality = qualities(('ld', 'sd', 'hd')) - - formats = [] - for src in video.findall('./src'): - if src is None: - continue - format_url = src.text - if not format_url: - continue - if src.get('type') == 'hls' or determine_ext(format_url) == 'm3u8': - formats.extend(self._extract_m3u8_formats( - format_url, video_id, 'mp4', entry_protocol='m3u8_native', - m3u8_id='hls', fatal=False)) - else: - formats.append({ - 'url': src.text, - 'format_id': src.get('quality'), - 'quality': quality(src.get('quality')), - }) - - if not formats: - error = xpath_text(video, './cap', 'error', default=None) - if error: - fail(error) - - webpage = self._download_webpage( - f'http://video.pladform.ru/catalog/video/videoid/{video_id}', - video_id) - - title = self._og_search_title(webpage, fatal=False) or xpath_text( - video, './/title', 'title', fatal=True) - description = self._search_regex( - r'\s*

([^<]+)

', webpage, 'description', fatal=False) - thumbnail = self._og_search_thumbnail(webpage) or xpath_text( - video, './/cover', 'cover') - - duration = int_or_none(xpath_text(video, './/time', 'duration')) - age_limit = int_or_none(xpath_text(video, './/age18', 'age limit')) - - return { - 'id': video_id, - 'title': title, - 'description': description, - 'thumbnail': thumbnail, - 'duration': duration, - 'age_limit': age_limit, - 'formats': formats, - } diff --git a/yt-dlp/yt_dlp/extractor/vevo.py b/yt-dlp/yt_dlp/extractor/vevo.py deleted file mode 100644 index 8552a609c9..0000000000 --- a/yt-dlp/yt_dlp/extractor/vevo.py +++ /dev/null @@ -1,352 +0,0 @@ -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')) diff --git a/yt-dlp/yt_dlp/extractor/vk.py b/yt-dlp/yt_dlp/extractor/vk.py index 8a106adb97..9adb3086a8 100644 --- a/yt-dlp/yt_dlp/extractor/vk.py +++ b/yt-dlp/yt_dlp/extractor/vk.py @@ -5,7 +5,6 @@ import re from .common import InfoExtractor from .dailymotion import DailymotionIE from .odnoklassniki import OdnoklassnikiIE -from .pladform import PladformIE from .sibnet import SibnetEmbedIE from .vimeo import VimeoIE from .youtube import YoutubeIE @@ -334,11 +333,6 @@ class VKIE(VKBaseIE): 'url': 'https://vk.com/video205387401_164765225', 'only_matching': True, }, - { - # pladform embed - 'url': 'https://vk.com/video-76116461_171554880', - 'only_matching': True, - }, { 'url': 'http://new.vk.com/video205387401_165548505', 'only_matching': True, @@ -456,10 +450,6 @@ class VKIE(VKBaseIE): if vimeo_url is not None: return self.url_result(vimeo_url, VimeoIE.ie_key()) - pladform_url = PladformIE._extract_url(info_page) - if pladform_url: - return self.url_result(pladform_url, PladformIE.ie_key()) - m_rutube = re.search( r'\ssrc="((?:https?:)?//rutube\.ru\\?/(?:video|play)\\?/embed(?:.*?))\\?"', info_page) if m_rutube is not None: