From 94f030fce02f3f3885d907538c6a573fd289b24c Mon Sep 17 00:00:00 2001 From: "github-action[bot]" Date: Tue, 12 Aug 2025 20:40:51 +0200 Subject: [PATCH] Update On Tue Aug 12 20:40:50 CEST 2025 --- .github/update.log | 1 + clash-meta/adapter/outbound/vless.go | 43 +-- clash-meta/listener/inbound/common_test.go | 36 +- clash-meta/listener/inbound/mux_test.go | 2 +- clash-meta/listener/sing_vless/server.go | 45 +-- .../transport/vless/encryption/client.go | 143 +++---- .../transport/vless/encryption/common.go | 36 +- clash-meta/transport/vless/encryption/doc.go | 3 + .../transport/vless/encryption/factory.go | 99 +++++ .../transport/vless/encryption/server.go | 149 ++++---- clash-meta/transport/vless/encryption/xor.go | 2 +- .../frontend/interface/package.json | 2 +- clash-nyanpasu/frontend/nyanpasu/package.json | 8 +- clash-nyanpasu/frontend/ui/package.json | 2 +- clash-nyanpasu/manifest/version.json | 6 +- clash-nyanpasu/pnpm-lock.yaml | 356 +++++++++--------- lede/target/imagebuilder/files/Makefile | 4 +- mieru/pkg/testtool/pipe.go | 92 +++-- mihomo/adapter/outbound/vless.go | 43 +-- mihomo/listener/inbound/common_test.go | 36 +- mihomo/listener/inbound/mux_test.go | 2 +- mihomo/listener/sing_vless/server.go | 45 +-- mihomo/transport/vless/encryption/client.go | 143 +++---- mihomo/transport/vless/encryption/common.go | 36 +- mihomo/transport/vless/encryption/doc.go | 3 + mihomo/transport/vless/encryption/factory.go | 99 +++++ mihomo/transport/vless/encryption/server.go | 149 ++++---- mihomo/transport/vless/encryption/xor.go | 2 +- shadowsocks-rust/Cargo.lock | 54 +-- .../cbi/passwall/client/type/sing-box.lua | 24 +- .../usr/share/shadowsocksr/gen_config.lua | 10 +- .../v2rayN/ServiceLib/Models/ConfigItems.cs | 1 + v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx | 130 +++---- .../CoreConfig/CoreConfigSingboxService.cs | 18 +- .../CoreConfig/CoreConfigV2rayService.cs | 14 +- .../ViewModels/OptionSettingViewModel.cs | 3 + .../Views/OptionSettingWindow.axaml | 33 +- .../Views/OptionSettingWindow.axaml.cs | 1 + .../v2rayN/Views/OptionSettingWindow.xaml | 34 +- .../v2rayN/Views/OptionSettingWindow.xaml.cs | 1 + v2rayng/V2rayNG/app/build.gradle.kts | 4 +- .../src/main/java/com/v2ray/ang/AppConfig.kt | 1 + .../com/v2ray/ang/handler/SettingsManager.kt | 7 + .../com/v2ray/ang/service/TProxyService.kt | 3 +- .../com/v2ray/ang/service/Tun2SocksService.kt | 3 +- .../com/v2ray/ang/service/V2RayVpnService.kt | 3 +- .../java/com/v2ray/ang/ui/SettingsActivity.kt | 10 + .../app/src/main/res/values-ar/strings.xml | 2 + .../app/src/main/res/values-bn/strings.xml | 1 + .../src/main/res/values-bqi-rIR/strings.xml | 1 + .../app/src/main/res/values-fa/strings.xml | 1 + .../app/src/main/res/values-ru/strings.xml | 1 + .../app/src/main/res/values-vi/strings.xml | 1 + .../src/main/res/values-zh-rCN/strings.xml | 1 + .../src/main/res/values-zh-rTW/strings.xml | 1 + .../app/src/main/res/values/strings.xml | 1 + .../app/src/main/res/xml/pref_settings.xml | 6 + yt-dlp/yt_dlp/postprocessor/xattrpp.py | 5 +- 58 files changed, 1108 insertions(+), 854 deletions(-) create mode 100644 clash-meta/transport/vless/encryption/factory.go create mode 100644 mihomo/transport/vless/encryption/factory.go diff --git a/.github/update.log b/.github/update.log index 445c4cf68e..9fc4ae0bb6 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1087,3 +1087,4 @@ Update On Fri Aug 8 20:38:56 CEST 2025 Update On Sat Aug 9 20:40:00 CEST 2025 Update On Sun Aug 10 20:40:02 CEST 2025 Update On Mon Aug 11 20:43:53 CEST 2025 +Update On Tue Aug 12 20:40:42 CEST 2025 diff --git a/clash-meta/adapter/outbound/vless.go b/clash-meta/adapter/outbound/vless.go index de3c24ab75..b26ddb07f2 100644 --- a/clash-meta/adapter/outbound/vless.go +++ b/clash-meta/adapter/outbound/vless.go @@ -3,14 +3,11 @@ package outbound import ( "context" "crypto/tls" - "encoding/base64" "errors" "fmt" "net" "net/http" "strconv" - "strings" - "time" "github.com/metacubex/mihomo/common/convert" N "github.com/metacubex/mihomo/common/net" @@ -456,41 +453,11 @@ func NewVless(option VlessOption) (*Vless, error) { option: &option, } - if s := strings.SplitN(option.Encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" { - var minutes uint32 - if s[0] != "1rtt" { - t := strings.TrimSuffix(s[0], "min") - if t == s[0] { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - var i int - i, err = strconv.Atoi(t) - if err != nil { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - minutes = uint32(i) - } - var xor uint32 - switch s[1] { - case "vless": - case "aes128xor": - xor = 1 - default: - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - var b []byte - b, err = base64.RawURLEncoding.DecodeString(s[3]) - if err != nil { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - if len(b) == encryption.MLKEM768ClientLength { - v.encryption = &encryption.ClientInstance{} - if err = v.encryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { - return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) - } - } else { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } + v.encryption, err = encryption.NewClient(option.Encryption) + if err != nil { + return nil, err + } + if v.encryption != nil { if option.Flow != "" { return nil, errors.New(`vless "encryption" doesn't support "flow" yet`) } diff --git a/clash-meta/listener/inbound/common_test.go b/clash-meta/listener/inbound/common_test.go index 5b8a6f17e6..5b838bd303 100644 --- a/clash-meta/listener/inbound/common_test.go +++ b/clash-meta/listener/inbound/common_test.go @@ -10,11 +10,13 @@ import ( "net" "net/http" "net/netip" + "strconv" "sync" "testing" "time" N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" @@ -30,7 +32,7 @@ import ( ) var httpPath = "/inbound_test" -var httpData = make([]byte, 10240) +var httpData = make([]byte, 2*pool.RelayBufferSize) var remoteAddr = netip.MustParseAddr("1.2.3.4") var userUUID = utils.NewUUIDV4().String() var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256) @@ -134,14 +136,21 @@ func NewHttpTestTunnel() *TestTunnel { r := chi.NewRouter() r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) { - render.Data(w, r, httpData) + query := r.URL.Query() + size, err := strconv.Atoi(query.Get("size")) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.PlainText(w, r, err.Error()) + return + } + render.Data(w, r, httpData[:size]) }) h2Server := &http2.Server{} server := http.Server{Handler: r} _ = http2.ConfigureServer(&server, h2Server) go server.Serve(ln) - testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil) + testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string, size int) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s?size=%d", proto, remoteAddr, httpPath, size), nil) if !assert.NoError(t, err) { return } @@ -200,7 +209,7 @@ func NewHttpTestTunnel() *TestTunnel { if !assert.NoError(t, err) { return } - assert.Equal(t, httpData, data) + assert.Equal(t, httpData[:size], data) } tunnel := &TestTunnel{ HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) { @@ -241,27 +250,30 @@ func NewHttpTestTunnel() *TestTunnel { }, CloseFn: ln.Close, DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) { + // Sequential testing for debugging t.Run("Sequential", func(t *testing.T) { - testFn(t, proxy, "http") - testFn(t, proxy, "https") + testFn(t, proxy, "http", len(httpData)) + testFn(t, proxy, "https", len(httpData)) }) // Concurrent testing to detect stress t.Run("Concurrent", func(t *testing.T) { wg := sync.WaitGroup{} - const num = 50 - for i := 0; i < num; i++ { + num := len(httpData) / 1024 + for i := 1; i <= num; i++ { + i := i wg.Add(1) go func() { - testFn(t, proxy, "https") + testFn(t, proxy, "https", i*1024) defer wg.Done() }() } - for i := 0; i < num; i++ { + for i := 1; i <= num; i++ { + i := i wg.Add(1) go func() { - testFn(t, proxy, "http") + testFn(t, proxy, "http", i*1024) defer wg.Done() }() } diff --git a/clash-meta/listener/inbound/mux_test.go b/clash-meta/listener/inbound/mux_test.go index 4e676e6d96..a7c0208dd8 100644 --- a/clash-meta/listener/inbound/mux_test.go +++ b/clash-meta/listener/inbound/mux_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -var singMuxProtocolList = []string{"h2mux", "smux"} // don't test "yamux" because it has some confused bugs +var singMuxProtocolList = []string{"smux"} // don't test "h2mux" and "yamux" because it has some confused bugs // notCloseProxyAdapter is a proxy adapter that does not close the underlying outbound.ProxyAdapter. // The outbound.SingMux will close the underlying outbound.ProxyAdapter when it is closed, but we don't want to close it. diff --git a/clash-meta/listener/sing_vless/server.go b/clash-meta/listener/sing_vless/server.go index 41f23f34d8..0e729cab99 100644 --- a/clash-meta/listener/sing_vless/server.go +++ b/clash-meta/listener/sing_vless/server.go @@ -2,15 +2,11 @@ package sing_vless import ( "context" - "encoding/base64" "errors" - "fmt" "net" "net/http" "reflect" - "strconv" "strings" - "time" "unsafe" "github.com/metacubex/mihomo/adapter/inbound" @@ -88,42 +84,11 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) sl = &Listener{config: config, service: service} - if s := strings.SplitN(config.Decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" { - var minutes uint32 - if s[0] != "1rtt" { - t := strings.TrimSuffix(s[0], "min") - if t == s[0] { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - var i int - i, err = strconv.Atoi(t) - if err != nil { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - minutes = uint32(i) - } - var xor uint32 - switch s[1] { - case "vless": - case "aes128xor": - xor = 1 - default: - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - var b []byte - b, err = base64.RawURLEncoding.DecodeString(s[3]) - if err != nil { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - if len(b) == encryption.MLKEM768SeedLength { - sl.decryption = &encryption.ServerInstance{} - if err = sl.decryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { - return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) - } - } else { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - + sl.decryption, err = encryption.NewServer(config.Decryption) + if err != nil { + return nil, err + } + if sl.decryption != nil { defer func() { // decryption must be closed to avoid the goroutine leak if err != nil { _ = sl.decryption.Close() diff --git a/clash-meta/transport/vless/encryption/client.go b/clash-meta/transport/vless/encryption/client.go index 8453ea613d..726e98d87c 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" "io" "net" @@ -13,7 +12,6 @@ import ( "time" "github.com/metacubex/utls/mlkem" - "golang.org/x/crypto/hkdf" "golang.org/x/sys/cpu" ) @@ -30,19 +28,20 @@ var ( var ClientCipher byte func init() { - if !HasAESGCMHardwareSupport { + if HasAESGCMHardwareSupport { ClientCipher = 1 } } type ClientInstance struct { sync.RWMutex - eKeyNfs *mlkem.EncapsulationKey768 - xor uint32 - minutes time.Duration - expire time.Time - baseKey []byte - ticket []byte + nfsEKey *mlkem.EncapsulationKey768 + nfsEKeyBytes []byte + xor uint32 + minutes time.Duration + expire time.Time + baseKey []byte + ticket []byte } type ClientConn struct { @@ -58,19 +57,22 @@ type ClientConn struct { peerCache []byte } -func (i *ClientInstance) Init(eKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) { - i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData) - i.xor = xor +func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) { + i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes) + if xor > 0 { + i.nfsEKeyBytes = nfsEKeyBytes + i.xor = xor + } i.minutes = minutes return } func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { - if i.eKeyNfs == nil { + if i.nfsEKey == nil { return nil, errors.New("uninitialized") } - if i.xor == 1 { - conn = NewXorConn(conn, i.eKeyNfs.Bytes()) + if i.xor > 0 { + conn = NewXorConn(conn, i.nfsEKeyBytes) } c := &ClientConn{Conn: conn} @@ -86,18 +88,19 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { i.RUnlock() } - nfsKey, encapsulatedNfsKey := i.eKeyNfs.Encapsulate() - seed := make([]byte, 64) - rand.Read(seed) - dKeyPfs, _ := mlkem.NewDecapsulationKey768(seed) - eKeyPfs := dKeyPfs.EncapsulationKey().Bytes() - padding := randBetween(100, 1000) + pfsDKeySeed := make([]byte, 64) + rand.Read(pfsDKeySeed) + pfsDKey, _ := mlkem.NewDecapsulationKey768(pfsDKeySeed) + pfsEKeyBytes := pfsDKey.EncapsulationKey().Bytes() + nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate() + paddingLen := randBetween(100, 1000) - clientHello := make([]byte, 1088+1184+1+5+padding) - copy(clientHello, encapsulatedNfsKey) - copy(clientHello[1088:], eKeyPfs) - clientHello[2272] = ClientCipher - encodeHeader(clientHello[2273:], int(padding)) + clientHello := make([]byte, 1+1184+1088+5+paddingLen) + clientHello[0] = ClientCipher + copy(clientHello[1:], pfsEKeyBytes) + copy(clientHello[1185:], encapsulatedNfsKey) + EncodeHeader(clientHello[2273:], int(paddingLen)) + rand.Read(clientHello[2278:]) if _, err := c.Conn.Write(clientHello); err != nil { return nil, err @@ -111,17 +114,15 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { encapsulatedPfsKey := peerServerHello[:1088] c.ticket = peerServerHello[1088:] - pfsKey, err := dKeyPfs.Decapsulate(encapsulatedPfsKey) + pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey) if err != nil { return nil, err } - c.baseKey = append(nfsKey, pfsKey...) + c.baseKey = append(pfsKey, nfsKey...) - authKey := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfs).Read(authKey) - nonce := make([]byte, 12) - VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce, c.ticket, encapsulatedPfsKey) - if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message + nonce := [12]byte{ClientCipher} + VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, nonce[:], c.ticket, pfsEKeyBytes) + if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more messages return nil, errors.New("invalid server") } @@ -141,32 +142,39 @@ func (c *ClientConn) Write(b []byte) (int, error) { return 0, nil } var data []byte - if c.aead == nil { - c.random = make([]byte, 32) - rand.Read(c.random) - key := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, c.random, c.ticket).Read(key) - c.aead = newAead(ClientCipher, key) - c.nonce = make([]byte, 12) - - data = make([]byte, 21+32+5+len(b)+16) - copy(data, c.ticket) - copy(data[21:], c.random) - encodeHeader(data[53:], len(b)+16) - c.aead.Seal(data[:58], c.nonce, b, data[53:58]) - } else { - data = make([]byte, 5+len(b)+16) - encodeHeader(data, len(b)+16) - c.aead.Seal(data[:5], c.nonce, b, data[:5]) - } - increaseNonce(c.nonce) - if _, err := c.Conn.Write(data); err != nil { - return 0, err + for n := 0; n < len(b); { + b := b[n:] + if len(b) > 8192 { + b = b[:8192] // for avoiding another copy() in server's Read() + } + n += len(b) + if c.aead == nil { + c.random = make([]byte, 32) + rand.Read(c.random) + c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket) + c.nonce = make([]byte, 12) + data = make([]byte, 21+32+5+len(b)+16) + copy(data, c.ticket) + copy(data[21:], c.random) + EncodeHeader(data[53:], len(b)+16) + c.aead.Seal(data[:58], c.nonce, b, data[53:58]) + } else { + data = make([]byte, 5+len(b)+16) + EncodeHeader(data, len(b)+16) + c.aead.Seal(data[:5], c.nonce, b, data[:5]) + if bytes.Equal(c.nonce, MaxNonce) { + c.aead = NewAead(ClientCipher, c.baseKey, data[5:], data[:5]) + } + } + IncreaseNonce(c.nonce) + if _, err := c.Conn.Write(data); err != nil { + return 0, err + } } return len(b), nil } -func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() +func (c *ClientConn) Read(b []byte) (int, error) { if len(b) == 0 { return 0, nil } @@ -177,11 +185,11 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return 0, err } - peerPadding, _ := decodeHeader(peerHeader) - if peerPadding == 0 { + peerPaddingLen, _ := DecodeHeader(peerHeader) + if peerPaddingLen == 0 { break } - if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil { + if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil { return 0, err } } @@ -196,11 +204,9 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() return 0, err } if c.random == nil { - return 0, errors.New("can not Read() first") + return 0, errors.New("empty c.random") } - peerKey := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey) - c.peerAead = newAead(ClientCipher, peerKey) + c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandom, c.random) c.peerNonce = make([]byte, 12) } if len(c.peerCache) != 0 { @@ -211,7 +217,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return 0, err } - peerLength, err := decodeHeader(peerHeader) // 17~17000 + peerLength, err := DecodeHeader(peerHeader) // 17~17000 if err != nil { if c.instance != nil { c.instance.Lock() @@ -228,10 +234,17 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() } dst := peerData[:peerLength-16] if len(dst) <= len(b) { - dst = b[:len(dst)] // max=8192 is recommended for peer + dst = b[:len(dst)] // avoids another copy() + } + var peerAead cipher.AEAD + if bytes.Equal(c.peerNonce, MaxNonce) { + peerAead = NewAead(ClientCipher, c.baseKey, peerData, peerHeader) } _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader) - increaseNonce(c.peerNonce) + if peerAead != nil { + c.peerAead = peerAead + } + IncreaseNonce(c.peerNonce) if err != nil { return 0, err } diff --git a/clash-meta/transport/vless/encryption/common.go b/clash-meta/transport/vless/encryption/common.go index 6b02f54bee..32222f1851 100644 --- a/clash-meta/transport/vless/encryption/common.go +++ b/clash-meta/transport/vless/encryption/common.go @@ -1,17 +1,22 @@ package encryption import ( + "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" + "crypto/sha256" "errors" "math/big" "strconv" "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/hkdf" ) -func encodeHeader(b []byte, l int) { +var MaxNonce = bytes.Repeat([]byte{255}, 12) + +func EncodeHeader(b []byte, l int) { b[0] = 23 b[1] = 3 b[2] = 3 @@ -19,10 +24,10 @@ func encodeHeader(b []byte, l int) { b[4] = byte(l) } -func decodeHeader(b []byte) (int, error) { +func DecodeHeader(b []byte) (int, error) { if b[0] == 23 && b[1] == 3 && b[2] == 3 { l := int(b[3])<<8 | int(b[4]) - if l < 17 || l > 17000 { // TODO + if l < 17 || l > 17000 { // TODO: TLSv1.3 max length return 0, errors.New("invalid length in record's header: " + strconv.Itoa(l)) } return l, nil @@ -30,29 +35,24 @@ func decodeHeader(b []byte) (int, error) { return 0, errors.New("invalid record's header") } -func newAead(c byte, k []byte) cipher.AEAD { - switch c { - case 0: - if block, err := aes.NewCipher(k); err == nil { - aead, _ := cipher.NewGCM(block) - return aead - } - case 1: - aead, _ := chacha20poly1305.New(k) - return aead +func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) { + key := make([]byte, 32) + hkdf.New(sha256.New, secret, salt, info).Read(key) + if c&1 == 1 { + block, _ := aes.NewCipher(key) + aead, _ = cipher.NewGCM(block) + } else { + aead, _ = chacha20poly1305.New(key) } - return nil + return } -func increaseNonce(nonce []byte) { +func IncreaseNonce(nonce []byte) { for i := 0; i < 12; i++ { nonce[11-i]++ if nonce[11-i] != 0 { break } - if i == 11 { - // TODO - } } } diff --git a/clash-meta/transport/vless/encryption/doc.go b/clash-meta/transport/vless/encryption/doc.go index af531f8b99..d6aeb93efd 100644 --- a/clash-meta/transport/vless/encryption/doc.go +++ b/clash-meta/transport/vless/encryption/doc.go @@ -2,4 +2,7 @@ // https://github.com/XTLS/Xray-core/commit/f61c14e9c63dc41a8a09135db3aea337974f3f37 // https://github.com/XTLS/Xray-core/commit/3e19bf9233bdd9bafc073a71c65b737cc1ffba5e // https://github.com/XTLS/Xray-core/commit/7ffb555fc8ec51bd1e3e60f26f1d6957984dba80 +// https://github.com/XTLS/Xray-core/commit/ec1cc35188c1a5f38a2ff75e88b5d043ffdc59da +// https://github.com/XTLS/Xray-core/commit/5c611420487a92f931faefc01d4bf03869f477f6 +// https://github.com/XTLS/Xray-core/commit/23d7aad461d232bc5bed52dd6aaa731ecd88ad35 package encryption diff --git a/clash-meta/transport/vless/encryption/factory.go b/clash-meta/transport/vless/encryption/factory.go new file mode 100644 index 0000000000..18b88c2e3e --- /dev/null +++ b/clash-meta/transport/vless/encryption/factory.go @@ -0,0 +1,99 @@ +package encryption + +import ( + "encoding/base64" + "fmt" + "strconv" + "strings" + "time" +) + +// NewClient new client from encryption string +// maybe return a nil *ClientInstance without any error, that means don't need to encrypt +func NewClient(encryption string) (*ClientInstance, error) { + switch encryption { + case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility + return nil, nil + } + if s := strings.SplitN(encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" { + var minutes uint32 + if s[0] != "1rtt" { + t := strings.TrimSuffix(s[0], "min") + if t == s[0] { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + i, err := strconv.Atoi(t) + if err != nil { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + minutes = uint32(i) + } + var xor uint32 + switch s[1] { + case "vless": + case "aes128xor": + xor = 1 + default: + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + b, err := base64.RawURLEncoding.DecodeString(s[3]) + if err != nil { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + if len(b) == MLKEM768ClientLength { + client := &ClientInstance{} + if err = client.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { + return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) + } + return client, nil + } else { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + } + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) +} + +// NewServer new server from decryption string +// maybe return a nil *ServerInstance without any error, that means don't need to decrypt +func NewServer(decryption string) (*ServerInstance, error) { + switch decryption { + case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility + return nil, nil + } + if s := strings.SplitN(decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" { + var minutes uint32 + if s[0] != "1rtt" { + t := strings.TrimSuffix(s[0], "min") + if t == s[0] { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + i, err := strconv.Atoi(t) + if err != nil { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + minutes = uint32(i) + } + var xor uint32 + switch s[1] { + case "vless": + case "aes128xor": + xor = 1 + default: + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + b, err := base64.RawURLEncoding.DecodeString(s[3]) + if err != nil { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + if len(b) == MLKEM768SeedLength { + server := &ServerInstance{} + if err = server.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { + return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) + } + return server, nil + } else { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + } + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) +} diff --git a/clash-meta/transport/vless/encryption/server.go b/clash-meta/transport/vless/encryption/server.go index 6874445960..b42db3fdc5 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" "io" "net" @@ -12,7 +11,6 @@ import ( "time" "github.com/metacubex/utls/mlkem" - "golang.org/x/crypto/hkdf" ) type ServerSession struct { @@ -24,11 +22,12 @@ type ServerSession struct { type ServerInstance struct { sync.RWMutex - dKeyNfs *mlkem.DecapsulationKey768 - xor uint32 - minutes time.Duration - sessions map[[21]byte]*ServerSession - stop bool + nfsDKey *mlkem.DecapsulationKey768 + nfsEKeyBytes []byte + xor uint32 + minutes time.Duration + sessions map[[21]byte]*ServerSession + closed bool } type ServerConn struct { @@ -44,9 +43,12 @@ type ServerConn struct { nonce []byte } -func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) { - i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData) - i.xor = xor +func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Duration) (err error) { + i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed) + if xor > 0 { + i.nfsEKeyBytes = i.nfsDKey.EncapsulationKey().Bytes() + i.xor = xor + } if minutes > 0 { i.minutes = minutes i.sessions = make(map[[21]byte]*ServerSession) @@ -55,12 +57,13 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat time.Sleep(time.Minute) now := time.Now() i.Lock() - if i.stop { + if i.closed { + i.Unlock() return } - for index, session := range i.sessions { + for ticket, session := range i.sessions { if now.After(session.expire) { - delete(i.sessions, index) + delete(i.sessions, ticket) } } i.Unlock() @@ -72,17 +75,17 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat func (i *ServerInstance) Close() (err error) { i.Lock() - defer i.Unlock() - i.stop = true + i.closed = true + i.Unlock() return } func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { - if i.dKeyNfs == nil { + if i.nfsDKey == nil { return nil, errors.New("uninitialized") } - if i.xor == 1 { - conn = NewXorConn(conn, i.dKeyNfs.EncapsulationKey().Bytes()) + if i.xor > 0 { + conn = NewXorConn(conn, i.nfsEKeyBytes) } c := &ServerConn{Conn: conn} @@ -109,50 +112,49 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return nil, err } - if l, _ := decodeHeader(peerHeader); l != 0 { - c.Conn.Write(make([]byte, randBetween(100, 1000))) // make client do new handshake + if l, _ := DecodeHeader(peerHeader); l != 0 { + noise := make([]byte, randBetween(100, 1000)) + rand.Read(noise) + c.Conn.Write(noise) // make client do new handshake return nil, errors.New("invalid ticket") } - peerClientHello := make([]byte, 1088+1184+1) + peerClientHello := make([]byte, 1+1184+1088) copy(peerClientHello, peerTicketHello) copy(peerClientHello[53:], peerHeader) if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil { return nil, err } - encapsulatedNfsKey := peerClientHello[:1088] - eKeyPfsData := peerClientHello[1088:2272] - c.cipher = peerClientHello[2272] - if c.cipher != 0 && c.cipher != 1 { - return nil, errors.New("invalid cipher") - } + c.cipher = peerClientHello[0] + pfsEKeyBytes := peerClientHello[1:1185] + encapsulatedNfsKey := peerClientHello[1185:2273] - nfsKey, err := i.dKeyNfs.Decapsulate(encapsulatedNfsKey) + pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes) if err != nil { return nil, err } - eKeyPfs, err := mlkem.NewEncapsulationKey768(eKeyPfsData) + nfsKey, err := i.nfsDKey.Decapsulate(encapsulatedNfsKey) if err != nil { return nil, err } - pfsKey, encapsulatedPfsKey := eKeyPfs.Encapsulate() - c.baseKey = append(nfsKey, pfsKey...) + pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate() + c.baseKey = append(pfsKey, nfsKey...) - authKey := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfsData).Read(authKey) - nonce := make([]byte, 12) - c.ticket = newAead(c.cipher, authKey).Seal(nil, nonce, []byte("VLESS"), encapsulatedPfsKey) + nonce := [12]byte{c.cipher} + c.ticket = NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, nonce[:], []byte("VLESS"), pfsEKeyBytes) - padding := randBetween(100, 1000) + paddingLen := randBetween(100, 1000) - serverHello := make([]byte, 1088+21+5+padding) + serverHello := make([]byte, 1088+21+5+paddingLen) copy(serverHello, encapsulatedPfsKey) copy(serverHello[1088:], c.ticket) - encodeHeader(serverHello[1109:], int(padding)) + EncodeHeader(serverHello[1109:], int(paddingLen)) + rand.Read(serverHello[1114:]) if _, err := c.Conn.Write(serverHello); err != nil { return nil, err } + // we can send more padding if needed if i.minutes > 0 { i.Lock() @@ -178,11 +180,11 @@ func (c *ServerConn) Read(b []byte) (int, error) { if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return 0, err } - peerPadding, _ := decodeHeader(peerHeader) - if peerPadding == 0 { + peerPaddingLen, _ := DecodeHeader(peerHeader) + if peerPaddingLen == 0 { break } - if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil { + if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil { return 0, err } } @@ -199,9 +201,7 @@ func (c *ServerConn) Read(b []byte) (int, error) { return 0, err } } - peerKey := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, c.peerRandom, c.ticket).Read(peerKey) - c.peerAead = newAead(c.cipher, peerKey) + c.peerAead = NewAead(c.cipher, c.baseKey, c.peerRandom, c.ticket) c.peerNonce = make([]byte, 12) } if len(c.peerCache) != 0 { @@ -212,7 +212,7 @@ func (c *ServerConn) Read(b []byte) (int, error) { if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return 0, err } - peerLength, err := decodeHeader(peerHeader) // 17~17000 + peerLength, err := DecodeHeader(peerHeader) // 17~17000 if err != nil { return 0, err } @@ -222,10 +222,17 @@ func (c *ServerConn) Read(b []byte) (int, error) { } dst := peerData[:peerLength-16] if len(dst) <= len(b) { - dst = b[:len(dst)] // max=8192 is recommended for peer + dst = b[:len(dst)] // avoids another copy() + } + var peerAead cipher.AEAD + if bytes.Equal(c.peerNonce, MaxNonce) { + peerAead = NewAead(c.cipher, c.baseKey, peerData, peerHeader) } _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader) - increaseNonce(c.peerNonce) + if peerAead != nil { + c.peerAead = peerAead + } + IncreaseNonce(c.peerNonce) if err != nil { return 0, errors.New("error") } @@ -236,31 +243,39 @@ func (c *ServerConn) Read(b []byte) (int, error) { return len(dst), nil } -func (c *ServerConn) Write(b []byte) (int, error) { // after first Read() +func (c *ServerConn) Write(b []byte) (int, error) { if len(b) == 0 { return 0, nil } var data []byte - if c.aead == nil { - if c.peerRandom == nil { - return 0, errors.New("can not Write() first") + for n := 0; n < len(b); { + b := b[n:] + if len(b) > 8192 { + b = b[:8192] // for avoiding another copy() in client's Read() + } + n += len(b) + if c.aead == nil { + if c.peerRandom == nil { + return 0, errors.New("empty c.peerRandom") + } + data = make([]byte, 32+5+len(b)+16) + rand.Read(data[:32]) + c.aead = NewAead(c.cipher, c.baseKey, data[:32], c.peerRandom) + c.nonce = make([]byte, 12) + EncodeHeader(data[32:], len(b)+16) + c.aead.Seal(data[:37], c.nonce, b, data[32:37]) + } else { + data = make([]byte, 5+len(b)+16) + EncodeHeader(data, len(b)+16) + c.aead.Seal(data[:5], c.nonce, b, data[:5]) + if bytes.Equal(c.nonce, MaxNonce) { + c.aead = NewAead(c.cipher, c.baseKey, data[5:], data[:5]) + } + } + IncreaseNonce(c.nonce) + if _, err := c.Conn.Write(data); err != nil { + return 0, err } - data = make([]byte, 32+5+len(b)+16) - rand.Read(data[:32]) - key := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, data[:32], c.peerRandom).Read(key) - c.aead = newAead(c.cipher, key) - c.nonce = make([]byte, 12) - encodeHeader(data[32:], len(b)+16) - c.aead.Seal(data[:37], c.nonce, b, data[32:37]) - } else { - data = make([]byte, 5+len(b)+16) - encodeHeader(data, len(b)+16) - c.aead.Seal(data[:5], c.nonce, b, data[:5]) - } - increaseNonce(c.nonce) - if _, err := c.Conn.Write(data); err != nil { - return 0, err } return len(b), nil } diff --git a/clash-meta/transport/vless/encryption/xor.go b/clash-meta/transport/vless/encryption/xor.go index c8af2112d1..296fdd637a 100644 --- a/clash-meta/transport/vless/encryption/xor.go +++ b/clash-meta/transport/vless/encryption/xor.go @@ -38,7 +38,7 @@ func (c *XorConn) Write(b []byte) (int, error) { return 0, err } if iv != nil { - b = b[16:] + b = b[16:] // for len(b) } return len(b), nil } diff --git a/clash-nyanpasu/frontend/interface/package.json b/clash-nyanpasu/frontend/interface/package.json index b661893ced..b40f11fab3 100644 --- a/clash-nyanpasu/frontend/interface/package.json +++ b/clash-nyanpasu/frontend/interface/package.json @@ -22,6 +22,6 @@ }, "devDependencies": { "@types/lodash-es": "4.17.12", - "@types/react": "19.1.9" + "@types/react": "19.1.10" } } diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index a0180324b1..2995820881 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -59,9 +59,9 @@ "@iconify/json": "2.2.371", "@monaco-editor/react": "4.7.0", "@tanstack/react-query": "5.84.2", - "@tanstack/react-router": "1.131.3", - "@tanstack/react-router-devtools": "1.131.3", - "@tanstack/router-plugin": "1.131.3", + "@tanstack/react-router": "1.131.5", + "@tanstack/react-router-devtools": "1.131.5", + "@tanstack/router-plugin": "1.131.5", "@tauri-apps/plugin-clipboard-manager": "2.3.0", "@tauri-apps/plugin-dialog": "2.3.0", "@tauri-apps/plugin-fs": "2.4.0", @@ -70,7 +70,7 @@ "@tauri-apps/plugin-process": "2.3.0", "@tauri-apps/plugin-shell": "2.3.0", "@tauri-apps/plugin-updater": "2.9.0", - "@types/react": "19.1.9", + "@types/react": "19.1.10", "@types/react-dom": "19.1.7", "@types/validator": "13.15.2", "@vitejs/plugin-legacy": "7.2.1", diff --git a/clash-nyanpasu/frontend/ui/package.json b/clash-nyanpasu/frontend/ui/package.json index 5bffd06648..f919aec9a4 100644 --- a/clash-nyanpasu/frontend/ui/package.json +++ b/clash-nyanpasu/frontend/ui/package.json @@ -19,7 +19,7 @@ "@radix-ui/react-scroll-area": "1.2.9", "@tauri-apps/api": "2.6.0", "@types/d3": "7.4.3", - "@types/react": "19.1.9", + "@types/react": "19.1.10", "@vitejs/plugin-react": "4.7.0", "ahooks": "3.9.0", "d3": "7.9.0", diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 5806920903..3762c29ecd 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-3a0d267", + "mihomo_alpha": "alpha-eca5a27", "clash_rs": "v0.8.2", "clash_premium": "2023-09-05-gdcc8d87", - "clash_rs_alpha": "0.8.2-alpha+sha.ec9134e" + "clash_rs_alpha": "0.8.2-alpha+sha.df9f591" }, "arch_template": { "mihomo": { @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-08-10T22:21:15.754Z" + "updated_at": "2025-08-11T22:21:19.847Z" } diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 3c01034409..ac663307b6 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -204,8 +204,8 @@ importers: specifier: 4.17.12 version: 4.17.12 '@types/react': - specifier: 19.1.9 - version: 19.1.9 + specifier: 19.1.10 + version: 19.1.10 frontend/nyanpasu: dependencies: @@ -220,7 +220,7 @@ importers: version: 3.2.2(react@19.1.1) '@emotion/styled': specifier: 11.14.1 - version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) + version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) '@juggle/resize-observer': specifier: 3.4.0 version: 3.4.0 @@ -229,16 +229,16 @@ importers: version: 0.3.0 '@mui/icons-material': specifier: 7.3.1 - version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) + version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) '@mui/lab': specifier: 7.0.0-beta.16 - version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@mui/material': specifier: 7.3.1 - version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@mui/x-date-pickers': specifier: 8.10.0 - version: 8.10.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 8.10.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@nyanpasu/interface': specifier: workspace:^ version: link:../interface @@ -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.3(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.5(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 @@ -280,19 +280,19 @@ importers: version: 25.3.4(typescript@5.9.2) jotai: specifier: 2.13.1 - version: 2.13.1(@babel/core@7.28.0)(@babel/template@7.27.2)(@types/react@19.1.9)(react@19.1.1) + version: 2.13.1(@babel/core@7.28.0)(@babel/template@7.27.2)(@types/react@19.1.10)(react@19.1.1) json-schema: specifier: 0.4.0 version: 0.4.0 material-react-table: specifier: npm:@greenhat616/material-react-table@4.0.0 - version: '@greenhat616/material-react-table@4.0.0(ee367b75520587f2d436c002311f838b)' + version: '@greenhat616/material-react-table@4.0.0(098023d060c5c1c6a96afb3bdaa051b0)' monaco-editor: specifier: 0.52.2 version: 0.52.2 mui-color-input: specifier: 7.0.0 - version: 7.0.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.0.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: specifier: 19.1.1 version: 19.1.1 @@ -307,13 +307,13 @@ importers: version: 1.6.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-hook-form-mui: specifier: 7.6.2 - version: 7.6.2(dc790e3d871a3fe7e1d22dc6e321e397) + version: 7.6.2(aea177882beb7723aeada5c99e57089b) react-i18next: specifier: 15.6.1 version: 15.6.1(i18next@25.3.4(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2) react-markdown: specifier: 10.1.0 - version: 10.1.0(@types/react@19.1.9)(react@19.1.1) + version: 10.1.0(@types/react@19.1.10)(react@19.1.1) react-split-grid: specifier: 1.0.4 version: 1.0.4(react@19.1.1) @@ -341,7 +341,7 @@ importers: version: 11.13.5 '@emotion/react': specifier: 11.14.0 - version: 11.14.0(@types/react@19.1.9)(react@19.1.1) + version: 11.14.0(@types/react@19.1.10)(react@19.1.1) '@iconify/json': specifier: 2.2.371 version: 2.2.371 @@ -352,14 +352,14 @@ importers: specifier: 5.84.2 version: 5.84.2(react@19.1.1) '@tanstack/react-router': - specifier: 1.131.3 - version: 1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.131.5 + version: 1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/react-router-devtools': - specifier: 1.131.3 - version: 1.131.3(@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.3)(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.5 + version: 1.131.5(@tanstack/react-router@1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.5)(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.3 - version: 1.131.3(@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.1(@types/node@22.17.1)(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.3)(yaml@2.8.0)) + specifier: 1.131.5 + version: 1.131.5(@tanstack/react-router@1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.1(@types/node@22.17.1)(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.3)(yaml@2.8.0)) '@tauri-apps/plugin-clipboard-manager': specifier: 2.3.0 version: 2.3.0 @@ -385,11 +385,11 @@ importers: specifier: 2.9.0 version: 2.9.0 '@types/react': - specifier: 19.1.9 - version: 19.1.9 + specifier: 19.1.10 + version: 19.1.10 '@types/react-dom': specifier: 19.1.7 - version: 19.1.7(@types/react@19.1.9) + version: 19.1.7(@types/react@19.1.10) '@types/validator': specifier: 13.15.2 version: 13.15.2 @@ -464,19 +464,19 @@ importers: version: 0.3.0 '@mui/icons-material': specifier: 7.3.1 - version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) + version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) '@mui/lab': specifier: 7.0.0-beta.16 - version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@mui/material': specifier: 7.3.1 - version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-portal': specifier: 1.1.9 - version: 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-scroll-area': specifier: 1.2.9 - version: 1.2.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.2.9(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tauri-apps/api': specifier: 2.6.0 version: 2.6.0 @@ -484,8 +484,8 @@ importers: specifier: 7.4.3 version: 7.4.3 '@types/react': - specifier: 19.1.9 - version: 19.1.9 + specifier: 19.1.10 + version: 19.1.10 '@vitejs/plugin-react': specifier: 4.7.0 version: 4.7.0(vite@7.1.1(@types/node@22.17.1)(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.3)(yaml@2.8.0)) @@ -525,7 +525,7 @@ importers: devDependencies: '@emotion/react': specifier: 11.14.0 - version: 11.14.0(@types/react@19.1.9)(react@19.1.1) + version: 11.14.0(@types/react@19.1.10)(react@19.1.1) '@types/d3-interpolate-path': specifier: 2.0.3 version: 2.0.3 @@ -2939,16 +2939,16 @@ packages: peerDependencies: react: ^18 || ^19 - '@tanstack/react-router-devtools@1.131.3': - resolution: {integrity: sha512-opouR8dbBrDnkHdiyTVPb3rTfSRxWN+ZGi1YuEXNGUXgirvnpQUTWNP6kzDv1at29DxymwwfRRYD602MjZOOlA==} + '@tanstack/react-router-devtools@1.131.5': + resolution: {integrity: sha512-3LaEbWDYGnzw4J8DM7KX32qRslGrSt67Cg7uoAYReBfDlLpWVRnjpyG2fef7nHDqn5HaAuV0IbY1n6Duwp5IyQ==} engines: {node: '>=12'} peerDependencies: - '@tanstack/react-router': ^1.131.3 + '@tanstack/react-router': ^1.131.5 react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router@1.131.3': - resolution: {integrity: sha512-1wxsStYJai0/ssOQeO+8mh5VmMmJWIZOtIEEqDxIhQX4dHyurKl6khl34+qLtDvWFsj6zgiMwjID3GQj5SMz1w==} + '@tanstack/react-router@1.131.5': + resolution: {integrity: sha512-71suJGuCmrHN9PLLRUDB3CGnW5RNcEEfgfX616TOpKamHs977H8P4/75BgWPRWcLHCga/1kkA6c7bddCwZ35Fw==} engines: {node: '>=12'} peerDependencies: react: '>=18.0.0 || >=19.0.0' @@ -2973,15 +2973,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.3': - resolution: {integrity: sha512-8sby4n2pgRJ7qZrFvuS6v21oBDHCRoFM8p+/HAIra2d32cD5wS5k9DeNltWFUAJFXdsdwpdGVA0iXSAc0WfIHw==} + '@tanstack/router-core@1.131.5': + resolution: {integrity: sha512-XVfZdnKNQbWfkQ6G7I9ml2wHp98Wy7wgTboP5SfrJHfOE+kPeHeZRJqF/pp5oqLZ2feBJqsDDKNWo9323L7sWQ==} engines: {node: '>=12'} - '@tanstack/router-devtools-core@1.131.3': - resolution: {integrity: sha512-GQHVCE0ywJ0ajz9K52RLAhbDSuziD3MVRYYp5S5w2i31Mb9xkzITJe/CZ7WIgOuY+XQm18P8drbJnxdHTNOcWQ==} + '@tanstack/router-devtools-core@1.131.5': + resolution: {integrity: sha512-kH3cZz7UfnVQW9vMZJ/CAx15pu+iGkn10N4rRKBVWCEJZFPX3GZvYEwkeALHASsV0Io7yUvKDcWfPsc+UowyzQ==} engines: {node: '>=12'} peerDependencies: - '@tanstack/router-core': ^1.131.3 + '@tanstack/router-core': ^1.131.5 csstype: ^3.0.10 solid-js: '>=1.9.5' tiny-invariant: ^1.3.3 @@ -2989,16 +2989,16 @@ packages: csstype: optional: true - '@tanstack/router-generator@1.131.3': - resolution: {integrity: sha512-sTsi9lSxBCpAExQWyh3k2X40biiRHjIRISxVvko5sPG/+NKYFjGaQwgXQ/1jiDSTqe8i7/b/2l94ZJPTa6VPxQ==} + '@tanstack/router-generator@1.131.5': + resolution: {integrity: sha512-5+/zyp/R9WN8tHNVIEYQZpRMzcsOrNH06HoPnPMiLiB9T4WsOLFJCcHdyso9ofGQq+hoxB4M9SUBXVBbJVWbSw==} engines: {node: '>=12'} - '@tanstack/router-plugin@1.131.3': - resolution: {integrity: sha512-PLCjxTTHBf5H9TqH+jTNvgSnnCZhvrLj61C5XAtONA7NUv+Lh4xJ/u0nn83ZYb7uFM4rMg1JmpU5mG8iNbGHZw==} + '@tanstack/router-plugin@1.131.5': + resolution: {integrity: sha512-Px+GSijNv1cbSm74+U+kEbuSFsspL+/BakzytAJFdh2O4342G32tha1cMFMlzXbDd9SW1FaLWgu3VqNHjGIpOg==} engines: {node: '>=12'} peerDependencies: '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.131.3 + '@tanstack/react-router': ^1.131.5 vite: '>=5.0.0 || >=6.0.0' vite-plugin-solid: ^2.11.2 webpack: '>=5.92.0' @@ -3360,8 +3360,8 @@ packages: peerDependencies: '@types/react': '*' - '@types/react@19.1.9': - resolution: {integrity: sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==} + '@types/react@19.1.10': + resolution: {integrity: sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==} '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} @@ -9728,7 +9728,7 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1)': + '@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1)': dependencies: '@babel/runtime': 7.26.0 '@emotion/babel-plugin': 11.13.5 @@ -9740,7 +9740,7 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 19.1.1 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 transitivePeerDependencies: - supports-color @@ -9754,18 +9754,18 @@ snapshots: '@emotion/sheet@1.4.0': {} - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)': + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)': dependencies: '@babel/runtime': 7.27.6 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.3.0 - '@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1) '@emotion/serialize': 1.3.3 '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1) '@emotion/utils': 1.4.2 react: 19.1.1 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 transitivePeerDependencies: - supports-color @@ -9911,13 +9911,13 @@ snapshots: '@fastify/busboy@2.1.1': {} - '@greenhat616/material-react-table@4.0.0(ee367b75520587f2d436c002311f838b)': + '@greenhat616/material-react-table@4.0.0(098023d060c5c1c6a96afb3bdaa051b0)': dependencies: - '@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) - '@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@mui/x-date-pickers': 8.10.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) + '@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/x-date-pickers': 8.10.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/match-sorter-utils': 8.19.4 '@tanstack/react-table': 8.21.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/react-virtual': 3.13.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -10059,39 +10059,39 @@ snapshots: '@mui/core-downloads-tracker@7.3.1': {} - '@mui/icons-material@7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)': + '@mui/icons-material@7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 - '@mui/lab@7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@mui/lab@7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) - '@mui/types': 7.4.5(@types/react@19.1.9) - '@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) + '@mui/types': 7.4.5(@types/react@19.1.10) + '@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1) clsx: 2.1.1 prop-types: 15.8.1 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) - '@types/react': 19.1.9 + '@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) + '@types/react': 19.1.10 - '@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 '@mui/core-downloads-tracker': 7.3.1 - '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) - '@mui/types': 7.4.5(@types/react@19.1.9) - '@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1) + '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) + '@mui/types': 7.4.5(@types/react@19.1.10) + '@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1) '@popperjs/core': 2.11.8 - '@types/react-transition-group': 4.4.12(@types/react@19.1.9) + '@types/react-transition-group': 4.4.12(@types/react@19.1.10) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 @@ -10100,20 +10100,20 @@ snapshots: react-is: 19.1.1 react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) - '@types/react': 19.1.9 + '@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) + '@types/react': 19.1.10 - '@mui/private-theming@7.3.1(@types/react@19.1.9)(react@19.1.1)': + '@mui/private-theming@7.3.1(@types/react@19.1.10)(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1) + '@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1) prop-types: 15.8.1 react: 19.1.1 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 - '@mui/styled-engine@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(react@19.1.1)': + '@mui/styled-engine@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 '@emotion/cache': 11.14.0 @@ -10123,67 +10123,67 @@ snapshots: prop-types: 15.8.1 react: 19.1.1 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) - '@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)': + '@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/private-theming': 7.3.1(@types/react@19.1.9)(react@19.1.1) - '@mui/styled-engine': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(react@19.1.1) - '@mui/types': 7.4.5(@types/react@19.1.9) - '@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1) + '@mui/private-theming': 7.3.1(@types/react@19.1.10)(react@19.1.1) + '@mui/styled-engine': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(react@19.1.1) + '@mui/types': 7.4.5(@types/react@19.1.10) + '@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 react: 19.1.1 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) - '@types/react': 19.1.9 + '@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) + '@types/react': 19.1.10 - '@mui/types@7.4.5(@types/react@19.1.9)': + '@mui/types@7.4.5(@types/react@19.1.10)': dependencies: '@babel/runtime': 7.28.2 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 - '@mui/utils@7.3.1(@types/react@19.1.9)(react@19.1.1)': + '@mui/utils@7.3.1(@types/react@19.1.10)(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/types': 7.4.5(@types/react@19.1.9) + '@mui/types': 7.4.5(@types/react@19.1.10) '@types/prop-types': 15.7.15 clsx: 2.1.1 prop-types: 15.8.1 react: 19.1.1 react-is: 19.1.1 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 - '@mui/x-date-pickers@8.10.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@mui/x-date-pickers@8.10.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) - '@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1) - '@mui/x-internals': 8.10.0(@types/react@19.1.9)(react@19.1.1) - '@types/react-transition-group': 4.4.12(@types/react@19.1.9) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) + '@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1) + '@mui/x-internals': 8.10.0(@types/react@19.1.10)(react@19.1.1) + '@types/react-transition-group': 4.4.12(@types/react@19.1.10) clsx: 2.1.1 prop-types: 15.8.1 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) dayjs: 1.11.13 transitivePeerDependencies: - '@types/react' - '@mui/x-internals@8.10.0(@types/react@19.1.9)(react@19.1.1)': + '@mui/x-internals@8.10.0(@types/react@19.1.10)(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1) + '@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1) react: 19.1.1 reselect: 5.1.1 use-sync-external-store: 1.5.0(react@19.1.1) @@ -10578,88 +10578,88 @@ snapshots: '@radix-ui/primitive@1.1.2': {} - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.9)(react@19.1.1)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.10)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 - '@radix-ui/react-context@1.1.2(@types/react@19.1.9)(react@19.1.1)': + '@radix-ui/react-context@1.1.2(@types/react@19.1.10)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 - '@radix-ui/react-direction@1.1.1(@types/react@19.1.9)(react@19.1.1)': + '@radix-ui/react-direction@1.1.1(@types/react@19.1.10)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.10)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.9 - '@types/react-dom': 19.1.7(@types/react@19.1.9) + '@types/react': 19.1.10 + '@types/react-dom': 19.1.7(@types/react@19.1.10) - '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.10)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.10)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.9 - '@types/react-dom': 19.1.7(@types/react@19.1.9) + '@types/react': 19.1.10 + '@types/react-dom': 19.1.7(@types/react@19.1.10) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.10)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.9 - '@types/react-dom': 19.1.7(@types/react@19.1.9) + '@types/react': 19.1.10 + '@types/react-dom': 19.1.7(@types/react@19.1.10) - '@radix-ui/react-scroll-area@1.2.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-scroll-area@1.2.9(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.10)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.10)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.10)(react@19.1.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.10)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.10)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.9 - '@types/react-dom': 19.1.7(@types/react@19.1.9) + '@types/react': 19.1.10 + '@types/react-dom': 19.1.7(@types/react@19.1.10) - '@radix-ui/react-slot@1.2.3(@types/react@19.1.9)(react@19.1.1)': + '@radix-ui/react-slot@1.2.3(@types/react@19.1.10)(react@19.1.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.10)(react@19.1.1) react: 19.1.1 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.9)(react@19.1.1)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.10)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.9)(react@19.1.1)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.10)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 '@rolldown/pluginutils@1.0.0-beta.27': {} @@ -11040,10 +11040,10 @@ snapshots: '@tanstack/query-core': 5.83.1 react: 19.1.1 - '@tanstack/react-router-devtools@1.131.3(@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.3)(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.5(@tanstack/react-router@1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.5)(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.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/router-devtools-core': 1.131.3(@tanstack/router-core@1.131.3)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) + '@tanstack/react-router': 1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tanstack/router-devtools-core': 1.131.5(@tanstack/router-core@1.131.5)(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: @@ -11052,11 +11052,11 @@ snapshots: - solid-js - tiny-invariant - '@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@tanstack/react-router@1.131.5(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.3 + '@tanstack/router-core': 1.131.5 isbot: 5.1.28 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -11082,7 +11082,7 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@tanstack/router-core@1.131.3': + '@tanstack/router-core@1.131.5': dependencies: '@tanstack/history': 1.131.2 '@tanstack/store': 0.7.0 @@ -11092,9 +11092,9 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/router-devtools-core@1.131.3(@tanstack/router-core@1.131.3)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)': + '@tanstack/router-devtools-core@1.131.5(@tanstack/router-core@1.131.5)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)': dependencies: - '@tanstack/router-core': 1.131.3 + '@tanstack/router-core': 1.131.5 clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) solid-js: 1.9.5 @@ -11102,9 +11102,9 @@ snapshots: optionalDependencies: csstype: 3.1.3 - '@tanstack/router-generator@1.131.3': + '@tanstack/router-generator@1.131.5': dependencies: - '@tanstack/router-core': 1.131.3 + '@tanstack/router-core': 1.131.5 '@tanstack/router-utils': 1.131.2 '@tanstack/virtual-file-routes': 1.131.2 prettier: 3.6.2 @@ -11115,7 +11115,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.131.3(@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.1(@types/node@22.17.1)(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.3)(yaml@2.8.0))': + '@tanstack/router-plugin@1.131.5(@tanstack/react-router@1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.1(@types/node@22.17.1)(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.3)(yaml@2.8.0))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) @@ -11123,8 +11123,8 @@ snapshots: '@babel/template': 7.27.2 '@babel/traverse': 7.28.0 '@babel/types': 7.28.1 - '@tanstack/router-core': 1.131.3 - '@tanstack/router-generator': 1.131.3 + '@tanstack/router-core': 1.131.5 + '@tanstack/router-generator': 1.131.5 '@tanstack/router-utils': 1.131.2 '@tanstack/virtual-file-routes': 1.131.2 babel-dead-code-elimination: 1.0.10 @@ -11132,7 +11132,7 @@ snapshots: unplugin: 2.3.5 zod: 3.25.76 optionalDependencies: - '@tanstack/react-router': 1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tanstack/react-router': 1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) vite: 7.1.1(@types/node@22.17.1)(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.3)(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -11148,9 +11148,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.131.3(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.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(zod@4.0.17)': dependencies: - '@tanstack/react-router': 1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tanstack/react-router': 1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) zod: 4.0.17 '@tanstack/store@0.7.0': {} @@ -11496,15 +11496,15 @@ snapshots: '@types/prop-types@15.7.15': {} - '@types/react-dom@19.1.7(@types/react@19.1.9)': + '@types/react-dom@19.1.7(@types/react@19.1.10)': dependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 - '@types/react-transition-group@4.4.12(@types/react@19.1.9)': + '@types/react-transition-group@4.4.12(@types/react@19.1.10)': dependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 - '@types/react@19.1.9': + '@types/react@19.1.10': dependencies: csstype: 3.1.3 @@ -14464,11 +14464,11 @@ snapshots: jju@1.4.0: {} - jotai@2.13.1(@babel/core@7.28.0)(@babel/template@7.27.2)(@types/react@19.1.9)(react@19.1.1): + jotai@2.13.1(@babel/core@7.28.0)(@babel/template@7.27.2)(@types/react@19.1.10)(react@19.1.1): optionalDependencies: '@babel/core': 7.28.0 '@babel/template': 7.27.2 - '@types/react': 19.1.9 + '@types/react': 19.1.10 react: 19.1.1 js-cookie@2.2.1: {} @@ -15109,16 +15109,16 @@ snapshots: muggle-string@0.4.1: {} - mui-color-input@7.0.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + mui-color-input@7.0.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@ctrl/tinycolor': 4.1.0 - '@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.9 + '@types/react': 19.1.10 nano-css@5.6.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: @@ -15704,14 +15704,14 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - react-hook-form-mui@7.6.2(dc790e3d871a3fe7e1d22dc6e321e397): + react-hook-form-mui@7.6.2(aea177882beb7723aeada5c99e57089b): dependencies: - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 react-hook-form: 7.52.1(react@19.1.1) optionalDependencies: - '@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react@19.1.1) - '@mui/x-date-pickers': 8.10.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1) + '@mui/x-date-pickers': 8.10.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-hook-form@7.52.1(react@19.1.1): dependencies: @@ -15731,11 +15731,11 @@ snapshots: react-is@19.1.1: {} - react-markdown@10.1.0(@types/react@19.1.9)(react@19.1.1): + react-markdown@10.1.0(@types/react@19.1.10)(react@19.1.1): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.3 - '@types/react': 19.1.9 + '@types/react': 19.1.10 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.0 html-url-attributes: 3.0.0 diff --git a/lede/target/imagebuilder/files/Makefile b/lede/target/imagebuilder/files/Makefile index 275932577b..5aeedb74da 100644 --- a/lede/target/imagebuilder/files/Makefile +++ b/lede/target/imagebuilder/files/Makefile @@ -51,6 +51,7 @@ image: make image EXTRA_IMAGE_NAME="" # Add this to the output image filename (sanitized) make image DISABLED_SERVICES=" [ [ ..]]" # Which services in /etc/init.d/ should be disabled make image ADD_LOCAL_KEY=1 # store locally generated signing key in built images + make image ROOTFS_PARTSIZE="" # override the default rootfs partition size in MegaBytes manifest: List "all" packages which get installed into the image. @@ -261,7 +262,8 @@ image: $(if $(FILES),USER_FILES="$(FILES)") \ $(if $(PACKAGES),USER_PACKAGES="$(PACKAGES)") \ $(if $(BIN_DIR),BIN_DIR="$(BIN_DIR)") \ - $(if $(DISABLED_SERVICES),DISABLED_SERVICES="$(DISABLED_SERVICES)")) + $(if $(DISABLED_SERVICES),DISABLED_SERVICES="$(DISABLED_SERVICES)") \ + $(if $(ROOTFS_PARTSIZE),CONFIG_TARGET_ROOTFS_PARTSIZE="$(ROOTFS_PARTSIZE)")) manifest: FORCE $(MAKE) -s _check_profile diff --git a/mieru/pkg/testtool/pipe.go b/mieru/pkg/testtool/pipe.go index ce2705c59b..7739a4c248 100644 --- a/mieru/pkg/testtool/pipe.go +++ b/mieru/pkg/testtool/pipe.go @@ -17,12 +17,10 @@ package testtool import ( "bytes" - "errors" "io" - mrand "math/rand" "net" - "runtime" "sync" + "sync/atomic" "time" "github.com/enfein/mieru/v3/pkg/common" @@ -32,12 +30,17 @@ import ( func BufPipe() (net.Conn, net.Conn) { var buf1, buf2 bytes.Buffer var lock1, lock2 sync.Mutex + cond1 := sync.NewCond(&lock1) // endpoint 1 has data to read + cond2 := sync.NewCond(&lock2) // endpoint 2 has data to read + ep1 := &ioEndpoint{ direction: forward, buf1: &buf1, buf2: &buf2, lock1: &lock1, lock2: &lock2, + cond1: cond1, + cond2: cond2, } ep2 := &ioEndpoint{ direction: backward, @@ -45,7 +48,11 @@ func BufPipe() (net.Conn, net.Conn) { buf2: &buf2, lock1: &lock1, lock2: &lock2, + cond1: cond1, + cond2: cond2, } + ep1.peer = ep2 + ep2.peer = ep1 return ep1, ep2 } @@ -62,56 +69,83 @@ type ioEndpoint struct { buf2 *bytes.Buffer // backward writes to here lock1 *sync.Mutex // lock of buf1 lock2 *sync.Mutex // lock of buf2 - closed bool + cond1 *sync.Cond + cond2 *sync.Cond + closed atomic.Bool + peer *ioEndpoint } var _ net.Conn = &ioEndpoint{} func (e *ioEndpoint) Read(b []byte) (n int, err error) { - if e.closed { + if e.closed.Load() { return 0, io.EOF } + + var buffer *bytes.Buffer + var lock *sync.Mutex + var cond *sync.Cond + if e.direction == forward { - e.lock2.Lock() - n, err = e.buf2.Read(b) - e.lock2.Unlock() + buffer = e.buf2 + lock = e.lock2 + cond = e.cond2 } else { - e.lock1.Lock() - n, err = e.buf1.Read(b) - e.lock1.Unlock() + buffer = e.buf1 + lock = e.lock1 + cond = e.cond1 } - if errors.Is(err, io.EOF) { - // io.ReadFull() with partial result will not fail. - err = nil - action := mrand.Intn(2) - if action == 0 { - // Allow the writer to catch up. - runtime.Gosched() - } else { - time.Sleep(time.Microsecond) + + lock.Lock() + defer lock.Unlock() + + for buffer.Len() == 0 { + if e.closed.Load() || e.peer.closed.Load() { + return 0, io.EOF } + cond.Wait() } - return + + return buffer.Read(b) } func (e *ioEndpoint) Write(b []byte) (n int, err error) { - if e.closed { + if e.closed.Load() { return 0, io.ErrClosedPipe } + + var buffer *bytes.Buffer + var lock *sync.Mutex + var cond *sync.Cond + if e.direction == forward { - e.lock1.Lock() - n, err = e.buf1.Write(b) - e.lock1.Unlock() + buffer = e.buf1 + lock = e.lock1 + cond = e.cond1 } else { - e.lock2.Lock() - n, err = e.buf2.Write(b) - e.lock2.Unlock() + buffer = e.buf2 + lock = e.lock2 + cond = e.cond2 } + + lock.Lock() + defer lock.Unlock() + + if e.peer.closed.Load() { + return 0, io.ErrClosedPipe + } + + n, err = buffer.Write(b) + cond.Signal() return } func (e *ioEndpoint) Close() error { - e.closed = true + if e.closed.Swap(true) { + return nil + } + e.cond1.Broadcast() + e.cond2.Broadcast() return nil } diff --git a/mihomo/adapter/outbound/vless.go b/mihomo/adapter/outbound/vless.go index de3c24ab75..b26ddb07f2 100644 --- a/mihomo/adapter/outbound/vless.go +++ b/mihomo/adapter/outbound/vless.go @@ -3,14 +3,11 @@ package outbound import ( "context" "crypto/tls" - "encoding/base64" "errors" "fmt" "net" "net/http" "strconv" - "strings" - "time" "github.com/metacubex/mihomo/common/convert" N "github.com/metacubex/mihomo/common/net" @@ -456,41 +453,11 @@ func NewVless(option VlessOption) (*Vless, error) { option: &option, } - if s := strings.SplitN(option.Encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" { - var minutes uint32 - if s[0] != "1rtt" { - t := strings.TrimSuffix(s[0], "min") - if t == s[0] { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - var i int - i, err = strconv.Atoi(t) - if err != nil { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - minutes = uint32(i) - } - var xor uint32 - switch s[1] { - case "vless": - case "aes128xor": - xor = 1 - default: - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - var b []byte - b, err = base64.RawURLEncoding.DecodeString(s[3]) - if err != nil { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - if len(b) == encryption.MLKEM768ClientLength { - v.encryption = &encryption.ClientInstance{} - if err = v.encryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { - return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) - } - } else { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } + v.encryption, err = encryption.NewClient(option.Encryption) + if err != nil { + return nil, err + } + if v.encryption != nil { if option.Flow != "" { return nil, errors.New(`vless "encryption" doesn't support "flow" yet`) } diff --git a/mihomo/listener/inbound/common_test.go b/mihomo/listener/inbound/common_test.go index 5b8a6f17e6..5b838bd303 100644 --- a/mihomo/listener/inbound/common_test.go +++ b/mihomo/listener/inbound/common_test.go @@ -10,11 +10,13 @@ import ( "net" "net/http" "net/netip" + "strconv" "sync" "testing" "time" N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" @@ -30,7 +32,7 @@ import ( ) var httpPath = "/inbound_test" -var httpData = make([]byte, 10240) +var httpData = make([]byte, 2*pool.RelayBufferSize) var remoteAddr = netip.MustParseAddr("1.2.3.4") var userUUID = utils.NewUUIDV4().String() var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256) @@ -134,14 +136,21 @@ func NewHttpTestTunnel() *TestTunnel { r := chi.NewRouter() r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) { - render.Data(w, r, httpData) + query := r.URL.Query() + size, err := strconv.Atoi(query.Get("size")) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.PlainText(w, r, err.Error()) + return + } + render.Data(w, r, httpData[:size]) }) h2Server := &http2.Server{} server := http.Server{Handler: r} _ = http2.ConfigureServer(&server, h2Server) go server.Serve(ln) - testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil) + testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string, size int) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s?size=%d", proto, remoteAddr, httpPath, size), nil) if !assert.NoError(t, err) { return } @@ -200,7 +209,7 @@ func NewHttpTestTunnel() *TestTunnel { if !assert.NoError(t, err) { return } - assert.Equal(t, httpData, data) + assert.Equal(t, httpData[:size], data) } tunnel := &TestTunnel{ HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) { @@ -241,27 +250,30 @@ func NewHttpTestTunnel() *TestTunnel { }, CloseFn: ln.Close, DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) { + // Sequential testing for debugging t.Run("Sequential", func(t *testing.T) { - testFn(t, proxy, "http") - testFn(t, proxy, "https") + testFn(t, proxy, "http", len(httpData)) + testFn(t, proxy, "https", len(httpData)) }) // Concurrent testing to detect stress t.Run("Concurrent", func(t *testing.T) { wg := sync.WaitGroup{} - const num = 50 - for i := 0; i < num; i++ { + num := len(httpData) / 1024 + for i := 1; i <= num; i++ { + i := i wg.Add(1) go func() { - testFn(t, proxy, "https") + testFn(t, proxy, "https", i*1024) defer wg.Done() }() } - for i := 0; i < num; i++ { + for i := 1; i <= num; i++ { + i := i wg.Add(1) go func() { - testFn(t, proxy, "http") + testFn(t, proxy, "http", i*1024) defer wg.Done() }() } diff --git a/mihomo/listener/inbound/mux_test.go b/mihomo/listener/inbound/mux_test.go index 4e676e6d96..a7c0208dd8 100644 --- a/mihomo/listener/inbound/mux_test.go +++ b/mihomo/listener/inbound/mux_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -var singMuxProtocolList = []string{"h2mux", "smux"} // don't test "yamux" because it has some confused bugs +var singMuxProtocolList = []string{"smux"} // don't test "h2mux" and "yamux" because it has some confused bugs // notCloseProxyAdapter is a proxy adapter that does not close the underlying outbound.ProxyAdapter. // The outbound.SingMux will close the underlying outbound.ProxyAdapter when it is closed, but we don't want to close it. diff --git a/mihomo/listener/sing_vless/server.go b/mihomo/listener/sing_vless/server.go index 41f23f34d8..0e729cab99 100644 --- a/mihomo/listener/sing_vless/server.go +++ b/mihomo/listener/sing_vless/server.go @@ -2,15 +2,11 @@ package sing_vless import ( "context" - "encoding/base64" "errors" - "fmt" "net" "net/http" "reflect" - "strconv" "strings" - "time" "unsafe" "github.com/metacubex/mihomo/adapter/inbound" @@ -88,42 +84,11 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) sl = &Listener{config: config, service: service} - if s := strings.SplitN(config.Decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" { - var minutes uint32 - if s[0] != "1rtt" { - t := strings.TrimSuffix(s[0], "min") - if t == s[0] { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - var i int - i, err = strconv.Atoi(t) - if err != nil { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - minutes = uint32(i) - } - var xor uint32 - switch s[1] { - case "vless": - case "aes128xor": - xor = 1 - default: - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - var b []byte - b, err = base64.RawURLEncoding.DecodeString(s[3]) - if err != nil { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - if len(b) == encryption.MLKEM768SeedLength { - sl.decryption = &encryption.ServerInstance{} - if err = sl.decryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { - return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) - } - } else { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - + sl.decryption, err = encryption.NewServer(config.Decryption) + if err != nil { + return nil, err + } + if sl.decryption != nil { defer func() { // decryption must be closed to avoid the goroutine leak if err != nil { _ = sl.decryption.Close() diff --git a/mihomo/transport/vless/encryption/client.go b/mihomo/transport/vless/encryption/client.go index 8453ea613d..726e98d87c 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" "io" "net" @@ -13,7 +12,6 @@ import ( "time" "github.com/metacubex/utls/mlkem" - "golang.org/x/crypto/hkdf" "golang.org/x/sys/cpu" ) @@ -30,19 +28,20 @@ var ( var ClientCipher byte func init() { - if !HasAESGCMHardwareSupport { + if HasAESGCMHardwareSupport { ClientCipher = 1 } } type ClientInstance struct { sync.RWMutex - eKeyNfs *mlkem.EncapsulationKey768 - xor uint32 - minutes time.Duration - expire time.Time - baseKey []byte - ticket []byte + nfsEKey *mlkem.EncapsulationKey768 + nfsEKeyBytes []byte + xor uint32 + minutes time.Duration + expire time.Time + baseKey []byte + ticket []byte } type ClientConn struct { @@ -58,19 +57,22 @@ type ClientConn struct { peerCache []byte } -func (i *ClientInstance) Init(eKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) { - i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData) - i.xor = xor +func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) { + i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes) + if xor > 0 { + i.nfsEKeyBytes = nfsEKeyBytes + i.xor = xor + } i.minutes = minutes return } func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { - if i.eKeyNfs == nil { + if i.nfsEKey == nil { return nil, errors.New("uninitialized") } - if i.xor == 1 { - conn = NewXorConn(conn, i.eKeyNfs.Bytes()) + if i.xor > 0 { + conn = NewXorConn(conn, i.nfsEKeyBytes) } c := &ClientConn{Conn: conn} @@ -86,18 +88,19 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { i.RUnlock() } - nfsKey, encapsulatedNfsKey := i.eKeyNfs.Encapsulate() - seed := make([]byte, 64) - rand.Read(seed) - dKeyPfs, _ := mlkem.NewDecapsulationKey768(seed) - eKeyPfs := dKeyPfs.EncapsulationKey().Bytes() - padding := randBetween(100, 1000) + pfsDKeySeed := make([]byte, 64) + rand.Read(pfsDKeySeed) + pfsDKey, _ := mlkem.NewDecapsulationKey768(pfsDKeySeed) + pfsEKeyBytes := pfsDKey.EncapsulationKey().Bytes() + nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate() + paddingLen := randBetween(100, 1000) - clientHello := make([]byte, 1088+1184+1+5+padding) - copy(clientHello, encapsulatedNfsKey) - copy(clientHello[1088:], eKeyPfs) - clientHello[2272] = ClientCipher - encodeHeader(clientHello[2273:], int(padding)) + clientHello := make([]byte, 1+1184+1088+5+paddingLen) + clientHello[0] = ClientCipher + copy(clientHello[1:], pfsEKeyBytes) + copy(clientHello[1185:], encapsulatedNfsKey) + EncodeHeader(clientHello[2273:], int(paddingLen)) + rand.Read(clientHello[2278:]) if _, err := c.Conn.Write(clientHello); err != nil { return nil, err @@ -111,17 +114,15 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { encapsulatedPfsKey := peerServerHello[:1088] c.ticket = peerServerHello[1088:] - pfsKey, err := dKeyPfs.Decapsulate(encapsulatedPfsKey) + pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey) if err != nil { return nil, err } - c.baseKey = append(nfsKey, pfsKey...) + c.baseKey = append(pfsKey, nfsKey...) - authKey := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfs).Read(authKey) - nonce := make([]byte, 12) - VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce, c.ticket, encapsulatedPfsKey) - if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message + nonce := [12]byte{ClientCipher} + VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, nonce[:], c.ticket, pfsEKeyBytes) + if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more messages return nil, errors.New("invalid server") } @@ -141,32 +142,39 @@ func (c *ClientConn) Write(b []byte) (int, error) { return 0, nil } var data []byte - if c.aead == nil { - c.random = make([]byte, 32) - rand.Read(c.random) - key := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, c.random, c.ticket).Read(key) - c.aead = newAead(ClientCipher, key) - c.nonce = make([]byte, 12) - - data = make([]byte, 21+32+5+len(b)+16) - copy(data, c.ticket) - copy(data[21:], c.random) - encodeHeader(data[53:], len(b)+16) - c.aead.Seal(data[:58], c.nonce, b, data[53:58]) - } else { - data = make([]byte, 5+len(b)+16) - encodeHeader(data, len(b)+16) - c.aead.Seal(data[:5], c.nonce, b, data[:5]) - } - increaseNonce(c.nonce) - if _, err := c.Conn.Write(data); err != nil { - return 0, err + for n := 0; n < len(b); { + b := b[n:] + if len(b) > 8192 { + b = b[:8192] // for avoiding another copy() in server's Read() + } + n += len(b) + if c.aead == nil { + c.random = make([]byte, 32) + rand.Read(c.random) + c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket) + c.nonce = make([]byte, 12) + data = make([]byte, 21+32+5+len(b)+16) + copy(data, c.ticket) + copy(data[21:], c.random) + EncodeHeader(data[53:], len(b)+16) + c.aead.Seal(data[:58], c.nonce, b, data[53:58]) + } else { + data = make([]byte, 5+len(b)+16) + EncodeHeader(data, len(b)+16) + c.aead.Seal(data[:5], c.nonce, b, data[:5]) + if bytes.Equal(c.nonce, MaxNonce) { + c.aead = NewAead(ClientCipher, c.baseKey, data[5:], data[:5]) + } + } + IncreaseNonce(c.nonce) + if _, err := c.Conn.Write(data); err != nil { + return 0, err + } } return len(b), nil } -func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() +func (c *ClientConn) Read(b []byte) (int, error) { if len(b) == 0 { return 0, nil } @@ -177,11 +185,11 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return 0, err } - peerPadding, _ := decodeHeader(peerHeader) - if peerPadding == 0 { + peerPaddingLen, _ := DecodeHeader(peerHeader) + if peerPaddingLen == 0 { break } - if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil { + if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil { return 0, err } } @@ -196,11 +204,9 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() return 0, err } if c.random == nil { - return 0, errors.New("can not Read() first") + return 0, errors.New("empty c.random") } - peerKey := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey) - c.peerAead = newAead(ClientCipher, peerKey) + c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandom, c.random) c.peerNonce = make([]byte, 12) } if len(c.peerCache) != 0 { @@ -211,7 +217,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return 0, err } - peerLength, err := decodeHeader(peerHeader) // 17~17000 + peerLength, err := DecodeHeader(peerHeader) // 17~17000 if err != nil { if c.instance != nil { c.instance.Lock() @@ -228,10 +234,17 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() } dst := peerData[:peerLength-16] if len(dst) <= len(b) { - dst = b[:len(dst)] // max=8192 is recommended for peer + dst = b[:len(dst)] // avoids another copy() + } + var peerAead cipher.AEAD + if bytes.Equal(c.peerNonce, MaxNonce) { + peerAead = NewAead(ClientCipher, c.baseKey, peerData, peerHeader) } _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader) - increaseNonce(c.peerNonce) + if peerAead != nil { + c.peerAead = peerAead + } + IncreaseNonce(c.peerNonce) if err != nil { return 0, err } diff --git a/mihomo/transport/vless/encryption/common.go b/mihomo/transport/vless/encryption/common.go index 6b02f54bee..32222f1851 100644 --- a/mihomo/transport/vless/encryption/common.go +++ b/mihomo/transport/vless/encryption/common.go @@ -1,17 +1,22 @@ package encryption import ( + "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" + "crypto/sha256" "errors" "math/big" "strconv" "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/hkdf" ) -func encodeHeader(b []byte, l int) { +var MaxNonce = bytes.Repeat([]byte{255}, 12) + +func EncodeHeader(b []byte, l int) { b[0] = 23 b[1] = 3 b[2] = 3 @@ -19,10 +24,10 @@ func encodeHeader(b []byte, l int) { b[4] = byte(l) } -func decodeHeader(b []byte) (int, error) { +func DecodeHeader(b []byte) (int, error) { if b[0] == 23 && b[1] == 3 && b[2] == 3 { l := int(b[3])<<8 | int(b[4]) - if l < 17 || l > 17000 { // TODO + if l < 17 || l > 17000 { // TODO: TLSv1.3 max length return 0, errors.New("invalid length in record's header: " + strconv.Itoa(l)) } return l, nil @@ -30,29 +35,24 @@ func decodeHeader(b []byte) (int, error) { return 0, errors.New("invalid record's header") } -func newAead(c byte, k []byte) cipher.AEAD { - switch c { - case 0: - if block, err := aes.NewCipher(k); err == nil { - aead, _ := cipher.NewGCM(block) - return aead - } - case 1: - aead, _ := chacha20poly1305.New(k) - return aead +func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) { + key := make([]byte, 32) + hkdf.New(sha256.New, secret, salt, info).Read(key) + if c&1 == 1 { + block, _ := aes.NewCipher(key) + aead, _ = cipher.NewGCM(block) + } else { + aead, _ = chacha20poly1305.New(key) } - return nil + return } -func increaseNonce(nonce []byte) { +func IncreaseNonce(nonce []byte) { for i := 0; i < 12; i++ { nonce[11-i]++ if nonce[11-i] != 0 { break } - if i == 11 { - // TODO - } } } diff --git a/mihomo/transport/vless/encryption/doc.go b/mihomo/transport/vless/encryption/doc.go index af531f8b99..d6aeb93efd 100644 --- a/mihomo/transport/vless/encryption/doc.go +++ b/mihomo/transport/vless/encryption/doc.go @@ -2,4 +2,7 @@ // https://github.com/XTLS/Xray-core/commit/f61c14e9c63dc41a8a09135db3aea337974f3f37 // https://github.com/XTLS/Xray-core/commit/3e19bf9233bdd9bafc073a71c65b737cc1ffba5e // https://github.com/XTLS/Xray-core/commit/7ffb555fc8ec51bd1e3e60f26f1d6957984dba80 +// https://github.com/XTLS/Xray-core/commit/ec1cc35188c1a5f38a2ff75e88b5d043ffdc59da +// https://github.com/XTLS/Xray-core/commit/5c611420487a92f931faefc01d4bf03869f477f6 +// https://github.com/XTLS/Xray-core/commit/23d7aad461d232bc5bed52dd6aaa731ecd88ad35 package encryption diff --git a/mihomo/transport/vless/encryption/factory.go b/mihomo/transport/vless/encryption/factory.go new file mode 100644 index 0000000000..18b88c2e3e --- /dev/null +++ b/mihomo/transport/vless/encryption/factory.go @@ -0,0 +1,99 @@ +package encryption + +import ( + "encoding/base64" + "fmt" + "strconv" + "strings" + "time" +) + +// NewClient new client from encryption string +// maybe return a nil *ClientInstance without any error, that means don't need to encrypt +func NewClient(encryption string) (*ClientInstance, error) { + switch encryption { + case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility + return nil, nil + } + if s := strings.SplitN(encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" { + var minutes uint32 + if s[0] != "1rtt" { + t := strings.TrimSuffix(s[0], "min") + if t == s[0] { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + i, err := strconv.Atoi(t) + if err != nil { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + minutes = uint32(i) + } + var xor uint32 + switch s[1] { + case "vless": + case "aes128xor": + xor = 1 + default: + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + b, err := base64.RawURLEncoding.DecodeString(s[3]) + if err != nil { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + if len(b) == MLKEM768ClientLength { + client := &ClientInstance{} + if err = client.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { + return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) + } + return client, nil + } else { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + } + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) +} + +// NewServer new server from decryption string +// maybe return a nil *ServerInstance without any error, that means don't need to decrypt +func NewServer(decryption string) (*ServerInstance, error) { + switch decryption { + case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility + return nil, nil + } + if s := strings.SplitN(decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" { + var minutes uint32 + if s[0] != "1rtt" { + t := strings.TrimSuffix(s[0], "min") + if t == s[0] { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + i, err := strconv.Atoi(t) + if err != nil { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + minutes = uint32(i) + } + var xor uint32 + switch s[1] { + case "vless": + case "aes128xor": + xor = 1 + default: + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + b, err := base64.RawURLEncoding.DecodeString(s[3]) + if err != nil { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + if len(b) == MLKEM768SeedLength { + server := &ServerInstance{} + if err = server.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { + return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) + } + return server, nil + } else { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + } + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) +} diff --git a/mihomo/transport/vless/encryption/server.go b/mihomo/transport/vless/encryption/server.go index 6874445960..b42db3fdc5 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" "io" "net" @@ -12,7 +11,6 @@ import ( "time" "github.com/metacubex/utls/mlkem" - "golang.org/x/crypto/hkdf" ) type ServerSession struct { @@ -24,11 +22,12 @@ type ServerSession struct { type ServerInstance struct { sync.RWMutex - dKeyNfs *mlkem.DecapsulationKey768 - xor uint32 - minutes time.Duration - sessions map[[21]byte]*ServerSession - stop bool + nfsDKey *mlkem.DecapsulationKey768 + nfsEKeyBytes []byte + xor uint32 + minutes time.Duration + sessions map[[21]byte]*ServerSession + closed bool } type ServerConn struct { @@ -44,9 +43,12 @@ type ServerConn struct { nonce []byte } -func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) { - i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData) - i.xor = xor +func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Duration) (err error) { + i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed) + if xor > 0 { + i.nfsEKeyBytes = i.nfsDKey.EncapsulationKey().Bytes() + i.xor = xor + } if minutes > 0 { i.minutes = minutes i.sessions = make(map[[21]byte]*ServerSession) @@ -55,12 +57,13 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat time.Sleep(time.Minute) now := time.Now() i.Lock() - if i.stop { + if i.closed { + i.Unlock() return } - for index, session := range i.sessions { + for ticket, session := range i.sessions { if now.After(session.expire) { - delete(i.sessions, index) + delete(i.sessions, ticket) } } i.Unlock() @@ -72,17 +75,17 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat func (i *ServerInstance) Close() (err error) { i.Lock() - defer i.Unlock() - i.stop = true + i.closed = true + i.Unlock() return } func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { - if i.dKeyNfs == nil { + if i.nfsDKey == nil { return nil, errors.New("uninitialized") } - if i.xor == 1 { - conn = NewXorConn(conn, i.dKeyNfs.EncapsulationKey().Bytes()) + if i.xor > 0 { + conn = NewXorConn(conn, i.nfsEKeyBytes) } c := &ServerConn{Conn: conn} @@ -109,50 +112,49 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return nil, err } - if l, _ := decodeHeader(peerHeader); l != 0 { - c.Conn.Write(make([]byte, randBetween(100, 1000))) // make client do new handshake + if l, _ := DecodeHeader(peerHeader); l != 0 { + noise := make([]byte, randBetween(100, 1000)) + rand.Read(noise) + c.Conn.Write(noise) // make client do new handshake return nil, errors.New("invalid ticket") } - peerClientHello := make([]byte, 1088+1184+1) + peerClientHello := make([]byte, 1+1184+1088) copy(peerClientHello, peerTicketHello) copy(peerClientHello[53:], peerHeader) if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil { return nil, err } - encapsulatedNfsKey := peerClientHello[:1088] - eKeyPfsData := peerClientHello[1088:2272] - c.cipher = peerClientHello[2272] - if c.cipher != 0 && c.cipher != 1 { - return nil, errors.New("invalid cipher") - } + c.cipher = peerClientHello[0] + pfsEKeyBytes := peerClientHello[1:1185] + encapsulatedNfsKey := peerClientHello[1185:2273] - nfsKey, err := i.dKeyNfs.Decapsulate(encapsulatedNfsKey) + pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes) if err != nil { return nil, err } - eKeyPfs, err := mlkem.NewEncapsulationKey768(eKeyPfsData) + nfsKey, err := i.nfsDKey.Decapsulate(encapsulatedNfsKey) if err != nil { return nil, err } - pfsKey, encapsulatedPfsKey := eKeyPfs.Encapsulate() - c.baseKey = append(nfsKey, pfsKey...) + pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate() + c.baseKey = append(pfsKey, nfsKey...) - authKey := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfsData).Read(authKey) - nonce := make([]byte, 12) - c.ticket = newAead(c.cipher, authKey).Seal(nil, nonce, []byte("VLESS"), encapsulatedPfsKey) + nonce := [12]byte{c.cipher} + c.ticket = NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, nonce[:], []byte("VLESS"), pfsEKeyBytes) - padding := randBetween(100, 1000) + paddingLen := randBetween(100, 1000) - serverHello := make([]byte, 1088+21+5+padding) + serverHello := make([]byte, 1088+21+5+paddingLen) copy(serverHello, encapsulatedPfsKey) copy(serverHello[1088:], c.ticket) - encodeHeader(serverHello[1109:], int(padding)) + EncodeHeader(serverHello[1109:], int(paddingLen)) + rand.Read(serverHello[1114:]) if _, err := c.Conn.Write(serverHello); err != nil { return nil, err } + // we can send more padding if needed if i.minutes > 0 { i.Lock() @@ -178,11 +180,11 @@ func (c *ServerConn) Read(b []byte) (int, error) { if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return 0, err } - peerPadding, _ := decodeHeader(peerHeader) - if peerPadding == 0 { + peerPaddingLen, _ := DecodeHeader(peerHeader) + if peerPaddingLen == 0 { break } - if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil { + if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil { return 0, err } } @@ -199,9 +201,7 @@ func (c *ServerConn) Read(b []byte) (int, error) { return 0, err } } - peerKey := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, c.peerRandom, c.ticket).Read(peerKey) - c.peerAead = newAead(c.cipher, peerKey) + c.peerAead = NewAead(c.cipher, c.baseKey, c.peerRandom, c.ticket) c.peerNonce = make([]byte, 12) } if len(c.peerCache) != 0 { @@ -212,7 +212,7 @@ func (c *ServerConn) Read(b []byte) (int, error) { if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return 0, err } - peerLength, err := decodeHeader(peerHeader) // 17~17000 + peerLength, err := DecodeHeader(peerHeader) // 17~17000 if err != nil { return 0, err } @@ -222,10 +222,17 @@ func (c *ServerConn) Read(b []byte) (int, error) { } dst := peerData[:peerLength-16] if len(dst) <= len(b) { - dst = b[:len(dst)] // max=8192 is recommended for peer + dst = b[:len(dst)] // avoids another copy() + } + var peerAead cipher.AEAD + if bytes.Equal(c.peerNonce, MaxNonce) { + peerAead = NewAead(c.cipher, c.baseKey, peerData, peerHeader) } _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader) - increaseNonce(c.peerNonce) + if peerAead != nil { + c.peerAead = peerAead + } + IncreaseNonce(c.peerNonce) if err != nil { return 0, errors.New("error") } @@ -236,31 +243,39 @@ func (c *ServerConn) Read(b []byte) (int, error) { return len(dst), nil } -func (c *ServerConn) Write(b []byte) (int, error) { // after first Read() +func (c *ServerConn) Write(b []byte) (int, error) { if len(b) == 0 { return 0, nil } var data []byte - if c.aead == nil { - if c.peerRandom == nil { - return 0, errors.New("can not Write() first") + for n := 0; n < len(b); { + b := b[n:] + if len(b) > 8192 { + b = b[:8192] // for avoiding another copy() in client's Read() + } + n += len(b) + if c.aead == nil { + if c.peerRandom == nil { + return 0, errors.New("empty c.peerRandom") + } + data = make([]byte, 32+5+len(b)+16) + rand.Read(data[:32]) + c.aead = NewAead(c.cipher, c.baseKey, data[:32], c.peerRandom) + c.nonce = make([]byte, 12) + EncodeHeader(data[32:], len(b)+16) + c.aead.Seal(data[:37], c.nonce, b, data[32:37]) + } else { + data = make([]byte, 5+len(b)+16) + EncodeHeader(data, len(b)+16) + c.aead.Seal(data[:5], c.nonce, b, data[:5]) + if bytes.Equal(c.nonce, MaxNonce) { + c.aead = NewAead(c.cipher, c.baseKey, data[5:], data[:5]) + } + } + IncreaseNonce(c.nonce) + if _, err := c.Conn.Write(data); err != nil { + return 0, err } - data = make([]byte, 32+5+len(b)+16) - rand.Read(data[:32]) - key := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, data[:32], c.peerRandom).Read(key) - c.aead = newAead(c.cipher, key) - c.nonce = make([]byte, 12) - encodeHeader(data[32:], len(b)+16) - c.aead.Seal(data[:37], c.nonce, b, data[32:37]) - } else { - data = make([]byte, 5+len(b)+16) - encodeHeader(data, len(b)+16) - c.aead.Seal(data[:5], c.nonce, b, data[:5]) - } - increaseNonce(c.nonce) - if _, err := c.Conn.Write(data); err != nil { - return 0, err } return len(b), nil } diff --git a/mihomo/transport/vless/encryption/xor.go b/mihomo/transport/vless/encryption/xor.go index c8af2112d1..296fdd637a 100644 --- a/mihomo/transport/vless/encryption/xor.go +++ b/mihomo/transport/vless/encryption/xor.go @@ -38,7 +38,7 @@ func (c *XorConn) Write(b []byte) (int, error) { return 0, err } if iv != nil { - b = b[16:] + b = b[16:] // for len(b) } return len(b), nil } diff --git a/shadowsocks-rust/Cargo.lock b/shadowsocks-rust/Cargo.lock index 5f3bf0a7f3..025e1e74ca 100644 --- a/shadowsocks-rust/Cargo.lock +++ b/shadowsocks-rust/Cargo.lock @@ -579,18 +579,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.43" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f" +checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.43" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" dependencies = [ "anstream", "anstyle", @@ -800,7 +800,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -1004,7 +1004,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]] @@ -1403,7 +1403,7 @@ dependencies = [ "ring", "rustls", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "tinyvec", "tokio", "tokio-rustls", @@ -1431,7 +1431,7 @@ dependencies = [ "rustls", "serde", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-rustls", "tracing", @@ -1942,7 +1942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.48.5", ] [[package]] @@ -2449,7 +2449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.12", + "thiserror 2.0.14", "ucd-trie", ] @@ -2668,7 +2668,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2 0.5.10", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tracing", "web-time", @@ -2689,7 +2689,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.14", "tinyvec", "tracing", "web-time", @@ -2706,7 +2706,7 @@ dependencies = [ "once_cell", "socket2 0.5.10", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2806,7 +2806,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -3021,7 +3021,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3320,7 +3320,7 @@ dependencies = [ "shadowsocks-crypto", "socket2 0.6.0", "spin", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-tfo", "trait-variant", @@ -3387,7 +3387,7 @@ dependencies = [ "snmalloc-rs", "sysexits", "tcmalloc", - "thiserror 2.0.12", + "thiserror 2.0.14", "time", "tokio", "tracing", @@ -3436,7 +3436,7 @@ dependencies = [ "smoltcp", "socket2 0.6.0", "spin", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-native-tls", "tokio-rustls", @@ -3703,7 +3703,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3727,11 +3727,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.14", ] [[package]] @@ -3747,9 +3747,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", @@ -4053,7 +4053,7 @@ dependencies = [ "libc", "log", "nix", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-util", "windows-sys 0.60.2", @@ -4343,7 +4343,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]] @@ -4737,7 +4737,7 @@ dependencies = [ "futures", "libloading", "log", - "thiserror 2.0.12", + "thiserror 2.0.14", "windows-sys 0.60.2", "winreg 0.55.0", ] diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua index eed90f6b6f..84223800d1 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua @@ -475,30 +475,30 @@ if singbox_tags:find("with_utls") then o:value("firefox") o:value("edge") o:value("safari") - -- o:value("360") + o:value("360") o:value("qq") o:value("ios") - -- o:value("android") + o:value("android") o:value("random") - -- o:value("randomized") + o:value("randomized") o.default = "chrome" - o:depends({ [_n("tls")] = true, [_n("utls")] = true }) + o:depends({ [_n("utls")] = true }) -- [[ REALITY部分 ]] -- o = s:option(Flag, _n("reality"), translate("REALITY")) o.default = 0 - o:depends({ [_n("protocol")] = "vless", [_n("utls")] = true }) - o:depends({ [_n("protocol")] = "vmess", [_n("utls")] = true }) - o:depends({ [_n("protocol")] = "shadowsocks", [_n("utls")] = true }) - o:depends({ [_n("protocol")] = "socks", [_n("utls")] = true }) - o:depends({ [_n("protocol")] = "trojan", [_n("utls")] = true }) - o:depends({ [_n("protocol")] = "anytls", [_n("utls")] = true }) + o:depends({ [_n("protocol")] = "vless", [_n("tls")] = true }) + o:depends({ [_n("protocol")] = "vmess", [_n("tls")] = true }) + o:depends({ [_n("protocol")] = "shadowsocks", [_n("tls")] = true }) + o:depends({ [_n("protocol")] = "socks", [_n("tls")] = true }) + o:depends({ [_n("protocol")] = "trojan", [_n("tls")] = true }) + o:depends({ [_n("protocol")] = "anytls", [_n("tls")] = true }) o = s:option(Value, _n("reality_publicKey"), translate("Public Key")) - o:depends({ [_n("utls")] = true, [_n("reality")] = true }) + o:depends({ [_n("reality")] = true }) o = s:option(Value, _n("reality_shortId"), translate("Short Id")) - o:depends({ [_n("utls")] = true, [_n("reality")] = true }) + o:depends({ [_n("reality")] = true }) end o = s:option(ListValue, _n("transport"), translate("Transport")) diff --git a/small/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua b/small/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua index 62cabddd21..20a9395a8b 100755 --- a/small/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua +++ b/small/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua @@ -17,6 +17,9 @@ local xray_fragment = ucursor:get_all("shadowsocksr", "@global_xray_fragment[0]" local xray_noise = ucursor:get_all("shadowsocksr", "@xray_noise_packets[0]") or {} local outbound_settings = nil +local node_id = server_section +local remarks = server.alias or "" + function vmess_vless() outbound_settings = { vnext = { @@ -238,7 +241,7 @@ end rawSettings = (server.transport == "raw" or server.transport == "tcp") and { -- tcp header = { - type = server.tcp_guise or "none", + type = server.tcp_guise, request = (server.tcp_guise == "http") and { -- request path = {server.http_path} or {"/"}, @@ -317,7 +320,8 @@ end tcpMptcp = (server.mptcp == "1") and true or nil, -- MPTCP Penetrate = (server.mptcp == "1") and true or nil, -- Penetrate MPTCP tcpcongestion = server.custom_tcpcongestion, -- 连接服务器节点的 TCP 拥塞控制算法 - dialerProxy = (xray_fragment.fragment == "1" or xray_fragment.noise == "1") and "dialerproxy" or nil + dialerProxy = (xray_fragment.fragment == "1" or xray_fragment.noise == "1") and + ((remarks ~= nil and remarks ~= "") and (node_id .. "." .. remarks) or node_id) or nil } } or nil, mux = (server.v2ray_protocol ~= "wireguard") and { @@ -334,7 +338,7 @@ end if xray_fragment.fragment ~= "0" or (xray_fragment.noise ~= "0" and xray_noise.enabled ~= "0") then table.insert(Xray.outbounds, { protocol = "freedom", - tag = "dialerproxy", + tag = (remarks ~= nil and remarks ~= "") and (node_id .. "." .. remarks) or node_id, settings = { domainStrategy = (xray_fragment.noise == "1" and xray_noise.enabled == "1") and xray_noise.domainStrategy, fragment = (xray_fragment.fragment == "1") and { diff --git a/v2rayn/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayn/v2rayN/ServiceLib/Models/ConfigItems.cs index 998b49f176..1c3a57c311 100644 --- a/v2rayn/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayn/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -142,6 +142,7 @@ public class CoreTypeItem public class TunModeItem { public bool EnableTun { get; set; } + public bool AutoRoute { get; set; } = true; public bool StrictRoute { get; set; } = true; public string Stack { get; set; } public int Mtu { get; set; } diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx index fd53b82639..97c04c819e 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1,17 +1,17 @@ - @@ -1099,13 +1099,13 @@ Отмена тестирования... - *gRPC Authority + * gRPC Authority (HTTP/2 псевдозаголовок :authority) Добавить сервер [HTTP] - which conflicts with the group previous proxy + что конфликтует с предыдущим прокси группы Включить фрагментацию (Fragment) @@ -1318,13 +1318,13 @@ Пароль sudo системы - 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. + Пароль sudo будет проверен в терминале. Если из-за ошибки проверки приложение начнёт работать некорректно, перезапустите его. Пароль не сохраняется — его нужно вводить после каждого перезапуска. *XHTTP-режим - Дополнительный XHTTP сырой JSON, формат: { XHTTPObject } + Дополнительный „сырой“ JSON для XHTTP, формат: { XHTTP Object } Скрыть в трее при закрытии окна @@ -1393,10 +1393,10 @@ URL для тестирования текущего соединения - Can fill in the configuration remarks, please make sure it exist and are unique + Можно указать название (Remarks) из конфигурации, убедитесь, что оно существует и уникально - Incorrect password, please try again. + Неверный пароль, попробуйте ещё раз. Mldsa65Verify @@ -1405,99 +1405,99 @@ Добавить сервер [Anytls] - Remote DNS + Удалённый DNS - Domestic DNS + Внутренний DNS - Outbound DNS Resolution (sing-box) + Резолвер DNS для исходящих (sing-box) - Resolve Outbound Domains + Разрешать домены для исходящих соединений - sing-box DoH Resolver Server + Сервер DoH-резолвера (sing-box) - Fallback DNS Resolution, Suggest IP + Резервное DNS-разрешение (рекомендуется указывать IP) - xray Freedom Resolution Strategy + Стратегия резолвинга Freedom (Xray) - sing-box Direct Resolution Strategy + Стратегия прямого резолвинга (sing-box) - sing-box Remote Resolution Strategy + Стратегия удалённого резолвинга (sing-box) - Add Common DNS Hosts + Добавить стандартные записи hosts (DNS) - The sing-box DoH resolution server can be overwritten + Сервер DoH-резолвера sing-box можно переопределить FakeIP - Block SVCB and HTTPS Queries + Блокировать DNS-запросы SVCB и HTTPS - DNS Hosts: ("domain1 ip1 ip2" per line) + DNS hosts: (каждая строка в формате "domain1 ip1 ip2") - Apply to Proxy Domains Only + Применять только к доменам через прокси - Basic DNS Settings + Базовые настройки DNS - Advanced DNS Settings + Расширенные настройки DNS - Validate Regional Domain IPs + Проверять IP-адреса региональных доменов - When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs + При включении проверяет IP-адреса, возвращаемые для региональных доменов (например, geosite:cn), и оставляет только ожидаемые IP-адреса - Enable Custom DNS + Включить пользовательский DNS - Custom DNS Enabled, This Page's Settings Invalid + Включён пользовательский DNS — настройки на этой странице не применяются - Prevent domain-based routing rules from failing + Предотвращает сбои доменных правил маршрутизации - Please fill in the correct config template + Пожалуйста, заполните корректный шаблон конфигурации - Full Config Template Setting + Настройка полного шаблона конфигурации - Enable Full Config Template + Включить полный шаблон конфигурации - v2ray Full Config Template + Полный шаблон конфигурации v2ray - Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document + Добавляет только конфигурацию исходящих (outbound), а также routing.balancers и routing.rules.outboundTag. Нажмите, чтобы открыть документ - Do Not Add Non-Proxy Protocol Outbound + Не добавлять исходящие для непрокси-протоколов - Set Upstream Proxy Tag + Задать тег верхнего прокси (upstream) - sing-box Full Config Template + Полный шаблон конфигурации sing-box - Add Outbound and Endpoint Config Only, Click to view the document + Добавляет только конфигурацию Outbound и Endpoint. Нажмите, чтобы открыть документ - 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. + Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную. - \ No newline at end of file + diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 19f0af35f5..3fb3252478 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -80,8 +80,7 @@ public class CoreConfigSingboxService ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.sing_box); - ret.Data = await ApplyFullConfigTemplate(fullConfigTemplate, singboxConfig); + ret.Data = await ApplyFullConfigTemplate(singboxConfig); return ret; } catch (Exception ex) @@ -434,8 +433,7 @@ public class CoreConfigSingboxService ret.Success = true; - var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.sing_box); - ret.Data = await ApplyFullConfigTemplate(fullConfigTemplate, singboxConfig); + ret.Data = await ApplyFullConfigTemplate(singboxConfig); return ret; } catch (Exception ex) @@ -628,6 +626,7 @@ public class CoreConfigSingboxService var tunInbound = JsonUtils.Deserialize(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { }; tunInbound.interface_name = Utils.IsOSX() ? $"utun{new Random().Next(99)}" : "singbox_tun"; tunInbound.mtu = _config.TunModeItem.Mtu; + tunInbound.auto_route = _config.TunModeItem.AutoRoute; tunInbound.strict_route = _config.TunModeItem.StrictRoute; tunInbound.stack = _config.TunModeItem.Stack; if (_config.TunModeItem.EnableIPv6Address == false) @@ -2218,15 +2217,16 @@ public class CoreConfigSingboxService return 0; } - private async Task ApplyFullConfigTemplate(FullConfigTemplateItem fullConfigTemplate, SingboxConfig singboxConfig) + private async Task ApplyFullConfigTemplate(SingboxConfig singboxConfig) { - var fullConfigTemplateItem = fullConfigTemplate.Config; - if (_config.TunModeItem.EnableTun) + var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.sing_box); + if (fullConfigTemplate == null || !fullConfigTemplate.Enabled) { - fullConfigTemplateItem = fullConfigTemplate.TunConfig; + return JsonUtils.Serialize(singboxConfig); } - if (!fullConfigTemplate.Enabled || fullConfigTemplateItem.IsNullOrEmpty()) + var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config; + if (fullConfigTemplateItem.IsNullOrEmpty()) { return JsonUtils.Serialize(singboxConfig); } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index 96388ed3e1..43911046b8 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -3,7 +3,6 @@ using System.Net.NetworkInformation; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; -using ServiceLib.Models; namespace ServiceLib.Services.CoreConfig; @@ -69,9 +68,7 @@ public class CoreConfigV2rayService ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - - var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.Xray); - ret.Data = await ApplyFullConfigTemplate(fullConfigTemplate, v2rayConfig); + ret.Data = await ApplyFullConfigTemplate(v2rayConfig); return ret; } catch (Exception ex) @@ -201,8 +198,7 @@ public class CoreConfigV2rayService ret.Success = true; - var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.Xray); - ret.Data = await ApplyFullConfigTemplate(fullConfigTemplate, v2rayConfig, true); + ret.Data = await ApplyFullConfigTemplate(v2rayConfig, true); return ret; } catch (Exception ex) @@ -1844,9 +1840,10 @@ public class CoreConfigV2rayService return await Task.FromResult(0); } - private async Task ApplyFullConfigTemplate(FullConfigTemplateItem fullConfigTemplate, V2rayConfig v2rayConfig, bool handleBalancerAndRules = false) + private async Task ApplyFullConfigTemplate(V2rayConfig v2rayConfig, bool handleBalancerAndRules = false) { - if (!fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty()) + var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.Xray); + if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty()) { return JsonUtils.Serialize(v2rayConfig); } @@ -1918,6 +1915,7 @@ public class CoreConfigV2rayService } customOutboundsNode.Add(JsonUtils.DeepCopy(outbound)); } + fullConfigTemplateNode["outbounds"] = customOutboundsNode; return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode)); } diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index 57d8cac72b..b45eb30c72 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -84,6 +84,7 @@ public class OptionSettingViewModel : MyReactiveObject #region Tun mode + [Reactive] public bool TunAutoRoute { get; set; } [Reactive] public bool TunStrictRoute { get; set; } [Reactive] public string TunStack { get; set; } [Reactive] public int TunMtu { get; set; } @@ -201,6 +202,7 @@ public class OptionSettingViewModel : MyReactiveObject #region Tun mode + TunAutoRoute = _config.TunModeItem.AutoRoute; TunStrictRoute = _config.TunModeItem.StrictRoute; TunStack = _config.TunModeItem.Stack; TunMtu = _config.TunModeItem.Mtu; @@ -354,6 +356,7 @@ public class OptionSettingViewModel : MyReactiveObject _config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol; //tun mode + _config.TunModeItem.AutoRoute = TunAutoRoute; _config.TunModeItem.StrictRoute = TunStrictRoute; _config.TunModeItem.Stack = TunStack; _config.TunModeItem.Mtu = TunMtu; diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index a75ed6fc03..ba174a6261 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -729,71 +729,84 @@ Margin="{StaticResource Margin4}" ColumnDefinitions="Auto,Auto,Auto" DockPanel.Dock="Top" - RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> + + + diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs b/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs index 010488e508..a7529369d4 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs +++ b/v2rayn/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs @@ -105,6 +105,7 @@ public partial class OptionSettingWindow : WindowBase this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.TunAutoRoute, v => v.togAutoRoute.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunStrictRoute, v => v.togStrictRoute.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunStack, v => v.cmbStack.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.SelectedValue).DisposeWith(disposables); diff --git a/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index 890352fcb0..bdd47f1c85 100644 --- a/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -1023,6 +1023,7 @@ + @@ -1036,9 +1037,9 @@ Margin="{StaticResource Margin8}" VerticalAlignment="Center" Style="{StaticResource ToolbarTextBlock}" - Text="Strict Route" /> + Text="Auto Route" /> + + + @@ -1231,3 +1246,4 @@ + diff --git a/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs b/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs index c790e25da3..dea7302bf6 100644 --- a/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs +++ b/v2rayn/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs @@ -117,6 +117,7 @@ public partial class OptionSettingWindow this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.TunAutoRoute, v => v.togAutoRoute.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunStrictRoute, v => v.togStrictRoute.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunStack, v => v.cmbStack.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.Text).DisposeWith(disposables); diff --git a/v2rayng/V2rayNG/app/build.gradle.kts b/v2rayng/V2rayNG/app/build.gradle.kts index d5d5cfbd0b..be37cade9d 100644 --- a/v2rayng/V2rayNG/app/build.gradle.kts +++ b/v2rayng/V2rayNG/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "com.v2ray.ang" minSdk = 21 targetSdk = 35 - versionCode = 666 - versionName = "1.10.16" + versionCode = 667 + versionName = "1.10.17" multiDexEnabled = true val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';') diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt index 3b57311446..8f54e3dbce 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt @@ -27,6 +27,7 @@ object AppConfig { const val PREF_VPN_DNS = "pref_vpn_dns" const val PREF_VPN_BYPASS_LAN = "pref_vpn_bypass_lan" const val PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX = "pref_vpn_interface_address_config_index" + const val PREF_VPN_MTU = "pref_vpn_mtu" const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy" const val PREF_ROUTING_RULESET = "pref_routing_ruleset" const val PREF_MUX_ENABLED = "pref_mux_enabled" diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsManager.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsManager.kt index b2e23f7f07..348a950279 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsManager.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsManager.kt @@ -370,4 +370,11 @@ object SettingsManager { val selectedIndex = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX, "0")?.toInt() return VpnInterfaceAddressConfig.getConfigByIndex(selectedIndex ?: 0) } + + /** + * Get the VPN MTU from settings, defaulting to AppConfig.VPN_MTU. + */ + fun getVpnMtu(): Int { + return Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_MTU), AppConfig.VPN_MTU) + } } diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/TProxyService.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/TProxyService.kt index 2e76435757..c23ac32857 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/TProxyService.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/TProxyService.kt @@ -4,7 +4,6 @@ import android.content.Context import android.os.ParcelFileDescriptor import android.util.Log import com.v2ray.ang.AppConfig -import com.v2ray.ang.AppConfig.VPN_MTU import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.SettingsManager import java.io.File @@ -60,7 +59,7 @@ class TProxyService( val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig() return buildString { appendLine("tunnel:") - appendLine(" mtu: $VPN_MTU") + appendLine(" mtu: ${SettingsManager.getVpnMtu()}") appendLine(" ipv4: ${vpnConfig.ipv4Client}") if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) { diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/Tun2SocksService.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/Tun2SocksService.kt index 37a26fd252..eb33f5b7d6 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/Tun2SocksService.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/Tun2SocksService.kt @@ -6,7 +6,6 @@ import android.net.LocalSocketAddress import android.os.ParcelFileDescriptor import android.util.Log import com.v2ray.ang.AppConfig -import com.v2ray.ang.AppConfig.VPN_MTU import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.SettingsManager import com.v2ray.ang.util.Utils @@ -42,7 +41,7 @@ class Tun2SocksService( "--netif-ipaddr", vpnConfig.ipv4Router, "--netif-netmask", "255.255.255.252", "--socks-server-addr", "${AppConfig.LOOPBACK}:${socksPort}", - "--tunmtu", VPN_MTU.toString(), + "--tunmtu", SettingsManager.getVpnMtu().toString(), "--sock-path", "sock_path", "--enable-udprelay", "--loglevel", "notice" diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayVpnService.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayVpnService.kt index db4cf04b92..ee71e79b45 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayVpnService.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayVpnService.kt @@ -17,7 +17,6 @@ import android.util.Log import androidx.annotation.RequiresApi import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.LOOPBACK -import com.v2ray.ang.AppConfig.VPN_MTU import com.v2ray.ang.BuildConfig import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.NotificationManager @@ -185,7 +184,7 @@ class V2RayVpnService : VpnService(), ServiceControl { val bypassLan = SettingsManager.routingRulesetsBypassLan() // Configure IPv4 settings - builder.setMtu(VPN_MTU) + builder.setMtu(SettingsManager.getVpnMtu()) builder.addAddress(vpnConfig.ipv4Client, 30) // Configure routing rules diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt index f465c7292c..1a577db2a9 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt @@ -45,6 +45,7 @@ class SettingsActivity : BaseActivity() { private val vpnDns by lazy { findPreference(AppConfig.PREF_VPN_DNS) } private val vpnBypassLan by lazy { findPreference(AppConfig.PREF_VPN_BYPASS_LAN) } private val vpnInterfaceAddress by lazy { findPreference(AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX) } + private val vpnMtu by lazy { findPreference(AppConfig.PREF_VPN_MTU) } private val mux by lazy { findPreference(AppConfig.PREF_MUX_ENABLED) } private val muxConcurrency by lazy { findPreference(AppConfig.PREF_MUX_CONCURRENCY) } @@ -93,6 +94,12 @@ class SettingsActivity : BaseActivity() { true } + vpnMtu?.setOnPreferenceChangeListener { _, any -> + val nval = any as String + vpnMtu?.summary = if (TextUtils.isEmpty(nval)) AppConfig.VPN_MTU.toString() else nval + true + } + mux?.setOnPreferenceChangeListener { _, newValue -> updateMux(newValue as Boolean) true @@ -196,6 +203,7 @@ class SettingsActivity : BaseActivity() { appendHttpProxy?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_APPEND_HTTP_PROXY, false) localDnsPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS) vpnDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_DNS, AppConfig.DNS_VPN) + vpnMtu?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_MTU, AppConfig.VPN_MTU.toString()) updateMux(MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false)) mux?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false) @@ -229,6 +237,7 @@ class SettingsActivity : BaseActivity() { listOf( localDnsPort, vpnDns, + vpnMtu, muxConcurrency, muxXudpConcurrency, fragmentLength, @@ -298,6 +307,7 @@ class SettingsActivity : BaseActivity() { vpnDns?.isEnabled = vpn vpnBypassLan?.isEnabled = vpn vpnInterfaceAddress?.isEnabled = vpn + vpnMtu?.isEnabled = vpn if (vpn) { updateLocalDns( MmkvManager.decodeSettingsBool( diff --git a/v2rayng/V2rayNG/app/src/main/res/values-ar/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-ar/strings.xml index 181b6f4541..bb50c08acb 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-ar/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-ar/strings.xml @@ -81,6 +81,7 @@ PreSharedKey(optional) المعرّف القصير SpiderX + Mldsa65Verify المفتاح السري محجوز (اختياري) العنوان المحلي (اختياري IPv4/IPv6، مفصولة بفواصل) @@ -183,6 +184,7 @@ Does VPN bypass LAN VPN Interface Address + VPN MTU (default 1500) DNS المحلي (اختياري) DNS diff --git a/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml index 58567af1b2..53593a3220 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml @@ -184,6 +184,7 @@ Does VPN bypass LAN VPN Interface Address + VPN MTU (default 1500) ঘরোয়া DNS (ঐচ্ছিক) DNS diff --git a/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml index 07271df29f..7a2dd4d6ba 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml @@ -184,6 +184,7 @@ VPN ز شبکه مهلی اگوڌرته؟ نشۊوی رابت VPN + VPN MTU (default 1500) DNS منی (اختیاری) DNS diff --git a/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml index 48f714631c..0d19dbcd9a 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml @@ -182,6 +182,7 @@ آیا VPN از شبکه محلی عبور می کند؟ آدرس واسط VPN + VPN MTU (default 1500) DNS داخلی (اختیاری) DNS diff --git a/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml index 91e129cc06..9fd4478fd3 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml @@ -183,6 +183,7 @@ VPN обходит LAN Адрес интерфейса VPN + VPN MTU (default 1500) Внутренняя DNS (необязательно) DNS diff --git a/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml index 15991f9485..133ae493b9 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml @@ -184,6 +184,7 @@ Does VPN bypass LAN VPN Interface Address + VPN MTU (default 1500) DNS nội địa (Không bắt buộc) DNS diff --git a/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index 9ea47e2407..04cc6d160a 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -181,6 +181,7 @@ VPN 是否绕过局域网 VPN 接口地址 + VPN MTU (默认 1500) 境内 DNS (可选) DNS diff --git a/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index a7510e483a..025d91e707 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -183,6 +183,7 @@ VPN 是否繞過區域網 VPN 介面位址 + VPN MTU (預設 1500) DNS 境内 DNS (可选) diff --git a/v2rayng/V2rayNG/app/src/main/res/values/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values/strings.xml index df3735904c..f72c0d6973 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values/strings.xml @@ -185,6 +185,7 @@ Does VPN bypass LAN VPN Interface Address + VPN MTU (default 1500) Domestic DNS (Optional) DNS diff --git a/v2rayng/V2rayNG/app/src/main/res/xml/pref_settings.xml b/v2rayng/V2rayNG/app/src/main/res/xml/pref_settings.xml index 99f35aee22..2ad2679237 100644 --- a/v2rayng/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -72,6 +72,12 @@ android:summary="%s" android:title="@string/title_pref_vpn_interface_address" /> + +