diff --git a/.github/update.log b/.github/update.log index 46b0b6f095..d072464c43 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1096,3 +1096,4 @@ Update On Sun Aug 17 20:39:03 CEST 2025 Update On Mon Aug 18 20:42:30 CEST 2025 Update On Tue Aug 19 20:36:45 CEST 2025 Update On Wed Aug 20 20:53:01 CEST 2025 +Update On Thu Aug 21 20:41:11 CEST 2025 diff --git a/brook/docs/images/user-system.png b/brook/docs/images/user-system.png new file mode 100644 index 0000000000..cda5e2f85e Binary files /dev/null and b/brook/docs/images/user-system.png differ diff --git a/clash-meta/common/net/deadline/conn.go b/clash-meta/common/net/deadline/conn.go index 01519e941c..aafad9e4ab 100644 --- a/clash-meta/common/net/deadline/conn.go +++ b/clash-meta/common/net/deadline/conn.go @@ -149,6 +149,10 @@ func (c *Conn) ReaderReplaceable() bool { return c.disablePipe.Load() || c.deadline.Load().IsZero() } +func (c *Conn) WriterReplaceable() bool { + return true +} + func (c *Conn) Upstream() any { return c.ExtendedConn } diff --git a/clash-meta/component/generater/types.go b/clash-meta/component/generater/types.go deleted file mode 100644 index 06f59e9468..0000000000 --- a/clash-meta/component/generater/types.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copy from https://github.com/WireGuard/wgctrl-go/blob/a9ab2273dd1075ea74b88c76f8757f8b4003fcbf/wgtypes/types.go#L71-L155 - -package generater - -import ( - "crypto/rand" - "encoding/base64" - "fmt" - - "golang.org/x/crypto/curve25519" -) - -// KeyLen is the expected key length for a WireGuard key. -const KeyLen = 32 // wgh.KeyLen - -// A Key is a public, private, or pre-shared secret key. The Key constructor -// functions in this package can be used to create Keys suitable for each of -// these applications. -type Key [KeyLen]byte - -// GenerateKey generates a Key suitable for use as a pre-shared secret key from -// a cryptographically safe source. -// -// The output Key should not be used as a private key; use GeneratePrivateKey -// instead. -func GenerateKey() (Key, error) { - b := make([]byte, KeyLen) - if _, err := rand.Read(b); err != nil { - return Key{}, fmt.Errorf("wgtypes: failed to read random bytes: %v", err) - } - - return NewKey(b) -} - -// GeneratePrivateKey generates a Key suitable for use as a private key from a -// cryptographically safe source. -func GeneratePrivateKey() (Key, error) { - key, err := GenerateKey() - if err != nil { - return Key{}, err - } - - // Modify random bytes using algorithm described at: - // https://cr.yp.to/ecdh.html. - key[0] &= 248 - key[31] &= 127 - key[31] |= 64 - - return key, nil -} - -// NewKey creates a Key from an existing byte slice. The byte slice must be -// exactly 32 bytes in length. -func NewKey(b []byte) (Key, error) { - if len(b) != KeyLen { - return Key{}, fmt.Errorf("wgtypes: incorrect key size: %d", len(b)) - } - - var k Key - copy(k[:], b) - - return k, nil -} - -// ParseKey parses a Key from a base64-encoded string, as produced by the -// Key.String method. -func ParseKey(s string) (Key, error) { - b, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return Key{}, fmt.Errorf("wgtypes: failed to parse base64-encoded key: %v", err) - } - - return NewKey(b) -} - -// PublicKey computes a public key from the private key k. -// -// PublicKey should only be called when k is a private key. -func (k Key) PublicKey() Key { - var ( - pub [KeyLen]byte - priv = [KeyLen]byte(k) - ) - - // ScalarBaseMult uses the correct base value per https://cr.yp.to/ecdh.html, - // so no need to specify it. - curve25519.ScalarBaseMult(&pub, &priv) - - return Key(pub) -} - -// String returns the base64-encoded string representation of a Key. -// -// ParseKey can be used to produce a new Key from this string. -func (k Key) String() string { - return base64.StdEncoding.EncodeToString(k[:]) -} diff --git a/clash-meta/component/generater/cmd.go b/clash-meta/component/generator/cmd.go similarity index 58% rename from clash-meta/component/generater/cmd.go rename to clash-meta/component/generator/cmd.go index 2bd5b9cd86..537fba91bb 100644 --- a/clash-meta/component/generater/cmd.go +++ b/clash-meta/component/generator/cmd.go @@ -1,4 +1,4 @@ -package generater +package generator import ( "encoding/base64" @@ -12,7 +12,7 @@ import ( func Main(args []string) { if len(args) < 1 { - panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768") + panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768/vless-x25519") } switch args[0] { case "uuid": @@ -22,20 +22,19 @@ func Main(args []string) { } fmt.Println(newUUID.String()) case "reality-keypair": - privateKey, err := GeneratePrivateKey() + privateKey, err := GenX25519PrivateKey() if err != nil { panic(err) } - publicKey := privateKey.PublicKey() - fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:])) - fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:])) + fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey.Bytes())) + fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(privateKey.PublicKey().Bytes())) case "wg-keypair": - privateKey, err := GeneratePrivateKey() + privateKey, err := GenX25519PrivateKey() if err != nil { panic(err) } - fmt.Println("PrivateKey: " + privateKey.String()) - fmt.Println("PublicKey: " + privateKey.PublicKey().String()) + fmt.Println("PrivateKey: " + base64.StdEncoding.EncodeToString(privateKey.Bytes())) + fmt.Println("PublicKey: " + base64.StdEncoding.EncodeToString(privateKey.PublicKey().Bytes())) case "ech-keypair": if len(args) < 2 { panic("Using: generate ech-keypair ") @@ -51,11 +50,23 @@ func Main(args []string) { if len(args) > 1 { seed = args[1] } - seedBase64, clientBase64, err := encryption.GenMLKEM768(seed) + seedBase64, clientBase64, hash11Base64, err := encryption.GenMLKEM768(seed) if err != nil { panic(err) } fmt.Println("Seed: " + seedBase64) fmt.Println("Client: " + clientBase64) + fmt.Println("Hash11: " + hash11Base64) + case "vless-x25519": + var privateKey string + if len(args) > 1 { + privateKey = args[1] + } + privateKeyBase64, passwordBase64, err := encryption.GenX25519(privateKey) + if err != nil { + panic(err) + } + fmt.Println("PrivateKey: " + privateKeyBase64) + fmt.Println("Password: " + passwordBase64) } } diff --git a/clash-meta/component/generator/x25519.go b/clash-meta/component/generator/x25519.go new file mode 100644 index 0000000000..e99fff2bae --- /dev/null +++ b/clash-meta/component/generator/x25519.go @@ -0,0 +1,27 @@ +package generator + +import ( + "crypto/ecdh" + "crypto/rand" +) + +const X25519KeySize = 32 + +func GenX25519PrivateKey() (*ecdh.PrivateKey, error) { + var privateKey [X25519KeySize]byte + _, err := rand.Read(privateKey[:]) + if err != nil { + return nil, err + } + + // Avoid generating equivalent X25519 private keys + // https://github.com/XTLS/Xray-core/pull/1747 + // + // Modify random bytes using algorithm described at: + // https://cr.yp.to/ecdh.html. + privateKey[0] &= 248 + privateKey[31] &= 127 + privateKey[31] |= 64 + + return ecdh.X25519().NewPrivateKey(privateKey[:]) +} diff --git a/clash-meta/docs/config.yaml b/clash-meta/docs/config.yaml index 80af843bf9..0313bee71f 100644 --- a/clash-meta/docs/config.yaml +++ b/clash-meta/docs/config.yaml @@ -638,8 +638,12 @@ proxies: # socks5 port: 443 uuid: uuid network: tcp - encryption: "8min-vless-mlkem768client-bas64RawURLEncoding" # 复用八分钟后协商新的 sharedKey,需小于服务端的值 - # encryption: "8min-xored-mlkem768client-bas64RawURLEncoding" + # ------------------------- + # vless encryption客户端配置: + # (只使用 1-RTT 模式 / 复用八分钟后协商新的 baseKey,周期需小于服务端的值) + # / 是只能选一个,后面是 base64RawURLEncoding,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号 + # ------------------------- + encryption: "1rtt/8min.native/divide/random.mlkem768Client.(X25519 Password).(ML-KEM-768 Client)" tls: false #可以不开启tls udp: true @@ -1359,8 +1363,12 @@ listeners: flow: xtls-rprx-vision # ws-path: "/" # 如果不为空则开启 websocket 传输层 # grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层 - # decryption: "10min-vless-mlkem768seed-bas64RawURLEncoding" # 同时允许 1-RTT 模式与十分钟复用的 0-RTT 模式, 后面base64字符串可由可由 mihomo generate vless-mlkem768 命令生成 - # decryption: "10min-xored-mlkem768seed-bas64RawURLEncoding" + # ------------------------- + # vless encryption服务端配置: + # (只允许 1-RTT 模式 / 同时允许 1-RTT 模式与十分钟复用的 0-RTT 模式;原生外观 / ECH 式 XOR / 全随机数) + # / 是只能选一个,后面是 base64RawURLEncoding,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号 + # ------------------------- + # decryption: "1rtt/10min.native/divide/random.mlkem768Seed.(X25519 PrivateKey).(ML-KEM-768 Seed)" # 下面两项如果填写则开启 tls(需要同时填写) # certificate: ./server.crt # private-key: ./server.key diff --git a/clash-meta/go.mod b/clash-meta/go.mod index 5695c7827e..57c10000ba 100644 --- a/clash-meta/go.mod +++ b/clash-meta/go.mod @@ -31,7 +31,7 @@ require ( github.com/metacubex/sing-shadowsocks2 v0.2.6 github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 github.com/metacubex/sing-tun v0.4.7 - github.com/metacubex/sing-vmess v0.2.4-0.20250819151326-51d195aac5db + github.com/metacubex/sing-vmess v0.2.4-0.20250821024956-97839f31a65d github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f github.com/metacubex/smux v0.0.0-20250503055512-501391591dee github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 diff --git a/clash-meta/go.sum b/clash-meta/go.sum index 69811ad2ba..d8bfd9a592 100644 --- a/clash-meta/go.sum +++ b/clash-meta/go.sum @@ -131,8 +131,8 @@ github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MY github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= github.com/metacubex/sing-tun v0.4.7 h1:ZDY/W+1c7PeWWKeKRyUo18fySF/TWjB0i5ui81Ar778= github.com/metacubex/sing-tun v0.4.7/go.mod h1:xHecZRwBnKWe6zG9amAK9cXf91lF6blgjBqm+VvOrmU= -github.com/metacubex/sing-vmess v0.2.4-0.20250819151326-51d195aac5db h1:W7VKxR0r5IR+56Lblx2iyrEaykx0esdQwTQbkSrSaek= -github.com/metacubex/sing-vmess v0.2.4-0.20250819151326-51d195aac5db/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= +github.com/metacubex/sing-vmess v0.2.4-0.20250821024956-97839f31a65d h1:jchYEho5+kTmok4aTMflqJyTRnqVPTOVeC1RFXxuw9A= +github.com/metacubex/sing-vmess v0.2.4-0.20250821024956-97839f31a65d/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo= diff --git a/clash-meta/listener/inbound/common_test.go b/clash-meta/listener/inbound/common_test.go index 5b838bd303..75b98e0bc4 100644 --- a/clash-meta/listener/inbound/common_test.go +++ b/clash-meta/listener/inbound/common_test.go @@ -21,7 +21,7 @@ import ( "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/ech" - "github.com/metacubex/mihomo/component/generater" + "github.com/metacubex/mihomo/component/generator" tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" @@ -48,13 +48,12 @@ var echConfigBase64, echKeyPem, _ = ech.GenECHConfig(echPublicSni) func init() { rand.Read(httpData) - privateKey, err := generater.GeneratePrivateKey() + privateKey, err := generator.GenX25519PrivateKey() if err != nil { panic(err) } - publicKey := privateKey.PublicKey() - realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey[:]) - realityPublickey = base64.RawURLEncoding.EncodeToString(publicKey[:]) + realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey.Bytes()) + realityPublickey = base64.RawURLEncoding.EncodeToString(privateKey.PublicKey().Bytes()) } type TestTunnel struct { diff --git a/clash-meta/listener/inbound/vless_test.go b/clash-meta/listener/inbound/vless_test.go index f3fcd39cb0..4ac9654b3b 100644 --- a/clash-meta/listener/inbound/vless_test.go +++ b/clash-meta/listener/inbound/vless_test.go @@ -89,39 +89,38 @@ func TestInboundVless_TLS(t *testing.T) { } func TestInboundVless_Encryption(t *testing.T) { - seedBase64, clientBase64, err := encryption.GenMLKEM768("") + seedBase64, clientBase64, _, err := encryption.GenMLKEM768("") if err != nil { t.Fatal(err) return } - t.Run("-vless-", func(t *testing.T) { - inboundOptions := inbound.VlessOption{ - Decryption: "10min-vless-mlkem768seed-" + seedBase64, - } - outboundOptions := outbound.VlessOption{ - Encryption: "8min-vless-mlkem768client-" + clientBase64, - } - testInboundVless(t, inboundOptions, outboundOptions) - t.Run("xtls-rprx-vision", func(t *testing.T) { - outboundOptions := outboundOptions - outboundOptions.Flow = "xtls-rprx-vision" + privateKeyBase64, passwordBase64, err := encryption.GenX25519("") + if err != nil { + t.Fatal(err) + return + } + var modes = []string{ + "native", + "divide", + "random", + } + for i := range modes { + mode := modes[i] + t.Run(mode, func(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Decryption: "10min." + mode + ".mlkem768Seed." + privateKeyBase64 + "." + seedBase64, + } + outboundOptions := outbound.VlessOption{ + Encryption: "8min." + mode + ".mlkem768Client." + passwordBase64 + "." + clientBase64, + } testInboundVless(t, inboundOptions, outboundOptions) + t.Run("xtls-rprx-vision", func(t *testing.T) { + outboundOptions := outboundOptions + outboundOptions.Flow = "xtls-rprx-vision" + testInboundVless(t, inboundOptions, outboundOptions) + }) }) - }) - t.Run("-xored-", func(t *testing.T) { - inboundOptions := inbound.VlessOption{ - Decryption: "10min-xored-mlkem768seed-" + seedBase64, - } - outboundOptions := outbound.VlessOption{ - Encryption: "8min-xored-mlkem768client-" + clientBase64, - } - testInboundVless(t, inboundOptions, outboundOptions) - t.Run("xtls-rprx-vision", func(t *testing.T) { - outboundOptions := outboundOptions - outboundOptions.Flow = "xtls-rprx-vision" - testInboundVless(t, inboundOptions, outboundOptions) - }) - }) + } } func TestInboundVless_Wss1(t *testing.T) { diff --git a/clash-meta/main.go b/clash-meta/main.go index 3bc3d74f73..6a85e5df79 100644 --- a/clash-meta/main.go +++ b/clash-meta/main.go @@ -14,7 +14,7 @@ import ( "strings" "syscall" - "github.com/metacubex/mihomo/component/generater" + "github.com/metacubex/mihomo/component/generator" "github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/updater" "github.com/metacubex/mihomo/config" @@ -73,7 +73,7 @@ func main() { } if len(os.Args) > 1 && os.Args[1] == "generate" { - generater.Main(os.Args[2:]) + generator.Main(os.Args[2:]) return } diff --git a/clash-meta/transport/vless/encryption/client.go b/clash-meta/transport/vless/encryption/client.go index ecf20692ea..dfa4fa63b7 100644 --- a/clash-meta/transport/vless/encryption/client.go +++ b/clash-meta/transport/vless/encryption/client.go @@ -3,6 +3,7 @@ package encryption import ( "bytes" "crypto/cipher" + "crypto/ecdh" "crypto/rand" "errors" "fmt" @@ -40,7 +41,8 @@ type ClientInstance struct { sync.RWMutex nfsEKey *mlkem.EncapsulationKey768 hash11 [11]byte // no more capacity - xorKey []byte + xorMode uint32 + xorPKey *ecdh.PublicKey minutes time.Duration expire time.Time baseKey []byte @@ -60,22 +62,23 @@ type ClientConn struct { input bytes.Reader // peerCache } -func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) { +func (i *ClientInstance) Init(nfsEKeyBytes, xorPKeyBytes []byte, xorMode, minutes uint32) (err error) { if i.nfsEKey != nil { err = errors.New("already initialized") return } - i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes) - if err != nil { + if i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes); err != nil { return } hash32 := sha3.Sum256(nfsEKeyBytes) copy(i.hash11[:], hash32[:]) - if xor > 0 { - xorKey := sha3.Sum256(nfsEKeyBytes) - i.xorKey = xorKey[:] + if xorMode > 0 { + i.xorMode = xorMode + if i.xorPKey, err = ecdh.X25519().NewPublicKey(xorPKeyBytes); err != nil { + return + } } - i.minutes = minutes + i.minutes = time.Duration(minutes) * time.Minute return } @@ -83,8 +86,8 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*ClientConn, error) { if i.nfsEKey == nil { return nil, errors.New("uninitialized") } - if i.xorKey != nil { - conn = NewXorConn(conn, i.xorKey) + if i.xorMode > 0 { + conn, _ = NewXorConn(conn, i.xorMode, i.xorPKey, nil) } c := &ClientConn{Conn: conn} @@ -145,7 +148,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*ClientConn, error) { } c.baseKey = append(pfsKey, nfsKey...) - VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes) + VLESS, _ := NewAEAD(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes) if !bytes.Equal(VLESS, []byte("VLESS")) { return nil, errors.New("invalid server") } @@ -180,7 +183,7 @@ func (c *ClientConn) Write(b []byte) (int, error) { rand.Read(c.random) copy(data[5+32:], c.random) EncodeHeader(data[5+32+32:], 23, len(b)+16) - c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket) + c.aead = NewAEAD(ClientCipher, c.baseKey, c.random, c.ticket) c.nonce = make([]byte, 12) c.aead.Seal(data[:5+32+32+5], c.nonce, b, data[5+32+32:5+32+32+5]) } else { @@ -188,7 +191,7 @@ func (c *ClientConn) Write(b []byte) (int, error) { EncodeHeader(data, 23, 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]) + c.aead = NewAEAD(ClientCipher, c.baseKey, data[5:], data[:5]) } } IncreaseNonce(c.nonce) @@ -229,7 +232,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { if c.random == nil { return 0, errors.New("empty c.random") } - c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandomHello, c.random) + c.peerAead = NewAEAD(ClientCipher, c.baseKey, peerRandomHello, c.random) c.peerNonce = make([]byte, 12) } if c.input.Len() > 0 { @@ -252,7 +255,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { } var peerAead cipher.AEAD if bytes.Equal(c.peerNonce, MaxNonce) { - peerAead = NewAead(ClientCipher, c.baseKey, peerData, h) + peerAead = NewAEAD(ClientCipher, c.baseKey, peerData, h) } _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, h) if peerAead != nil { diff --git a/clash-meta/transport/vless/encryption/common.go b/clash-meta/transport/vless/encryption/common.go index a67a616927..d383d22e98 100644 --- a/clash-meta/transport/vless/encryption/common.go +++ b/clash-meta/transport/vless/encryption/common.go @@ -73,7 +73,7 @@ func ReadAndDiscardPaddings(conn net.Conn) (h []byte, t byte, l int, err error) } } -func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) { +func NewAEAD(c byte, secret, salt, info []byte) (aead cipher.AEAD) { key := make([]byte, 32) hkdf.New(sha3.New256, secret, salt, info).Read(key) if c&1 == 1 { diff --git a/clash-meta/transport/vless/encryption/doc.go b/clash-meta/transport/vless/encryption/doc.go index 6f0c8f7039..10cd320b2b 100644 --- a/clash-meta/transport/vless/encryption/doc.go +++ b/clash-meta/transport/vless/encryption/doc.go @@ -14,4 +14,5 @@ // https://github.com/XTLS/Xray-core/commit/d1fb48521271251a8c74bd64fcc2fc8700717a3b // https://github.com/XTLS/Xray-core/commit/49580705f6029648399304b816a2737f991582a8 // https://github.com/XTLS/Xray-core/commit/84835bec7d0d8555d0dd30953ed26a272de814c4 +// https://github.com/XTLS/Xray-core/commit/373558ed7abdbac3de41745cf30ec04c9adde604 package encryption diff --git a/clash-meta/transport/vless/encryption/factory.go b/clash-meta/transport/vless/encryption/factory.go index 4988dbbfb9..d7e4c7e003 100644 --- a/clash-meta/transport/vless/encryption/factory.go +++ b/clash-meta/transport/vless/encryption/factory.go @@ -5,7 +5,6 @@ import ( "fmt" "strconv" "strings" - "time" ) // NewClient new client from encryption string @@ -15,7 +14,7 @@ func NewClient(encryption string) (*ClientInstance, error) { 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" { + if s := strings.Split(encryption, "."); len(s) == 5 && s[2] == "mlkem768Client" { var minutes uint32 if s[0] != "1rtt" { t := strings.TrimSuffix(s[0], "min") @@ -28,27 +27,35 @@ func NewClient(encryption string) (*ClientInstance, error) { } minutes = uint32(i) } - var xor uint32 + var xorMode uint32 switch s[1] { - case "vless": - case "xored": - xor = 1 + case "native": + case "divide": + xorMode = 1 + case "random": + xorMode = 2 default: return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) } - b, err := base64.RawURLEncoding.DecodeString(s[3]) + xorPKeyBytes, 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 { + if len(xorPKeyBytes) != X25519PasswordSize { return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) } + nfsEKeyBytes, err := base64.RawURLEncoding.DecodeString(s[4]) + if err != nil { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + if len(nfsEKeyBytes) != MLKEM768ClientLength { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + client := &ClientInstance{} + if err = client.Init(nfsEKeyBytes, xorPKeyBytes, xorMode, minutes); err != nil { + return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) + } + return client, nil } return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) } @@ -60,7 +67,7 @@ func NewServer(decryption string) (*ServerInstance, error) { 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" { + if s := strings.Split(decryption, "."); len(s) == 5 && s[2] == "mlkem768Seed" { var minutes uint32 if s[0] != "1rtt" { t := strings.TrimSuffix(s[0], "min") @@ -73,27 +80,35 @@ func NewServer(decryption string) (*ServerInstance, error) { } minutes = uint32(i) } - var xor uint32 + var xorMode uint32 switch s[1] { - case "vless": - case "xored": - xor = 1 + case "native": + case "divide": + xorMode = 1 + case "random": + xorMode = 2 default: return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) } - b, err := base64.RawURLEncoding.DecodeString(s[3]) + xorSKeyBytes, 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 { + if len(xorSKeyBytes) != X25519PrivateKeySize { return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) } + nfsDKeySeed, err := base64.RawURLEncoding.DecodeString(s[4]) + if err != nil { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + if len(nfsDKeySeed) != MLKEM768SeedLength { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + server := &ServerInstance{} + if err = server.Init(nfsDKeySeed, xorSKeyBytes, xorMode, minutes); err != nil { + return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) + } + return server, nil } return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) } diff --git a/clash-meta/transport/vless/encryption/key.go b/clash-meta/transport/vless/encryption/key.go index 69b5289579..03ff284ef4 100644 --- a/clash-meta/transport/vless/encryption/key.go +++ b/clash-meta/transport/vless/encryption/key.go @@ -1,25 +1,29 @@ package encryption import ( + "crypto/ecdh" "crypto/rand" "encoding/base64" "fmt" "github.com/metacubex/utls/mlkem" + "golang.org/x/crypto/sha3" ) const MLKEM768SeedLength = mlkem.SeedSize const MLKEM768ClientLength = mlkem.EncapsulationKeySize768 +const X25519PasswordSize = 32 +const X25519PrivateKeySize = 32 -func GenMLKEM768(seedStr string) (seedBase64, clientBase64 string, err error) { - var seed [64]byte +func GenMLKEM768(seedStr string) (seedBase64, clientBase64, hash11Base64 string, err error) { + var seed [MLKEM768SeedLength]byte if len(seedStr) > 0 { s, _ := base64.RawURLEncoding.DecodeString(seedStr) - if len(s) != 64 { + if len(s) != MLKEM768SeedLength { err = fmt.Errorf("invalid length of ML-KEM-768 seed: %s", seedStr) return } - seed = [64]byte(s) + seed = [MLKEM768SeedLength]byte(s) } else { _, err = rand.Read(seed[:]) if err != nil { @@ -28,8 +32,45 @@ func GenMLKEM768(seedStr string) (seedBase64, clientBase64 string, err error) { } key, _ := mlkem.NewDecapsulationKey768(seed[:]) - pub := key.EncapsulationKey() + client := key.EncapsulationKey().Bytes() + hash32 := sha3.Sum256(client) seedBase64 = base64.RawURLEncoding.EncodeToString(seed[:]) - clientBase64 = base64.RawURLEncoding.EncodeToString(pub.Bytes()) + clientBase64 = base64.RawURLEncoding.EncodeToString(client) + hash11Base64 = base64.RawURLEncoding.EncodeToString(hash32[:11]) + return +} + +func GenX25519(privateKeyStr string) (privateKeyBase64, passwordBase64 string, err error) { + var privateKey [X25519PrivateKeySize]byte + if len(privateKeyStr) > 0 { + s, _ := base64.RawURLEncoding.DecodeString(privateKeyStr) + if len(s) != X25519PrivateKeySize { + err = fmt.Errorf("invalid length of X25519 private key: %s", privateKeyStr) + return + } + privateKey = [X25519PrivateKeySize]byte(s) + } else { + _, err = rand.Read(privateKey[:]) + if err != nil { + return + } + } + + // Avoid generating equivalent X25519 private keys + // https://github.com/XTLS/Xray-core/pull/1747 + // + // Modify random bytes using algorithm described at: + // https://cr.yp.to/ecdh.html. + privateKey[0] &= 248 + privateKey[31] &= 127 + privateKey[31] |= 64 + + key, err := ecdh.X25519().NewPrivateKey(privateKey[:]) + if err != nil { + fmt.Println(err.Error()) + return + } + privateKeyBase64 = base64.RawURLEncoding.EncodeToString(privateKey[:]) + passwordBase64 = base64.RawURLEncoding.EncodeToString(key.PublicKey().Bytes()) return } diff --git a/clash-meta/transport/vless/encryption/server.go b/clash-meta/transport/vless/encryption/server.go index ea72f2f5cf..baa8432cd0 100644 --- a/clash-meta/transport/vless/encryption/server.go +++ b/clash-meta/transport/vless/encryption/server.go @@ -3,6 +3,7 @@ package encryption import ( "bytes" "crypto/cipher" + "crypto/ecdh" "crypto/rand" "errors" "fmt" @@ -26,7 +27,8 @@ type ServerInstance struct { sync.RWMutex nfsDKey *mlkem.DecapsulationKey768 hash11 [11]byte // no more capacity - xorKey []byte + xorMode uint32 + xorSKey *ecdh.PrivateKey minutes time.Duration sessions map[[32]byte]*ServerSession closed bool @@ -45,23 +47,24 @@ type ServerConn struct { nonce []byte } -func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Duration) (err error) { +func (i *ServerInstance) Init(nfsDKeySeed, xorSKeyBytes []byte, xorMode, minutes uint32) (err error) { if i.nfsDKey != nil { err = errors.New("already initialized") return } - i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed) - if err != nil { + if i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed); err != nil { return } hash32 := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) copy(i.hash11[:], hash32[:]) - if xor > 0 { - xorKey := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) - i.xorKey = xorKey[:] + if xorMode > 0 { + i.xorMode = xorMode + if i.xorSKey, err = ecdh.X25519().NewPrivateKey(xorSKeyBytes); err != nil { + return + } } if minutes > 0 { - i.minutes = minutes + i.minutes = time.Duration(minutes) * time.Minute i.sessions = make(map[[32]byte]*ServerSession) go func() { for { @@ -95,8 +98,11 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*ServerConn, error) { if i.nfsDKey == nil { return nil, errors.New("uninitialized") } - if i.xorKey != nil { - conn = NewXorConn(conn, i.xorKey) + if i.xorMode > 0 { + var err error + if conn, err = NewXorConn(conn, i.xorMode, nil, i.xorSKey); err != nil { + return nil, err + } } c := &ServerConn{Conn: conn} @@ -167,7 +173,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*ServerConn, error) { pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate() c.baseKey = append(pfsKey, nfsKey...) - c.ticket = append(i.hash11[:], NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, peerClientHello[:12], []byte("VLESS"), pfsEKeyBytes)...) + c.ticket = append(i.hash11[:], NewAEAD(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, peerClientHello[:12], []byte("VLESS"), pfsEKeyBytes)...) paddingLen := randBetween(100, 1000) @@ -221,7 +227,7 @@ func (c *ServerConn) Read(b []byte) (int, error) { } c.peerRandom = peerTicketHello[32:] } - c.peerAead = NewAead(c.cipher, c.baseKey, c.peerRandom, c.ticket) + c.peerAead = NewAEAD(c.cipher, c.baseKey, c.peerRandom, c.ticket) c.peerNonce = make([]byte, 12) } if c.input.Len() > 0 { @@ -244,7 +250,7 @@ func (c *ServerConn) Read(b []byte) (int, error) { } var peerAead cipher.AEAD if bytes.Equal(c.peerNonce, MaxNonce) { - peerAead = NewAead(c.cipher, c.baseKey, peerData, h) + peerAead = NewAEAD(c.cipher, c.baseKey, peerData, h) } _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, h) if peerAead != nil { @@ -280,7 +286,7 @@ func (c *ServerConn) Write(b []byte) (int, error) { EncodeHeader(data, 0, 32) rand.Read(data[5 : 5+32]) EncodeHeader(data[5+32:], 23, len(b)+16) - c.aead = NewAead(c.cipher, c.baseKey, data[5:5+32], c.peerRandom) + c.aead = NewAEAD(c.cipher, c.baseKey, data[5:5+32], c.peerRandom) c.nonce = make([]byte, 12) c.aead.Seal(data[:5+32+5], c.nonce, b, data[5+32:5+32+5]) } else { @@ -288,7 +294,7 @@ func (c *ServerConn) Write(b []byte) (int, error) { EncodeHeader(data, 23, 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]) + c.aead = NewAEAD(c.cipher, c.baseKey, data[5:], data[:5]) } } IncreaseNonce(c.nonce) diff --git a/clash-meta/transport/vless/encryption/xor.go b/clash-meta/transport/vless/encryption/xor.go index caad12bf07..88bd76982e 100644 --- a/clash-meta/transport/vless/encryption/xor.go +++ b/clash-meta/transport/vless/encryption/xor.go @@ -3,13 +3,21 @@ package encryption import ( "crypto/aes" "crypto/cipher" + "crypto/ecdh" "crypto/rand" + "errors" "io" "net" + + "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/sha3" ) type XorConn struct { net.Conn + Divide bool + + head []byte key []byte ctr cipher.Stream peerCtr cipher.Stream @@ -25,8 +33,55 @@ type XorConn struct { in_skip int } -func NewXorConn(conn net.Conn, key []byte) *XorConn { - return &XorConn{Conn: conn, key: key} +func NewCTR(key, iv []byte, isServer bool) cipher.Stream { + info := "CLIENT" + if isServer { + info = "SERVER" // avoids attackers sending traffic back to the client, though the encryption layer has its own protection + } + hkdf.New(sha3.New256, key, iv, []byte(info)).Read(key) // avoids using pKey directly if attackers sent the basepoint, or whaterver they like + block, _ := aes.NewCipher(key) + return cipher.NewCTR(block, iv) +} + +func NewXorConn(conn net.Conn, mode uint32, pKey *ecdh.PublicKey, sKey *ecdh.PrivateKey) (*XorConn, error) { + if mode == 0 || (pKey == nil && sKey == nil) || (pKey != nil && sKey != nil) { + return nil, errors.New("invalid parameters") + } + c := &XorConn{ + Conn: conn, + Divide: mode == 1, + isHeader: true, + out_header: make([]byte, 0, 5), // important + in_header: make([]byte, 0, 5), // important + } + if pKey != nil { + c.head = make([]byte, 16+32) + rand.Read(c.head) + eSKey, _ := ecdh.X25519().GenerateKey(rand.Reader) + NewCTR(pKey.Bytes(), c.head[:16], false).XORKeyStream(c.head[16:], eSKey.PublicKey().Bytes()) // make X25519 public key distinguishable from random bytes + c.key, _ = eSKey.ECDH(pKey) + c.ctr = NewCTR(c.key, c.head[:16], false) + } + if sKey != nil { + peerHead := make([]byte, 16+32) + if _, err := io.ReadFull(c.Conn, peerHead); err != nil { + return nil, err + } + NewCTR(sKey.PublicKey().Bytes(), peerHead[:16], false).XORKeyStream(peerHead[16:], peerHead[16:]) // we don't use buggy elligator, because we have PSK :) + ePKey, err := ecdh.X25519().NewPublicKey(peerHead[16:]) + if err != nil { + return nil, err + } + key, err := sKey.ECDH(ePKey) + if err != nil { + return nil, err + } + c.peerCtr = NewCTR(key, peerHead[:16], false) + c.head = make([]byte, 16) + rand.Read(c.head) // make sure the server always replies random bytes even when received replays, though it is not important + c.ctr = NewCTR(key, c.head, true) // the same key links the upload & download, though the encryption layer has its own link + } + return c, nil //chacha20.NewUnauthenticatedCipher() } @@ -35,13 +90,6 @@ func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records return 0, nil } if !c.out_after0 { - var iv []byte - if c.ctr == nil { - block, _ := aes.NewCipher(c.key) - iv = make([]byte, 16) - rand.Read(iv) - c.ctr = cipher.NewCTR(block, iv) - } t, l, _ := DecodeHeader(b) if t == 23 { // single 23 l = 5 @@ -49,20 +97,24 @@ func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records l += 10 if t == 0 { c.out_after0 = true - c.out_header = make([]byte, 0, 5) // important + if c.Divide { + l -= 5 + } } } c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard b - if iv != nil { - b = append(iv, b...) + l = len(b) + if c.head != nil { + b = append(c.head, b...) + c.head = nil } if _, err := c.Conn.Write(b); err != nil { return 0, err } - if iv != nil { - b = b[16:] // for len(b) - } - return len(b), nil + return l, nil + } + if c.Divide { + return c.Conn.Write(b) } for p := b; ; { // for XTLS if len(p) <= c.out_skip { @@ -93,14 +145,12 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes... return 0, nil } if !c.in_after0 || !c.isHeader { - if c.peerCtr == nil { + if c.peerCtr == nil { // for client peerIv := make([]byte, 16) if _, err := io.ReadFull(c.Conn, peerIv); err != nil { return 0, err } - block, _ := aes.NewCipher(c.key) - c.peerCtr = cipher.NewCTR(block, peerIv) - c.isHeader = true + c.peerCtr = NewCTR(c.key, peerIv, true) } if _, err := io.ReadFull(c.Conn, b); err != nil { return 0, err @@ -117,7 +167,6 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes... c.isHeader = false if t == 0 { c.in_after0 = true - c.in_header = make([]byte, 0, 5) // important } } } else { @@ -125,6 +174,9 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes... } return len(b), nil } + if c.Divide { + return c.Conn.Read(b) + } n, err := c.Conn.Read(b) for p := b[:n]; ; { // for XTLS if len(p) <= c.in_skip { @@ -146,3 +198,27 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes... } return n, err } + +func (c *XorConn) WriterReplaceable() bool { + if !c.Divide { // never replaceable + return false + } + if !c.out_after0 { + return false + } + return true +} + +func (c *XorConn) ReaderReplaceable() bool { + if !c.Divide { // never replaceable + return false + } + if !c.in_after0 || !c.isHeader { + return false + } + return true +} + +func (c *XorConn) Upstream() any { + return c.Conn +} diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index 214ad1edd0..5e2bd7f52a 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -19,7 +19,7 @@ "@mui/icons-material": "7.3.1", "@mui/lab": "7.0.0-beta.16", "@mui/material": "7.3.1", - "@mui/x-date-pickers": "8.10.0", + "@mui/x-date-pickers": "8.10.2", "@nyanpasu/interface": "workspace:^", "@nyanpasu/ui": "workspace:^", "@tailwindcss/postcss": "4.1.12", @@ -78,7 +78,7 @@ "@vitejs/plugin-react-swc": "4.0.1", "change-case": "5.4.4", "clsx": "2.1.1", - "core-js": "3.45.0", + "core-js": "3.45.1", "filesize": "11.0.2", "meta-json-schema": "1.19.12", "monaco-yaml": "5.4.0", diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index e9e7d5f2f9..8f6621fcca 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,7 +2,7 @@ "manifest_version": 1, "latest": { "mihomo": "v1.19.12", - "mihomo_alpha": "alpha-2790481", + "mihomo_alpha": "alpha-5f09db2", "clash_rs": "v0.9.0", "clash_premium": "2023-09-05-gdcc8d87", "clash_rs_alpha": "0.9.0-alpha+sha.51d55ef" @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-08-19T22:21:14.276Z" + "updated_at": "2025-08-20T22:21:19.472Z" } diff --git a/clash-nyanpasu/package.json b/clash-nyanpasu/package.json index 36ed6a72a3..b38352fc39 100644 --- a/clash-nyanpasu/package.json +++ b/clash-nyanpasu/package.json @@ -67,8 +67,8 @@ "@types/fs-extra": "11.0.4", "@types/lodash-es": "4.17.12", "@types/node": "22.17.2", - "@typescript-eslint/eslint-plugin": "8.39.1", - "@typescript-eslint/parser": "8.39.1", + "@typescript-eslint/eslint-plugin": "8.40.0", + "@typescript-eslint/parser": "8.40.0", "autoprefixer": "10.4.21", "conventional-changelog-conventionalcommits": "9.1.0", "cross-env": "10.0.0", @@ -107,7 +107,7 @@ "tailwindcss": "4.1.12", "tsx": "4.20.4", "typescript": "5.9.2", - "typescript-eslint": "8.39.1" + "typescript-eslint": "8.40.0" }, "packageManager": "pnpm@10.14.0", "engines": { diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 95af83b32b..f3ecfaef6e 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -50,11 +50,11 @@ importers: specifier: 22.17.2 version: 22.17.2 '@typescript-eslint/eslint-plugin': - specifier: 8.39.1 - version: 8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + specifier: 8.40.0 + version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/parser': - specifier: 8.39.1 - version: 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + specifier: 8.40.0 + version: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) autoprefixer: specifier: 10.4.21 version: 10.4.21(postcss@8.5.6) @@ -75,13 +75,13 @@ importers: version: 10.1.8(eslint@9.33.0(jiti@2.5.1)) eslint-import-resolver-alias: specifier: 1.1.2 - version: 1.1.2(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))) + version: 1.1.2(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))) eslint-plugin-html: specifier: 8.1.3 version: 8.1.3 eslint-plugin-import: specifier: 2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)) + version: 2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-n: specifier: 17.21.3 version: 17.21.3(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) @@ -111,7 +111,7 @@ importers: version: 16.1.5 neostandard: specifier: 0.12.2 - version: 0.12.2(@typescript-eslint/utils@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 0.12.2(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) npm-run-all2: specifier: 8.0.4 version: 8.0.4 @@ -170,8 +170,8 @@ importers: specifier: 5.9.2 version: 5.9.2 typescript-eslint: - specifier: 8.39.1 - version: 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + specifier: 8.40.0 + version: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) frontend/interface: dependencies: @@ -237,8 +237,8 @@ importers: specifier: 7.3.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.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) + specifier: 8.10.2 + version: 8.10.2(@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 @@ -286,7 +286,7 @@ importers: version: 0.4.0 material-react-table: specifier: npm:@greenhat616/material-react-table@4.0.0 - version: '@greenhat616/material-react-table@4.0.0(098023d060c5c1c6a96afb3bdaa051b0)' + version: '@greenhat616/material-react-table@4.0.0(d0e09da0dd57ee89e64ea69924c8dcff)' monaco-editor: specifier: 0.52.2 version: 0.52.2 @@ -307,7 +307,7 @@ 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(aea177882beb7723aeada5c99e57089b) + version: 7.6.2(8d03dcd7938824115d8443966a6d9038) react-i18next: specifier: 15.6.1 version: 15.6.1(i18next@25.3.6(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2) @@ -409,8 +409,8 @@ importers: specifier: 2.1.1 version: 2.1.1 core-js: - specifier: 3.45.0 - version: 3.45.0 + specifier: 3.45.1 + version: 3.45.1 filesize: specifier: 11.0.2 version: 11.0.2 @@ -1970,8 +1970,8 @@ packages: '@types/react': optional: true - '@mui/x-date-pickers@8.10.0': - resolution: {integrity: sha512-3nY+SS2/JtqcptQodECIyWKsTvPBDAcXKkyW65R4rQUCrnV6tuzriSrzy/FEYqTK0hyXYPIGJhQ6A0FbtQ9AkQ==} + '@mui/x-date-pickers@8.10.2': + resolution: {integrity: sha512-eS5t1jUojN/jL2FeJ8gtpCBxIEswUp9kLjM64aJ5LUKrNgM7X9dwsEHyplS+x07kWLiEAhO3nX3mepnS3Z43qg==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.9.0 @@ -2007,8 +2007,8 @@ packages: moment-jalaali: optional: true - '@mui/x-internals@8.10.0': - resolution: {integrity: sha512-stYhWBeCKfV2/ltAWShZ3ZJ51otbqpMpC+krWWoIsxM8TuvGzwXw5YMU9L2fTb8hRstsiOCQfEzIn12Ii7+N0Q==} + '@mui/x-internals@8.10.2': + resolution: {integrity: sha512-dlC0BQRRBdiWtqn1yDppaHYRUjU3OuPWTxy0UtqxDaJjJf4pfR8ALr243nbxgJAFqvQyWPWyO4A6p9x9eJMJEQ==} engines: {node: '>=14.0.0'} peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3427,16 +3427,16 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.39.1': - resolution: {integrity: sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==} + '@typescript-eslint/eslint-plugin@8.40.0': + resolution: {integrity: sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.39.1 + '@typescript-eslint/parser': ^8.40.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.39.1': - resolution: {integrity: sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==} + '@typescript-eslint/parser@8.40.0': + resolution: {integrity: sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3448,8 +3448,8 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/project-service@8.39.1': - resolution: {integrity: sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==} + '@typescript-eslint/project-service@8.40.0': + resolution: {integrity: sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' @@ -3458,8 +3458,8 @@ packages: resolution: {integrity: sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.39.1': - resolution: {integrity: sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==} + '@typescript-eslint/scope-manager@8.40.0': + resolution: {integrity: sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/tsconfig-utils@8.38.0': @@ -3468,20 +3468,20 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/tsconfig-utils@8.39.0': - resolution: {integrity: sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/tsconfig-utils@8.39.1': resolution: {integrity: sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.39.1': - resolution: {integrity: sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==} + '@typescript-eslint/tsconfig-utils@8.40.0': + resolution: {integrity: sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.40.0': + resolution: {integrity: sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3495,22 +3495,22 @@ packages: resolution: {integrity: sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.39.0': - resolution: {integrity: sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.39.1': resolution: {integrity: sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.40.0': + resolution: {integrity: sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.38.0': resolution: {integrity: sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/typescript-estree@8.39.1': - resolution: {integrity: sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==} + '@typescript-eslint/typescript-estree@8.40.0': + resolution: {integrity: sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' @@ -3522,8 +3522,8 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.39.1': - resolution: {integrity: sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==} + '@typescript-eslint/utils@8.40.0': + resolution: {integrity: sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3533,8 +3533,8 @@ packages: resolution: {integrity: sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.39.1': - resolution: {integrity: sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==} + '@typescript-eslint/visitor-keys@8.40.0': + resolution: {integrity: sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.2.0': @@ -4253,8 +4253,8 @@ packages: core-js-compat@3.44.0: resolution: {integrity: sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==} - core-js@3.45.0: - resolution: {integrity: sha512-c2KZL9lP4DjkN3hk/an4pWn5b5ZefhRJnAc42n6LJ19kSnbeRbdQZE5dSeE2LBol1OwJD3X1BQvFTAsa8ReeDA==} + core-js@3.45.1: + resolution: {integrity: sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==} cosmiconfig-typescript-loader@6.1.0: resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==} @@ -8133,8 +8133,8 @@ packages: typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - typescript-eslint@8.39.1: - resolution: {integrity: sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==} + typescript-eslint@8.40.0: + resolution: {integrity: sha512-Xvd2l+ZmFDPEt4oj1QEXzA4A2uUK6opvKu3eGN9aGjB8au02lIVcLyi375w94hHyejTOmzIU77L8ol2sRg9n7Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -8765,7 +8765,7 @@ snapshots: '@babel/generator@7.17.7': dependencies: - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 jsesc: 2.5.2 source-map: 0.5.7 optional: true @@ -8856,20 +8856,20 @@ snapshots: '@babel/helper-environment-visitor@7.24.7': dependencies: - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 optional: true '@babel/helper-function-name@7.24.7': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 optional: true '@babel/helper-globals@7.28.0': {} '@babel/helper-hoist-variables@7.24.7': dependencies: - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 optional: true '@babel/helper-member-expression-to-functions@7.25.9': @@ -8982,7 +8982,7 @@ snapshots: '@babel/helper-split-export-declaration@7.24.7': dependencies: - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 optional: true '@babel/helper-string-parser@7.27.1': {} @@ -9574,13 +9574,13 @@ snapshots: '@babel/traverse@7.23.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.3 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-function-name': 7.24.7 '@babel/helper-hoist-variables': 7.24.7 '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 debug: 4.4.1 globals: 11.12.0 transitivePeerDependencies: @@ -10032,13 +10032,13 @@ snapshots: '@fastify/busboy@2.1.1': {} - '@greenhat616/material-react-table@4.0.0(098023d060c5c1c6a96afb3bdaa051b0)': + '@greenhat616/material-react-table@4.0.0(d0e09da0dd57ee89e64ea69924c8dcff)': dependencies: '@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) + '@mui/x-date-pickers': 8.10.2(@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) @@ -10286,13 +10286,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.10 - '@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)': + '@mui/x-date-pickers@8.10.2(@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.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) + '@mui/x-internals': 8.10.2(@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 @@ -10306,7 +10306,7 @@ snapshots: transitivePeerDependencies: - '@types/react' - '@mui/x-internals@8.10.0(@types/react@19.1.10)(react@19.1.1)': + '@mui/x-internals@8.10.2(@types/react@19.1.10)(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 '@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1) @@ -11377,7 +11377,7 @@ snapshots: '@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.6.2)': dependencies: '@babel/generator': 7.17.7 - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.3 '@babel/traverse': 7.23.2 '@babel/types': 7.17.0 javascript-natural-sort: 0.7.1 @@ -11657,14 +11657,14 @@ snapshots: '@types/node': 22.17.2 optional: true - '@typescript-eslint/eslint-plugin@8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.39.1 - '@typescript-eslint/type-utils': 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/utils': 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.39.1 + '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.40.0 + '@typescript-eslint/type-utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.40.0 eslint: 9.33.0(jiti@2.5.1) graphemer: 1.4.0 ignore: 7.0.5 @@ -11674,12 +11674,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: - '@typescript-eslint/scope-manager': 8.39.1 - '@typescript-eslint/types': 8.39.1 - '@typescript-eslint/typescript-estree': 8.39.1(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.39.1 + '@typescript-eslint/scope-manager': 8.40.0 + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.40.0 debug: 4.4.1 eslint: 9.33.0(jiti@2.5.1) typescript: 5.9.2 @@ -11688,17 +11688,17 @@ snapshots: '@typescript-eslint/project-service@8.38.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.9.2) - '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/tsconfig-utils': 8.39.1(typescript@5.9.2) + '@typescript-eslint/types': 8.39.1 debug: 4.4.1 typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.39.1(typescript@5.9.2)': + '@typescript-eslint/project-service@8.40.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.39.1(typescript@5.9.2) - '@typescript-eslint/types': 8.39.1 + '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2) + '@typescript-eslint/types': 8.40.0 debug: 4.4.1 typescript: 5.9.2 transitivePeerDependencies: @@ -11709,28 +11709,28 @@ snapshots: '@typescript-eslint/types': 8.38.0 '@typescript-eslint/visitor-keys': 8.38.0 - '@typescript-eslint/scope-manager@8.39.1': + '@typescript-eslint/scope-manager@8.40.0': dependencies: - '@typescript-eslint/types': 8.39.1 - '@typescript-eslint/visitor-keys': 8.39.1 + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/visitor-keys': 8.40.0 '@typescript-eslint/tsconfig-utils@8.38.0(typescript@5.9.2)': dependencies: typescript: 5.9.2 - '@typescript-eslint/tsconfig-utils@8.39.0(typescript@5.9.2)': - dependencies: - typescript: 5.9.2 - '@typescript-eslint/tsconfig-utils@8.39.1(typescript@5.9.2)': dependencies: typescript: 5.9.2 - '@typescript-eslint/type-utils@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.40.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.39.1 - '@typescript-eslint/typescript-estree': 8.39.1(typescript@5.9.2) - '@typescript-eslint/utils': 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + typescript: 5.9.2 + + '@typescript-eslint/type-utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + dependencies: + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) debug: 4.4.1 eslint: 9.33.0(jiti@2.5.1) ts-api-utils: 2.1.0(typescript@5.9.2) @@ -11742,10 +11742,10 @@ snapshots: '@typescript-eslint/types@8.38.0': {} - '@typescript-eslint/types@8.39.0': {} - '@typescript-eslint/types@8.39.1': {} + '@typescript-eslint/types@8.40.0': {} + '@typescript-eslint/typescript-estree@8.38.0(typescript@5.9.2)': dependencies: '@typescript-eslint/project-service': 8.38.0(typescript@5.9.2) @@ -11762,12 +11762,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.39.1(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.40.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/project-service': 8.39.1(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.39.1(typescript@5.9.2) - '@typescript-eslint/types': 8.39.1 - '@typescript-eslint/visitor-keys': 8.39.1 + '@typescript-eslint/project-service': 8.40.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2) + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/visitor-keys': 8.40.0 debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -11789,12 +11789,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) - '@typescript-eslint/scope-manager': 8.39.1 - '@typescript-eslint/types': 8.39.1 - '@typescript-eslint/typescript-estree': 8.39.1(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.40.0 + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) eslint: 9.33.0(jiti@2.5.1) typescript: 5.9.2 transitivePeerDependencies: @@ -11805,9 +11805,9 @@ snapshots: '@typescript-eslint/types': 8.38.0 eslint-visitor-keys: 4.2.1 - '@typescript-eslint/visitor-keys@8.39.1': + '@typescript-eslint/visitor-keys@8.40.0': dependencies: - '@typescript-eslint/types': 8.39.1 + '@typescript-eslint/types': 8.40.0 eslint-visitor-keys: 4.2.1 '@ungap/structured-clone@1.2.0': {} @@ -11881,7 +11881,7 @@ snapshots: babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.0) browserslist: 4.25.1 browserslist-to-esbuild: 2.1.1(browserslist@4.25.1) - core-js: 3.45.0 + core-js: 3.45.1 magic-string: 0.30.17 regenerator-runtime: 0.14.1 systemjs: 6.15.1 @@ -12584,7 +12584,7 @@ snapshots: dependencies: browserslist: 4.25.1 - core-js@3.45.0: {} + core-js@3.45.1: {} cosmiconfig-typescript-loader@6.1.0(@types/node@22.17.2)(cosmiconfig@9.0.0(typescript@5.9.2))(typescript@5.9.2): dependencies: @@ -13397,9 +13397,9 @@ snapshots: optionalDependencies: unrs-resolver: 1.10.1 - eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))): + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))): dependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)) eslint-import-resolver-node@0.3.9: dependencies: @@ -13409,7 +13409,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -13420,16 +13420,16 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.10.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)) - eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)) + eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: @@ -13446,7 +13446,7 @@ snapshots: dependencies: htmlparser2: 10.0.0 - eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)): + eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)): dependencies: '@typescript-eslint/types': 8.35.1 comment-parser: 1.4.1 @@ -13459,12 +13459,12 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.10.1 optionalDependencies: - '@typescript-eslint/utils': 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -13475,7 +13475,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -13487,7 +13487,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -15286,20 +15286,20 @@ snapshots: sax: 1.3.0 optional: true - neostandard@0.12.2(@typescript-eslint/utils@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2): + neostandard@0.12.2(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2): dependencies: '@humanwhocodes/gitignore-to-minimatch': 1.0.2 '@stylistic/eslint-plugin': 2.11.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.33.0(jiti@2.5.1) - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) - eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) + eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-n: 17.21.3(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) eslint-plugin-promise: 7.2.1(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@2.5.1)) find-up: 5.0.0 globals: 15.15.0 peowly: 1.3.2 - typescript-eslint: 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + typescript-eslint: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) transitivePeerDependencies: - '@typescript-eslint/utils' - eslint-import-resolver-node @@ -15847,14 +15847,14 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - react-hook-form-mui@7.6.2(aea177882beb7723aeada5c99e57089b): + react-hook-form-mui@7.6.2(8d03dcd7938824115d8443966a6d9038): dependencies: '@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.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) + '@mui/x-date-pickers': 8.10.2(@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: @@ -17000,12 +17000,12 @@ snapshots: dependencies: is-typedarray: 1.0.0 - typescript-eslint@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2): + typescript-eslint@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/parser': 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/typescript-estree': 8.39.1(typescript@5.9.2) - '@typescript-eslint/utils': 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.33.0(jiti@2.5.1) typescript: 5.9.2 transitivePeerDependencies: diff --git a/lede/package/boot/uboot-sunxi/Makefile b/lede/package/boot/uboot-sunxi/Makefile index 19731ed405..a30238082b 100644 --- a/lede/package/boot/uboot-sunxi/Makefile +++ b/lede/package/boot/uboot-sunxi/Makefile @@ -91,6 +91,12 @@ define U-Boot/Bananapro BUILD_DEVICES:=lemaker_bananapro endef +define U-Boot/beelink_x2 + BUILD_SUBTARGET:=cortexa7 + NAME:=Beelink X2 (H3) + BUILD_DEVICES:=roofull_beelink-x2 +endef + define U-Boot/Cubieboard BUILD_SUBTARGET:=cortexa8 NAME:=Cubieboard @@ -112,6 +118,7 @@ endef define U-Boot/Hummingbird_A31 BUILD_SUBTARGET:=cortexa7 NAME:=Hummingbird A31 board + BUILD_DEVICES:=merrii_hummingbird endef define U-Boot/Marsboard_A10 @@ -138,6 +145,12 @@ define U-Boot/Linksprite_pcDuino BUILD_DEVICES:=linksprite_a10-pcduino endef +define U-Boot/LicheePi_Zero + BUILD_SUBTARGET:=cortexa7 + NAME:=Lichee Pi Zero V3s + BUILD_DEVICES:=licheepi_licheepi-zero-dock +endef + define U-Boot/Linksprite_pcDuino3 BUILD_SUBTARGET:=cortexa7 NAME:=Linksprite pcDuino3 @@ -337,6 +350,15 @@ define U-Boot/orangepi_zero2 ATF:=h616 endef +define U-Boot/orangepi_zero2w + BUILD_SUBTARGET:=cortexa53 + NAME:=Xunlong Orange Pi Zero2W + BUILD_DEVICES:=xunlong_orangepi-zero2w + DEPENDS:=+PACKAGE_u-boot-orangepi_zero2w:trusted-firmware-a-sunxi-h616 + UENV:=h616 + ATF:=h616 +endef + define U-Boot/orangepi_zero3 BUILD_SUBTARGET:=cortexa53 NAME:=Xunlong Orange Pi Zero3 @@ -380,6 +402,7 @@ UBOOT_TARGETS := \ bananapi_p2_zero \ Bananapi_M2_Ultra \ Bananapro \ + beelink_x2 \ Cubieboard \ Cubieboard2 \ Cubietruck \ @@ -387,6 +410,7 @@ UBOOT_TARGETS := \ Marsboard_A10 \ Mele_M9 \ OLIMEX_A13_SOM \ + LicheePi_Zero \ Linksprite_pcDuino \ Linksprite_pcDuino3 \ Linksprite_pcDuino3_Nano \ @@ -409,6 +433,7 @@ UBOOT_TARGETS := \ orangepi_2 \ orangepi_pc2 \ orangepi_zero2 \ + orangepi_zero2w \ orangepi_zero3 \ pangolin \ pine64_plus \ diff --git a/lede/package/boot/uboot-sunxi/uEnv-a64.txt b/lede/package/boot/uboot-sunxi/uEnv-a64.txt index a47ebda613..4fe994caa9 100644 --- a/lede/package/boot/uboot-sunxi/uEnv-a64.txt +++ b/lede/package/boot/uboot-sunxi/uEnv-a64.txt @@ -1,7 +1,6 @@ setenv mmc_rootpart 2 part uuid mmc ${mmc_bootdev}:${mmc_rootpart} uuid -setenv loadkernel fatload mmc \$mmc_bootdev \$kernel_addr_r uImage -setenv loaddtb fatload mmc \$mmc_bootdev \$fdt_addr_r dtb +setenv loadkernel fatload mmc \$mmc_bootdev \$kernel_comp_addr_r uImage setenv bootargs console=ttyS0,115200 earlyprintk root=PARTUUID=${uuid} rootwait earlycon=uart,mmio32,0x01c28000 -setenv uenvcmd run loadkernel \&\& run loaddtb \&\& booti \$kernel_addr_r - \$fdt_addr_r +setenv uenvcmd run loadkernel \&\& bootm \$kernel_comp_addr_r run uenvcmd diff --git a/lede/package/boot/uboot-sunxi/uEnv-default.txt b/lede/package/boot/uboot-sunxi/uEnv-default.txt index 36e41c59b1..3651cd83df 100644 --- a/lede/package/boot/uboot-sunxi/uEnv-default.txt +++ b/lede/package/boot/uboot-sunxi/uEnv-default.txt @@ -1,8 +1,6 @@ -setenv fdt_high ffffffff setenv mmc_rootpart 2 part uuid mmc ${mmc_bootdev}:${mmc_rootpart} uuid setenv loadkernel fatload mmc \$mmc_bootdev \$kernel_addr_r uImage -setenv loaddtb fatload mmc \$mmc_bootdev \$fdt_addr_r dtb setenv bootargs console=ttyS0,115200 earlyprintk root=PARTUUID=${uuid} rootwait -setenv uenvcmd run loadkernel \&\& run loaddtb \&\& bootm \$kernel_addr_r - \$fdt_addr_r +setenv uenvcmd run loadkernel \&\& bootm \$kernel_addr_r run uenvcmd diff --git a/lede/package/boot/uboot-sunxi/uEnv-h6.txt b/lede/package/boot/uboot-sunxi/uEnv-h6.txt index 78810ff223..6f0060ec81 100644 --- a/lede/package/boot/uboot-sunxi/uEnv-h6.txt +++ b/lede/package/boot/uboot-sunxi/uEnv-h6.txt @@ -1,7 +1,6 @@ setenv mmc_rootpart 2 part uuid mmc ${mmc_bootdev}:${mmc_rootpart} uuid -setenv loadkernel fatload mmc \$mmc_bootdev \$kernel_addr_r uImage -setenv loaddtb fatload mmc \$mmc_bootdev \$fdt_addr_r dtb +setenv loadkernel fatload mmc \$mmc_bootdev \$kernel_comp_addr_r uImage setenv bootargs console=ttyS0,115200 earlyprintk root=PARTUUID=${uuid} rootwait -setenv uenvcmd run loadkernel \&\& run loaddtb \&\& booti \$kernel_addr_r - \$fdt_addr_r +setenv uenvcmd run loadkernel \&\& bootm \$kernel_comp_addr_r run uenvcmd diff --git a/lede/package/boot/uboot-sunxi/uEnv-h616.txt b/lede/package/boot/uboot-sunxi/uEnv-h616.txt index 78810ff223..6f0060ec81 100644 --- a/lede/package/boot/uboot-sunxi/uEnv-h616.txt +++ b/lede/package/boot/uboot-sunxi/uEnv-h616.txt @@ -1,7 +1,6 @@ setenv mmc_rootpart 2 part uuid mmc ${mmc_bootdev}:${mmc_rootpart} uuid -setenv loadkernel fatload mmc \$mmc_bootdev \$kernel_addr_r uImage -setenv loaddtb fatload mmc \$mmc_bootdev \$fdt_addr_r dtb +setenv loadkernel fatload mmc \$mmc_bootdev \$kernel_comp_addr_r uImage setenv bootargs console=ttyS0,115200 earlyprintk root=PARTUUID=${uuid} rootwait -setenv uenvcmd run loadkernel \&\& run loaddtb \&\& booti \$kernel_addr_r - \$fdt_addr_r +setenv uenvcmd run loadkernel \&\& bootm \$kernel_comp_addr_r run uenvcmd diff --git a/lede/package/boot/uboot-sunxi/uEnv-pangolin.txt b/lede/package/boot/uboot-sunxi/uEnv-pangolin.txt index 9c4fa11220..ecd86c0473 100644 --- a/lede/package/boot/uboot-sunxi/uEnv-pangolin.txt +++ b/lede/package/boot/uboot-sunxi/uEnv-pangolin.txt @@ -1,6 +1,4 @@ -setenv fdt_high ffffffff setenv loadkernel fatload mmc 0 \$kernel_addr_r uImage -setenv loaddtb fatload mmc 0 \$fdt_addr_r dtb setenv bootargs console=ttyS2,115200 earlyprintk root=/dev/mmcblk0p2 rootwait -setenv uenvcmd run loadkernel \&\& run loaddtb \&\& bootm \$kernel_addr_r - \$fdt_addr_r +setenv uenvcmd run loadkernel \&\& run loaddtb \&\& bootm \$kernel_addr_r run uenvcmd diff --git a/lede/target/linux/sunxi/cortexa7/config-6.12 b/lede/target/linux/sunxi/cortexa7/config-6.12 index 105c090890..beab75befa 100644 --- a/lede/target/linux/sunxi/cortexa7/config-6.12 +++ b/lede/target/linux/sunxi/cortexa7/config-6.12 @@ -16,6 +16,7 @@ CONFIG_NET_DSA=y CONFIG_NET_DSA_TAG_BRCM=y CONFIG_NET_DSA_TAG_BRCM_COMMON=y CONFIG_NET_DSA_TAG_BRCM_LEGACY=y +CONFIG_NET_DSA_TAG_BRCM_LEGACY_FCS=y CONFIG_NET_DSA_TAG_BRCM_PREPEND=y CONFIG_NET_SWITCHDEV=y CONFIG_NOP_USB_XCEIV=y diff --git a/lede/target/linux/sunxi/image/Makefile b/lede/target/linux/sunxi/image/Makefile index ee36df598a..8c37bd0fd1 100644 --- a/lede/target/linux/sunxi/image/Makefile +++ b/lede/target/linux/sunxi/image/Makefile @@ -16,7 +16,6 @@ define Build/sunxi-sdcard mkfs.fat $@.boot -C $(FAT32_BLOCKS) mcopy -i $@.boot $(STAGING_DIR_IMAGE)/$(DEVICE_NAME)-boot.scr ::boot.scr - mcopy -i $@.boot $(DTS_DIR)/$(SUNXI_DTS).dtb ::dtb mcopy -i $@.boot $(IMAGE_KERNEL) ::uImage ./gen_sunxi_sdcard_img.sh $@ \ $@.boot \ @@ -34,10 +33,18 @@ define Device/Default KERNEL := kernel-bin | uImage none IMAGES := sdcard.img.gz IMAGE/sdcard.img.gz := sunxi-sdcard | append-metadata | gzip - SUNXI_DTS_DIR :=allwinner/ + SUNXI_DTS_DIR := allwinner/ SUNXI_DTS = $$(SUNXI_DTS_DIR)$$(SOC)-$(lastword $(subst _, ,$(1))) endef +define Device/FitImageLzma + KERNEL = kernel-bin | lzma | fit lzma $$(DTS_DIR)/$$(SUNXI_DTS).dtb +endef + +define Device/FitImageGzip + KERNEL = kernel-bin | gzip | fit gzip $$(DTS_DIR)/$$(SUNXI_DTS).dtb +endef + include $(SUBTARGET).mk $(eval $(call BuildImage)) diff --git a/lede/target/linux/sunxi/image/cortexa53.mk b/lede/target/linux/sunxi/image/cortexa53.mk index 06b409deb7..49702ebadd 100644 --- a/lede/target/linux/sunxi/image/cortexa53.mk +++ b/lede/target/linux/sunxi/image/cortexa53.mk @@ -3,12 +3,12 @@ # Copyright (C) 2013-2016 OpenWrt.org # Copyright (C) 2016 Yousong Zhou -KERNEL_LOADADDR:=0x40008000 +KERNEL_LOADADDR:=0x40080000 define Device/sun50i + $(call Device/FitImageLzma) SUNXI_DTS_DIR := allwinner/ KERNEL_NAME := Image - KERNEL := kernel-bin endef define Device/sun50i-a64 @@ -127,6 +127,13 @@ define Device/xunlong_orangepi-zero2 endef TARGET_DEVICES += xunlong_orangepi-zero2 +define Device/xunlong_orangepi-zero2w + DEVICE_VENDOR := Xunlong + DEVICE_MODEL := Orange Pi Zero 2W + $(Device/sun50i-h618) +endef +TARGET_DEVICES += xunlong_orangepi-zero2w + define Device/xunlong_orangepi-zero3 DEVICE_VENDOR := Xunlong DEVICE_MODEL := Orange Pi Zero 3 diff --git a/lede/target/linux/sunxi/image/cortexa7.mk b/lede/target/linux/sunxi/image/cortexa7.mk index a85b20531d..9b0d12f125 100644 --- a/lede/target/linux/sunxi/image/cortexa7.mk +++ b/lede/target/linux/sunxi/image/cortexa7.mk @@ -6,6 +6,7 @@ KERNEL_LOADADDR:=0x40008000 define Device/cubietech_cubieboard2 + $(call Device/FitImageGzip) DEVICE_VENDOR := Cubietech DEVICE_MODEL := Cubieboard2 DEVICE_PACKAGES:=kmod-ata-sunxi kmod-sun4i-emac kmod-rtc-sunxi @@ -14,6 +15,7 @@ endef TARGET_DEVICES += cubietech_cubieboard2 define Device/cubietech_cubietruck + $(call Device/FitImageGzip) DEVICE_VENDOR := Cubietech DEVICE_MODEL := Cubietruck DEVICE_PACKAGES:=kmod-ata-sunxi kmod-rtc-sunxi kmod-brcmfmac @@ -22,6 +24,7 @@ endef TARGET_DEVICES += cubietech_cubietruck define Device/friendlyarm_nanopi-m1-plus + $(call Device/FitImageGzip) DEVICE_VENDOR := FriendlyARM DEVICE_MODEL := NanoPi M1 Plus DEVICE_PACKAGES:=kmod-leds-gpio kmod-brcmfmac \ @@ -31,6 +34,7 @@ endef TARGET_DEVICES += friendlyarm_nanopi-m1-plus define Device/friendlyarm_nanopi-neo + $(call Device/FitImageGzip) DEVICE_VENDOR := FriendlyARM DEVICE_MODEL := NanoPi NEO SOC := sun8i-h3 @@ -38,6 +42,7 @@ endef TARGET_DEVICES += friendlyarm_nanopi-neo define Device/friendlyarm_nanopi-neo-air + $(call Device/FitImageGzip) DEVICE_VENDOR := FriendlyARM DEVICE_MODEL := NanoPi NEO Air DEVICE_PACKAGES := kmod-leds-gpio kmod-brcmfmac \ @@ -47,6 +52,7 @@ endef TARGET_DEVICES += friendlyarm_nanopi-neo-air define Device/friendlyarm_nanopi-r1 + $(call Device/FitImageGzip) DEVICE_VENDOR := FriendlyARM DEVICE_MODEL := NanoPi R1 DEVICE_PACKAGES := kmod-usb-net-rtl8152 kmod-leds-gpio \ @@ -56,6 +62,7 @@ endef TARGET_DEVICES += friendlyarm_nanopi-r1 define Device/friendlyarm_zeropi + $(call Device/FitImageGzip) DEVICE_VENDOR := FriendlyARM DEVICE_MODEL := ZeroPi DEVICE_PACKAGES := kmod-rtc-sunxi @@ -64,6 +71,7 @@ endef TARGET_DEVICES += friendlyarm_zeropi define Device/lamobo_lamobo-r1 + $(call Device/FitImageGzip) DEVICE_VENDOR := Lamobo DEVICE_MODEL := Lamobo R1 DEVICE_ALT0_VENDOR := Bananapi @@ -76,6 +84,7 @@ endef TARGET_DEVICES += lamobo_lamobo-r1 define Device/lemaker_bananapi + $(call Device/FitImageGzip) DEVICE_VENDOR := LeMaker DEVICE_MODEL := Banana Pi DEVICE_PACKAGES:=kmod-rtc-sunxi kmod-ata-sunxi @@ -84,6 +93,7 @@ endef TARGET_DEVICES += lemaker_bananapi define Device/sinovoip_bananapi-m2-berry + $(call Device/FitImageGzip) DEVICE_VENDOR := Sinovoip DEVICE_MODEL := Banana Pi M2 Berry DEVICE_PACKAGES:=kmod-ata-sunxi kmod-brcmfmac \ @@ -94,6 +104,7 @@ endef TARGET_DEVICES += sinovoip_bananapi-m2-berry define Device/sinovoip_bananapi-m2-ultra + $(call Device/FitImageGzip) DEVICE_VENDOR := Sinovoip DEVICE_MODEL := Banana Pi M2 Ultra DEVICE_PACKAGES:=kmod-ata-sunxi kmod-brcmfmac \ @@ -104,6 +115,7 @@ endef TARGET_DEVICES += sinovoip_bananapi-m2-ultra define Device/lemaker_bananapro + $(call Device/FitImageGzip) DEVICE_VENDOR := LeMaker DEVICE_MODEL := Banana Pro DEVICE_PACKAGES:=kmod-rtc-sunxi kmod-ata-sunxi kmod-brcmfmac \ @@ -112,7 +124,17 @@ define Device/lemaker_bananapro endef TARGET_DEVICES += lemaker_bananapro +define Device/licheepi_licheepi-zero-dock + $(call Device/FitImageGzip) + DEVICE_VENDOR := LicheePi + DEVICE_MODEL := Zero with Dock (V3s) + DEVICE_PACKAGES:=kmod-rtc-sunxi + SOC := sun8i-v3s +endef +TARGET_DEVICES += licheepi_licheepi-zero-dock + define Device/linksprite_pcduino3 + $(call Device/FitImageGzip) DEVICE_VENDOR := LinkSprite DEVICE_MODEL := pcDuino3 DEVICE_PACKAGES:=kmod-sun4i-emac kmod-rtc-sunxi kmod-ata-sunxi kmod-rtl8xxxu \ @@ -122,6 +144,7 @@ endef TARGET_DEVICES += linksprite_pcduino3 define Device/linksprite_pcduino3-nano + $(call Device/FitImageGzip) DEVICE_VENDOR := LinkSprite DEVICE_MODEL := pcDuino3 Nano DEVICE_PACKAGES:=kmod-rtc-sunxi kmod-ata-sunxi @@ -130,6 +153,7 @@ endef TARGET_DEVICES += linksprite_pcduino3-nano define Device/mele_m9 + $(call Device/FitImageGzip) DEVICE_VENDOR := Mele DEVICE_MODEL := M9 DEVICE_PACKAGES:=kmod-sun4i-emac kmod-rtl8192cu @@ -137,7 +161,17 @@ define Device/mele_m9 endef TARGET_DEVICES += mele_m9 +define Device/merrii_hummingbird + $(call Device/FitImageGzip) + DEVICE_VENDOR := Merrii + DEVICE_MODEL := Hummingbird + DEVICE_PACKAGES:=kmod-brcmfmac cypress-firmware-43362-sdio wpad-basic-mbedtls + SOC := sun6i-a31 +endef +TARGET_DEVICES += merrii_hummingbird + define Device/olimex_a20-olinuxino-lime + $(call Device/FitImageGzip) DEVICE_VENDOR := Olimex DEVICE_MODEL := A20-OLinuXino-LIME DEVICE_PACKAGES:=kmod-ata-sunxi kmod-rtc-sunxi @@ -146,6 +180,7 @@ endef TARGET_DEVICES += olimex_a20-olinuxino-lime define Device/olimex_a20-olinuxino-lime2 + $(call Device/FitImageGzip) DEVICE_VENDOR := Olimex DEVICE_MODEL := A20-OLinuXino-LIME2 DEVICE_PACKAGES:=kmod-ata-sunxi kmod-rtc-sunxi kmod-usb-hid @@ -154,6 +189,7 @@ endef TARGET_DEVICES += olimex_a20-olinuxino-lime2 define Device/olimex_a20-olinuxino-lime2-emmc + $(call Device/FitImageGzip) DEVICE_VENDOR := Olimex DEVICE_MODEL := A20-OLinuXino-LIME2 DEVICE_VARIANT := eMMC @@ -163,6 +199,7 @@ endef TARGET_DEVICES += olimex_a20-olinuxino-lime2-emmc define Device/olimex_a20-olinuxino-micro + $(call Device/FitImageGzip) DEVICE_VENDOR := Olimex DEVICE_MODEL := A20-OLinuXino-MICRO DEVICE_PACKAGES:=kmod-ata-sunxi kmod-sun4i-emac kmod-rtc-sunxi @@ -170,7 +207,18 @@ define Device/olimex_a20-olinuxino-micro endef TARGET_DEVICES += olimex_a20-olinuxino-micro +define Device/roofull_beelink-x2 + $(call Device/FitImageGzip) + DEVICE_VENDOR := Roofull + DEVICE_MODEL := Beelink-X2 + DEVICE_PACKAGES:=kmod-leds-gpio kmod-gpio-button-hotplug \ + kmod-brcmfmac cypress-firmware-43430-sdio wpad-basic-mbedtls + SOC := sun8i-h3 +endef +TARGET_DEVICES += roofull_beelink-x2 + define Device/sinovoip_bananapi-m2-plus + $(call Device/FitImageGzip) DEVICE_VENDOR := Sinovoip DEVICE_MODEL := Banana Pi M2+ DEVICE_PACKAGES:=kmod-leds-gpio kmod-brcmfmac \ @@ -180,6 +228,7 @@ endef TARGET_DEVICES += sinovoip_bananapi-m2-plus define Device/sinovoip_bananapi-m3 + $(call Device/FitImageGzip) DEVICE_VENDOR := Sinovoip DEVICE_MODEL := Banana Pi M3 DEVICE_PACKAGES:=kmod-rtc-sunxi kmod-leds-gpio kmod-rtc-ac100 \ @@ -189,6 +238,7 @@ endef TARGET_DEVICES += sinovoip_bananapi-m3 define Device/sinovoip_bananapi-p2-zero + $(call Device/FitImageGzip) DEVICE_VENDOR := Sinovoip DEVICE_MODEL := Banana Pi P2 Zero DEVICE_PACKAGES:=kmod-leds-gpio kmod-brcmfmac \ @@ -198,6 +248,7 @@ endef TARGET_DEVICES += sinovoip_bananapi-p2-zero define Device/xunlong_orangepi-one + $(call Device/FitImageGzip) DEVICE_VENDOR := Xunlong DEVICE_MODEL := Orange Pi One DEVICE_PACKAGES:=kmod-rtc-sunxi @@ -206,6 +257,7 @@ endef TARGET_DEVICES += xunlong_orangepi-one define Device/xunlong_orangepi-pc + $(call Device/FitImageGzip) DEVICE_VENDOR := Xunlong DEVICE_MODEL := Orange Pi PC DEVICE_PACKAGES:=kmod-gpio-button-hotplug @@ -214,6 +266,7 @@ endef TARGET_DEVICES += xunlong_orangepi-pc define Device/xunlong_orangepi-pc-plus + $(call Device/FitImageGzip) DEVICE_VENDOR := Xunlong DEVICE_MODEL := Orange Pi PC Plus DEVICE_PACKAGES:=kmod-gpio-button-hotplug @@ -222,6 +275,7 @@ endef TARGET_DEVICES += xunlong_orangepi-pc-plus define Device/xunlong_orangepi-plus + $(call Device/FitImageGzip) DEVICE_VENDOR := Xunlong DEVICE_MODEL := Orange Pi Plus DEVICE_PACKAGES:=kmod-rtc-sunxi @@ -230,6 +284,7 @@ endef TARGET_DEVICES += xunlong_orangepi-plus define Device/xunlong_orangepi-r1 + $(call Device/FitImageGzip) DEVICE_VENDOR := Xunlong DEVICE_MODEL := Orange Pi R1 DEVICE_PACKAGES:=kmod-usb-net-rtl8152 @@ -238,6 +293,7 @@ endef TARGET_DEVICES += xunlong_orangepi-r1 define Device/xunlong_orangepi-zero + $(call Device/FitImageGzip) DEVICE_VENDOR := Xunlong DEVICE_MODEL := Orange Pi Zero DEVICE_PACKAGES:=kmod-rtc-sunxi @@ -246,6 +302,7 @@ endef TARGET_DEVICES += xunlong_orangepi-zero define Device/xunlong_orangepi-2 + $(call Device/FitImageGzip) DEVICE_VENDOR := Xunlong DEVICE_MODEL := Orange Pi 2 DEVICE_PACKAGES:=kmod-rtc-sunxi diff --git a/lede/target/linux/sunxi/image/cortexa8.mk b/lede/target/linux/sunxi/image/cortexa8.mk index e27db1ee16..f79c8d3513 100644 --- a/lede/target/linux/sunxi/image/cortexa8.mk +++ b/lede/target/linux/sunxi/image/cortexa8.mk @@ -6,6 +6,7 @@ KERNEL_LOADADDR:=0x40008000 define Device/cubietech_a10-cubieboard + $(call Device/FitImageGzip) DEVICE_VENDOR := Cubietech DEVICE_MODEL := Cubieboard DEVICE_PACKAGES:=kmod-ata-sunxi kmod-sun4i-emac kmod-rtc-sunxi @@ -14,6 +15,7 @@ endef TARGET_DEVICES += cubietech_a10-cubieboard define Device/haoyu_a10-marsboard + $(call Device/FitImageGzip) DEVICE_VENDOR := HAOYU Electronics DEVICE_MODEL := MarsBoard A10 DEVICE_PACKAGES:=kmod-ata-core kmod-ata-sunxi kmod-sun4i-emac \ @@ -24,6 +26,7 @@ endef TARGET_DEVICES += haoyu_a10-marsboard define Device/linksprite_a10-pcduino + $(call Device/FitImageGzip) DEVICE_VENDOR := LinkSprite DEVICE_MODEL := pcDuino DEVICE_PACKAGES:=kmod-sun4i-emac kmod-rtc-sunxi kmod-rtl8192cu @@ -32,6 +35,7 @@ endef TARGET_DEVICES += linksprite_a10-pcduino define Device/olimex_a10-olinuxino-lime + $(call Device/FitImageGzip) DEVICE_VENDOR := Olimex DEVICE_MODEL := A10-OLinuXino-LIME DEVICE_PACKAGES:=kmod-ata-sunxi kmod-sun4i-emac kmod-rtc-sunxi @@ -40,6 +44,7 @@ endef TARGET_DEVICES += olimex_a10-olinuxino-lime define Device/olimex_a13-olimex-som + $(call Device/FitImageGzip) DEVICE_VENDOR := Olimex DEVICE_MODEL := A13-SOM DEVICE_PACKAGES:=kmod-rtl8192cu @@ -50,6 +55,7 @@ endef TARGET_DEVICES += olimex_a13-olimex-som define Device/olimex_a13-olinuxino + $(call Device/FitImageGzip) DEVICE_VENDOR := Olimex DEVICE_MODEL := A13-OLinuXino DEVICE_PACKAGES:=kmod-rtl8192cu diff --git a/mihomo/common/net/deadline/conn.go b/mihomo/common/net/deadline/conn.go index 01519e941c..aafad9e4ab 100644 --- a/mihomo/common/net/deadline/conn.go +++ b/mihomo/common/net/deadline/conn.go @@ -149,6 +149,10 @@ func (c *Conn) ReaderReplaceable() bool { return c.disablePipe.Load() || c.deadline.Load().IsZero() } +func (c *Conn) WriterReplaceable() bool { + return true +} + func (c *Conn) Upstream() any { return c.ExtendedConn } diff --git a/mihomo/component/generater/types.go b/mihomo/component/generater/types.go deleted file mode 100644 index 06f59e9468..0000000000 --- a/mihomo/component/generater/types.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copy from https://github.com/WireGuard/wgctrl-go/blob/a9ab2273dd1075ea74b88c76f8757f8b4003fcbf/wgtypes/types.go#L71-L155 - -package generater - -import ( - "crypto/rand" - "encoding/base64" - "fmt" - - "golang.org/x/crypto/curve25519" -) - -// KeyLen is the expected key length for a WireGuard key. -const KeyLen = 32 // wgh.KeyLen - -// A Key is a public, private, or pre-shared secret key. The Key constructor -// functions in this package can be used to create Keys suitable for each of -// these applications. -type Key [KeyLen]byte - -// GenerateKey generates a Key suitable for use as a pre-shared secret key from -// a cryptographically safe source. -// -// The output Key should not be used as a private key; use GeneratePrivateKey -// instead. -func GenerateKey() (Key, error) { - b := make([]byte, KeyLen) - if _, err := rand.Read(b); err != nil { - return Key{}, fmt.Errorf("wgtypes: failed to read random bytes: %v", err) - } - - return NewKey(b) -} - -// GeneratePrivateKey generates a Key suitable for use as a private key from a -// cryptographically safe source. -func GeneratePrivateKey() (Key, error) { - key, err := GenerateKey() - if err != nil { - return Key{}, err - } - - // Modify random bytes using algorithm described at: - // https://cr.yp.to/ecdh.html. - key[0] &= 248 - key[31] &= 127 - key[31] |= 64 - - return key, nil -} - -// NewKey creates a Key from an existing byte slice. The byte slice must be -// exactly 32 bytes in length. -func NewKey(b []byte) (Key, error) { - if len(b) != KeyLen { - return Key{}, fmt.Errorf("wgtypes: incorrect key size: %d", len(b)) - } - - var k Key - copy(k[:], b) - - return k, nil -} - -// ParseKey parses a Key from a base64-encoded string, as produced by the -// Key.String method. -func ParseKey(s string) (Key, error) { - b, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return Key{}, fmt.Errorf("wgtypes: failed to parse base64-encoded key: %v", err) - } - - return NewKey(b) -} - -// PublicKey computes a public key from the private key k. -// -// PublicKey should only be called when k is a private key. -func (k Key) PublicKey() Key { - var ( - pub [KeyLen]byte - priv = [KeyLen]byte(k) - ) - - // ScalarBaseMult uses the correct base value per https://cr.yp.to/ecdh.html, - // so no need to specify it. - curve25519.ScalarBaseMult(&pub, &priv) - - return Key(pub) -} - -// String returns the base64-encoded string representation of a Key. -// -// ParseKey can be used to produce a new Key from this string. -func (k Key) String() string { - return base64.StdEncoding.EncodeToString(k[:]) -} diff --git a/mihomo/component/generater/cmd.go b/mihomo/component/generator/cmd.go similarity index 58% rename from mihomo/component/generater/cmd.go rename to mihomo/component/generator/cmd.go index 2bd5b9cd86..537fba91bb 100644 --- a/mihomo/component/generater/cmd.go +++ b/mihomo/component/generator/cmd.go @@ -1,4 +1,4 @@ -package generater +package generator import ( "encoding/base64" @@ -12,7 +12,7 @@ import ( func Main(args []string) { if len(args) < 1 { - panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768") + panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768/vless-x25519") } switch args[0] { case "uuid": @@ -22,20 +22,19 @@ func Main(args []string) { } fmt.Println(newUUID.String()) case "reality-keypair": - privateKey, err := GeneratePrivateKey() + privateKey, err := GenX25519PrivateKey() if err != nil { panic(err) } - publicKey := privateKey.PublicKey() - fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:])) - fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:])) + fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey.Bytes())) + fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(privateKey.PublicKey().Bytes())) case "wg-keypair": - privateKey, err := GeneratePrivateKey() + privateKey, err := GenX25519PrivateKey() if err != nil { panic(err) } - fmt.Println("PrivateKey: " + privateKey.String()) - fmt.Println("PublicKey: " + privateKey.PublicKey().String()) + fmt.Println("PrivateKey: " + base64.StdEncoding.EncodeToString(privateKey.Bytes())) + fmt.Println("PublicKey: " + base64.StdEncoding.EncodeToString(privateKey.PublicKey().Bytes())) case "ech-keypair": if len(args) < 2 { panic("Using: generate ech-keypair ") @@ -51,11 +50,23 @@ func Main(args []string) { if len(args) > 1 { seed = args[1] } - seedBase64, clientBase64, err := encryption.GenMLKEM768(seed) + seedBase64, clientBase64, hash11Base64, err := encryption.GenMLKEM768(seed) if err != nil { panic(err) } fmt.Println("Seed: " + seedBase64) fmt.Println("Client: " + clientBase64) + fmt.Println("Hash11: " + hash11Base64) + case "vless-x25519": + var privateKey string + if len(args) > 1 { + privateKey = args[1] + } + privateKeyBase64, passwordBase64, err := encryption.GenX25519(privateKey) + if err != nil { + panic(err) + } + fmt.Println("PrivateKey: " + privateKeyBase64) + fmt.Println("Password: " + passwordBase64) } } diff --git a/mihomo/component/generator/x25519.go b/mihomo/component/generator/x25519.go new file mode 100644 index 0000000000..e99fff2bae --- /dev/null +++ b/mihomo/component/generator/x25519.go @@ -0,0 +1,27 @@ +package generator + +import ( + "crypto/ecdh" + "crypto/rand" +) + +const X25519KeySize = 32 + +func GenX25519PrivateKey() (*ecdh.PrivateKey, error) { + var privateKey [X25519KeySize]byte + _, err := rand.Read(privateKey[:]) + if err != nil { + return nil, err + } + + // Avoid generating equivalent X25519 private keys + // https://github.com/XTLS/Xray-core/pull/1747 + // + // Modify random bytes using algorithm described at: + // https://cr.yp.to/ecdh.html. + privateKey[0] &= 248 + privateKey[31] &= 127 + privateKey[31] |= 64 + + return ecdh.X25519().NewPrivateKey(privateKey[:]) +} diff --git a/mihomo/docs/config.yaml b/mihomo/docs/config.yaml index 80af843bf9..0313bee71f 100644 --- a/mihomo/docs/config.yaml +++ b/mihomo/docs/config.yaml @@ -638,8 +638,12 @@ proxies: # socks5 port: 443 uuid: uuid network: tcp - encryption: "8min-vless-mlkem768client-bas64RawURLEncoding" # 复用八分钟后协商新的 sharedKey,需小于服务端的值 - # encryption: "8min-xored-mlkem768client-bas64RawURLEncoding" + # ------------------------- + # vless encryption客户端配置: + # (只使用 1-RTT 模式 / 复用八分钟后协商新的 baseKey,周期需小于服务端的值) + # / 是只能选一个,后面是 base64RawURLEncoding,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号 + # ------------------------- + encryption: "1rtt/8min.native/divide/random.mlkem768Client.(X25519 Password).(ML-KEM-768 Client)" tls: false #可以不开启tls udp: true @@ -1359,8 +1363,12 @@ listeners: flow: xtls-rprx-vision # ws-path: "/" # 如果不为空则开启 websocket 传输层 # grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层 - # decryption: "10min-vless-mlkem768seed-bas64RawURLEncoding" # 同时允许 1-RTT 模式与十分钟复用的 0-RTT 模式, 后面base64字符串可由可由 mihomo generate vless-mlkem768 命令生成 - # decryption: "10min-xored-mlkem768seed-bas64RawURLEncoding" + # ------------------------- + # vless encryption服务端配置: + # (只允许 1-RTT 模式 / 同时允许 1-RTT 模式与十分钟复用的 0-RTT 模式;原生外观 / ECH 式 XOR / 全随机数) + # / 是只能选一个,后面是 base64RawURLEncoding,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号 + # ------------------------- + # decryption: "1rtt/10min.native/divide/random.mlkem768Seed.(X25519 PrivateKey).(ML-KEM-768 Seed)" # 下面两项如果填写则开启 tls(需要同时填写) # certificate: ./server.crt # private-key: ./server.key diff --git a/mihomo/go.mod b/mihomo/go.mod index 5695c7827e..57c10000ba 100644 --- a/mihomo/go.mod +++ b/mihomo/go.mod @@ -31,7 +31,7 @@ require ( github.com/metacubex/sing-shadowsocks2 v0.2.6 github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 github.com/metacubex/sing-tun v0.4.7 - github.com/metacubex/sing-vmess v0.2.4-0.20250819151326-51d195aac5db + github.com/metacubex/sing-vmess v0.2.4-0.20250821024956-97839f31a65d github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f github.com/metacubex/smux v0.0.0-20250503055512-501391591dee github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 diff --git a/mihomo/go.sum b/mihomo/go.sum index 69811ad2ba..d8bfd9a592 100644 --- a/mihomo/go.sum +++ b/mihomo/go.sum @@ -131,8 +131,8 @@ github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MY github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= github.com/metacubex/sing-tun v0.4.7 h1:ZDY/W+1c7PeWWKeKRyUo18fySF/TWjB0i5ui81Ar778= github.com/metacubex/sing-tun v0.4.7/go.mod h1:xHecZRwBnKWe6zG9amAK9cXf91lF6blgjBqm+VvOrmU= -github.com/metacubex/sing-vmess v0.2.4-0.20250819151326-51d195aac5db h1:W7VKxR0r5IR+56Lblx2iyrEaykx0esdQwTQbkSrSaek= -github.com/metacubex/sing-vmess v0.2.4-0.20250819151326-51d195aac5db/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= +github.com/metacubex/sing-vmess v0.2.4-0.20250821024956-97839f31a65d h1:jchYEho5+kTmok4aTMflqJyTRnqVPTOVeC1RFXxuw9A= +github.com/metacubex/sing-vmess v0.2.4-0.20250821024956-97839f31a65d/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo= diff --git a/mihomo/listener/inbound/common_test.go b/mihomo/listener/inbound/common_test.go index 5b838bd303..75b98e0bc4 100644 --- a/mihomo/listener/inbound/common_test.go +++ b/mihomo/listener/inbound/common_test.go @@ -21,7 +21,7 @@ import ( "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/ech" - "github.com/metacubex/mihomo/component/generater" + "github.com/metacubex/mihomo/component/generator" tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" @@ -48,13 +48,12 @@ var echConfigBase64, echKeyPem, _ = ech.GenECHConfig(echPublicSni) func init() { rand.Read(httpData) - privateKey, err := generater.GeneratePrivateKey() + privateKey, err := generator.GenX25519PrivateKey() if err != nil { panic(err) } - publicKey := privateKey.PublicKey() - realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey[:]) - realityPublickey = base64.RawURLEncoding.EncodeToString(publicKey[:]) + realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey.Bytes()) + realityPublickey = base64.RawURLEncoding.EncodeToString(privateKey.PublicKey().Bytes()) } type TestTunnel struct { diff --git a/mihomo/listener/inbound/vless_test.go b/mihomo/listener/inbound/vless_test.go index f3fcd39cb0..4ac9654b3b 100644 --- a/mihomo/listener/inbound/vless_test.go +++ b/mihomo/listener/inbound/vless_test.go @@ -89,39 +89,38 @@ func TestInboundVless_TLS(t *testing.T) { } func TestInboundVless_Encryption(t *testing.T) { - seedBase64, clientBase64, err := encryption.GenMLKEM768("") + seedBase64, clientBase64, _, err := encryption.GenMLKEM768("") if err != nil { t.Fatal(err) return } - t.Run("-vless-", func(t *testing.T) { - inboundOptions := inbound.VlessOption{ - Decryption: "10min-vless-mlkem768seed-" + seedBase64, - } - outboundOptions := outbound.VlessOption{ - Encryption: "8min-vless-mlkem768client-" + clientBase64, - } - testInboundVless(t, inboundOptions, outboundOptions) - t.Run("xtls-rprx-vision", func(t *testing.T) { - outboundOptions := outboundOptions - outboundOptions.Flow = "xtls-rprx-vision" + privateKeyBase64, passwordBase64, err := encryption.GenX25519("") + if err != nil { + t.Fatal(err) + return + } + var modes = []string{ + "native", + "divide", + "random", + } + for i := range modes { + mode := modes[i] + t.Run(mode, func(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Decryption: "10min." + mode + ".mlkem768Seed." + privateKeyBase64 + "." + seedBase64, + } + outboundOptions := outbound.VlessOption{ + Encryption: "8min." + mode + ".mlkem768Client." + passwordBase64 + "." + clientBase64, + } testInboundVless(t, inboundOptions, outboundOptions) + t.Run("xtls-rprx-vision", func(t *testing.T) { + outboundOptions := outboundOptions + outboundOptions.Flow = "xtls-rprx-vision" + testInboundVless(t, inboundOptions, outboundOptions) + }) }) - }) - t.Run("-xored-", func(t *testing.T) { - inboundOptions := inbound.VlessOption{ - Decryption: "10min-xored-mlkem768seed-" + seedBase64, - } - outboundOptions := outbound.VlessOption{ - Encryption: "8min-xored-mlkem768client-" + clientBase64, - } - testInboundVless(t, inboundOptions, outboundOptions) - t.Run("xtls-rprx-vision", func(t *testing.T) { - outboundOptions := outboundOptions - outboundOptions.Flow = "xtls-rprx-vision" - testInboundVless(t, inboundOptions, outboundOptions) - }) - }) + } } func TestInboundVless_Wss1(t *testing.T) { diff --git a/mihomo/main.go b/mihomo/main.go index 3bc3d74f73..6a85e5df79 100644 --- a/mihomo/main.go +++ b/mihomo/main.go @@ -14,7 +14,7 @@ import ( "strings" "syscall" - "github.com/metacubex/mihomo/component/generater" + "github.com/metacubex/mihomo/component/generator" "github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/updater" "github.com/metacubex/mihomo/config" @@ -73,7 +73,7 @@ func main() { } if len(os.Args) > 1 && os.Args[1] == "generate" { - generater.Main(os.Args[2:]) + generator.Main(os.Args[2:]) return } diff --git a/mihomo/transport/vless/encryption/client.go b/mihomo/transport/vless/encryption/client.go index ecf20692ea..dfa4fa63b7 100644 --- a/mihomo/transport/vless/encryption/client.go +++ b/mihomo/transport/vless/encryption/client.go @@ -3,6 +3,7 @@ package encryption import ( "bytes" "crypto/cipher" + "crypto/ecdh" "crypto/rand" "errors" "fmt" @@ -40,7 +41,8 @@ type ClientInstance struct { sync.RWMutex nfsEKey *mlkem.EncapsulationKey768 hash11 [11]byte // no more capacity - xorKey []byte + xorMode uint32 + xorPKey *ecdh.PublicKey minutes time.Duration expire time.Time baseKey []byte @@ -60,22 +62,23 @@ type ClientConn struct { input bytes.Reader // peerCache } -func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) { +func (i *ClientInstance) Init(nfsEKeyBytes, xorPKeyBytes []byte, xorMode, minutes uint32) (err error) { if i.nfsEKey != nil { err = errors.New("already initialized") return } - i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes) - if err != nil { + if i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes); err != nil { return } hash32 := sha3.Sum256(nfsEKeyBytes) copy(i.hash11[:], hash32[:]) - if xor > 0 { - xorKey := sha3.Sum256(nfsEKeyBytes) - i.xorKey = xorKey[:] + if xorMode > 0 { + i.xorMode = xorMode + if i.xorPKey, err = ecdh.X25519().NewPublicKey(xorPKeyBytes); err != nil { + return + } } - i.minutes = minutes + i.minutes = time.Duration(minutes) * time.Minute return } @@ -83,8 +86,8 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*ClientConn, error) { if i.nfsEKey == nil { return nil, errors.New("uninitialized") } - if i.xorKey != nil { - conn = NewXorConn(conn, i.xorKey) + if i.xorMode > 0 { + conn, _ = NewXorConn(conn, i.xorMode, i.xorPKey, nil) } c := &ClientConn{Conn: conn} @@ -145,7 +148,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*ClientConn, error) { } c.baseKey = append(pfsKey, nfsKey...) - VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes) + VLESS, _ := NewAEAD(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes) if !bytes.Equal(VLESS, []byte("VLESS")) { return nil, errors.New("invalid server") } @@ -180,7 +183,7 @@ func (c *ClientConn) Write(b []byte) (int, error) { rand.Read(c.random) copy(data[5+32:], c.random) EncodeHeader(data[5+32+32:], 23, len(b)+16) - c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket) + c.aead = NewAEAD(ClientCipher, c.baseKey, c.random, c.ticket) c.nonce = make([]byte, 12) c.aead.Seal(data[:5+32+32+5], c.nonce, b, data[5+32+32:5+32+32+5]) } else { @@ -188,7 +191,7 @@ func (c *ClientConn) Write(b []byte) (int, error) { EncodeHeader(data, 23, 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]) + c.aead = NewAEAD(ClientCipher, c.baseKey, data[5:], data[:5]) } } IncreaseNonce(c.nonce) @@ -229,7 +232,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { if c.random == nil { return 0, errors.New("empty c.random") } - c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandomHello, c.random) + c.peerAead = NewAEAD(ClientCipher, c.baseKey, peerRandomHello, c.random) c.peerNonce = make([]byte, 12) } if c.input.Len() > 0 { @@ -252,7 +255,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { } var peerAead cipher.AEAD if bytes.Equal(c.peerNonce, MaxNonce) { - peerAead = NewAead(ClientCipher, c.baseKey, peerData, h) + peerAead = NewAEAD(ClientCipher, c.baseKey, peerData, h) } _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, h) if peerAead != nil { diff --git a/mihomo/transport/vless/encryption/common.go b/mihomo/transport/vless/encryption/common.go index a67a616927..d383d22e98 100644 --- a/mihomo/transport/vless/encryption/common.go +++ b/mihomo/transport/vless/encryption/common.go @@ -73,7 +73,7 @@ func ReadAndDiscardPaddings(conn net.Conn) (h []byte, t byte, l int, err error) } } -func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) { +func NewAEAD(c byte, secret, salt, info []byte) (aead cipher.AEAD) { key := make([]byte, 32) hkdf.New(sha3.New256, secret, salt, info).Read(key) if c&1 == 1 { diff --git a/mihomo/transport/vless/encryption/doc.go b/mihomo/transport/vless/encryption/doc.go index 6f0c8f7039..10cd320b2b 100644 --- a/mihomo/transport/vless/encryption/doc.go +++ b/mihomo/transport/vless/encryption/doc.go @@ -14,4 +14,5 @@ // https://github.com/XTLS/Xray-core/commit/d1fb48521271251a8c74bd64fcc2fc8700717a3b // https://github.com/XTLS/Xray-core/commit/49580705f6029648399304b816a2737f991582a8 // https://github.com/XTLS/Xray-core/commit/84835bec7d0d8555d0dd30953ed26a272de814c4 +// https://github.com/XTLS/Xray-core/commit/373558ed7abdbac3de41745cf30ec04c9adde604 package encryption diff --git a/mihomo/transport/vless/encryption/factory.go b/mihomo/transport/vless/encryption/factory.go index 4988dbbfb9..d7e4c7e003 100644 --- a/mihomo/transport/vless/encryption/factory.go +++ b/mihomo/transport/vless/encryption/factory.go @@ -5,7 +5,6 @@ import ( "fmt" "strconv" "strings" - "time" ) // NewClient new client from encryption string @@ -15,7 +14,7 @@ func NewClient(encryption string) (*ClientInstance, error) { 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" { + if s := strings.Split(encryption, "."); len(s) == 5 && s[2] == "mlkem768Client" { var minutes uint32 if s[0] != "1rtt" { t := strings.TrimSuffix(s[0], "min") @@ -28,27 +27,35 @@ func NewClient(encryption string) (*ClientInstance, error) { } minutes = uint32(i) } - var xor uint32 + var xorMode uint32 switch s[1] { - case "vless": - case "xored": - xor = 1 + case "native": + case "divide": + xorMode = 1 + case "random": + xorMode = 2 default: return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) } - b, err := base64.RawURLEncoding.DecodeString(s[3]) + xorPKeyBytes, 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 { + if len(xorPKeyBytes) != X25519PasswordSize { return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) } + nfsEKeyBytes, err := base64.RawURLEncoding.DecodeString(s[4]) + if err != nil { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + if len(nfsEKeyBytes) != MLKEM768ClientLength { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + client := &ClientInstance{} + if err = client.Init(nfsEKeyBytes, xorPKeyBytes, xorMode, minutes); err != nil { + return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) + } + return client, nil } return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) } @@ -60,7 +67,7 @@ func NewServer(decryption string) (*ServerInstance, error) { 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" { + if s := strings.Split(decryption, "."); len(s) == 5 && s[2] == "mlkem768Seed" { var minutes uint32 if s[0] != "1rtt" { t := strings.TrimSuffix(s[0], "min") @@ -73,27 +80,35 @@ func NewServer(decryption string) (*ServerInstance, error) { } minutes = uint32(i) } - var xor uint32 + var xorMode uint32 switch s[1] { - case "vless": - case "xored": - xor = 1 + case "native": + case "divide": + xorMode = 1 + case "random": + xorMode = 2 default: return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) } - b, err := base64.RawURLEncoding.DecodeString(s[3]) + xorSKeyBytes, 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 { + if len(xorSKeyBytes) != X25519PrivateKeySize { return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) } + nfsDKeySeed, err := base64.RawURLEncoding.DecodeString(s[4]) + if err != nil { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + if len(nfsDKeySeed) != MLKEM768SeedLength { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + server := &ServerInstance{} + if err = server.Init(nfsDKeySeed, xorSKeyBytes, xorMode, minutes); err != nil { + return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) + } + return server, nil } return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) } diff --git a/mihomo/transport/vless/encryption/key.go b/mihomo/transport/vless/encryption/key.go index 69b5289579..03ff284ef4 100644 --- a/mihomo/transport/vless/encryption/key.go +++ b/mihomo/transport/vless/encryption/key.go @@ -1,25 +1,29 @@ package encryption import ( + "crypto/ecdh" "crypto/rand" "encoding/base64" "fmt" "github.com/metacubex/utls/mlkem" + "golang.org/x/crypto/sha3" ) const MLKEM768SeedLength = mlkem.SeedSize const MLKEM768ClientLength = mlkem.EncapsulationKeySize768 +const X25519PasswordSize = 32 +const X25519PrivateKeySize = 32 -func GenMLKEM768(seedStr string) (seedBase64, clientBase64 string, err error) { - var seed [64]byte +func GenMLKEM768(seedStr string) (seedBase64, clientBase64, hash11Base64 string, err error) { + var seed [MLKEM768SeedLength]byte if len(seedStr) > 0 { s, _ := base64.RawURLEncoding.DecodeString(seedStr) - if len(s) != 64 { + if len(s) != MLKEM768SeedLength { err = fmt.Errorf("invalid length of ML-KEM-768 seed: %s", seedStr) return } - seed = [64]byte(s) + seed = [MLKEM768SeedLength]byte(s) } else { _, err = rand.Read(seed[:]) if err != nil { @@ -28,8 +32,45 @@ func GenMLKEM768(seedStr string) (seedBase64, clientBase64 string, err error) { } key, _ := mlkem.NewDecapsulationKey768(seed[:]) - pub := key.EncapsulationKey() + client := key.EncapsulationKey().Bytes() + hash32 := sha3.Sum256(client) seedBase64 = base64.RawURLEncoding.EncodeToString(seed[:]) - clientBase64 = base64.RawURLEncoding.EncodeToString(pub.Bytes()) + clientBase64 = base64.RawURLEncoding.EncodeToString(client) + hash11Base64 = base64.RawURLEncoding.EncodeToString(hash32[:11]) + return +} + +func GenX25519(privateKeyStr string) (privateKeyBase64, passwordBase64 string, err error) { + var privateKey [X25519PrivateKeySize]byte + if len(privateKeyStr) > 0 { + s, _ := base64.RawURLEncoding.DecodeString(privateKeyStr) + if len(s) != X25519PrivateKeySize { + err = fmt.Errorf("invalid length of X25519 private key: %s", privateKeyStr) + return + } + privateKey = [X25519PrivateKeySize]byte(s) + } else { + _, err = rand.Read(privateKey[:]) + if err != nil { + return + } + } + + // Avoid generating equivalent X25519 private keys + // https://github.com/XTLS/Xray-core/pull/1747 + // + // Modify random bytes using algorithm described at: + // https://cr.yp.to/ecdh.html. + privateKey[0] &= 248 + privateKey[31] &= 127 + privateKey[31] |= 64 + + key, err := ecdh.X25519().NewPrivateKey(privateKey[:]) + if err != nil { + fmt.Println(err.Error()) + return + } + privateKeyBase64 = base64.RawURLEncoding.EncodeToString(privateKey[:]) + passwordBase64 = base64.RawURLEncoding.EncodeToString(key.PublicKey().Bytes()) return } diff --git a/mihomo/transport/vless/encryption/server.go b/mihomo/transport/vless/encryption/server.go index ea72f2f5cf..baa8432cd0 100644 --- a/mihomo/transport/vless/encryption/server.go +++ b/mihomo/transport/vless/encryption/server.go @@ -3,6 +3,7 @@ package encryption import ( "bytes" "crypto/cipher" + "crypto/ecdh" "crypto/rand" "errors" "fmt" @@ -26,7 +27,8 @@ type ServerInstance struct { sync.RWMutex nfsDKey *mlkem.DecapsulationKey768 hash11 [11]byte // no more capacity - xorKey []byte + xorMode uint32 + xorSKey *ecdh.PrivateKey minutes time.Duration sessions map[[32]byte]*ServerSession closed bool @@ -45,23 +47,24 @@ type ServerConn struct { nonce []byte } -func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Duration) (err error) { +func (i *ServerInstance) Init(nfsDKeySeed, xorSKeyBytes []byte, xorMode, minutes uint32) (err error) { if i.nfsDKey != nil { err = errors.New("already initialized") return } - i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed) - if err != nil { + if i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed); err != nil { return } hash32 := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) copy(i.hash11[:], hash32[:]) - if xor > 0 { - xorKey := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) - i.xorKey = xorKey[:] + if xorMode > 0 { + i.xorMode = xorMode + if i.xorSKey, err = ecdh.X25519().NewPrivateKey(xorSKeyBytes); err != nil { + return + } } if minutes > 0 { - i.minutes = minutes + i.minutes = time.Duration(minutes) * time.Minute i.sessions = make(map[[32]byte]*ServerSession) go func() { for { @@ -95,8 +98,11 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*ServerConn, error) { if i.nfsDKey == nil { return nil, errors.New("uninitialized") } - if i.xorKey != nil { - conn = NewXorConn(conn, i.xorKey) + if i.xorMode > 0 { + var err error + if conn, err = NewXorConn(conn, i.xorMode, nil, i.xorSKey); err != nil { + return nil, err + } } c := &ServerConn{Conn: conn} @@ -167,7 +173,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*ServerConn, error) { pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate() c.baseKey = append(pfsKey, nfsKey...) - c.ticket = append(i.hash11[:], NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, peerClientHello[:12], []byte("VLESS"), pfsEKeyBytes)...) + c.ticket = append(i.hash11[:], NewAEAD(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, peerClientHello[:12], []byte("VLESS"), pfsEKeyBytes)...) paddingLen := randBetween(100, 1000) @@ -221,7 +227,7 @@ func (c *ServerConn) Read(b []byte) (int, error) { } c.peerRandom = peerTicketHello[32:] } - c.peerAead = NewAead(c.cipher, c.baseKey, c.peerRandom, c.ticket) + c.peerAead = NewAEAD(c.cipher, c.baseKey, c.peerRandom, c.ticket) c.peerNonce = make([]byte, 12) } if c.input.Len() > 0 { @@ -244,7 +250,7 @@ func (c *ServerConn) Read(b []byte) (int, error) { } var peerAead cipher.AEAD if bytes.Equal(c.peerNonce, MaxNonce) { - peerAead = NewAead(c.cipher, c.baseKey, peerData, h) + peerAead = NewAEAD(c.cipher, c.baseKey, peerData, h) } _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, h) if peerAead != nil { @@ -280,7 +286,7 @@ func (c *ServerConn) Write(b []byte) (int, error) { EncodeHeader(data, 0, 32) rand.Read(data[5 : 5+32]) EncodeHeader(data[5+32:], 23, len(b)+16) - c.aead = NewAead(c.cipher, c.baseKey, data[5:5+32], c.peerRandom) + c.aead = NewAEAD(c.cipher, c.baseKey, data[5:5+32], c.peerRandom) c.nonce = make([]byte, 12) c.aead.Seal(data[:5+32+5], c.nonce, b, data[5+32:5+32+5]) } else { @@ -288,7 +294,7 @@ func (c *ServerConn) Write(b []byte) (int, error) { EncodeHeader(data, 23, 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]) + c.aead = NewAEAD(c.cipher, c.baseKey, data[5:], data[:5]) } } IncreaseNonce(c.nonce) diff --git a/mihomo/transport/vless/encryption/xor.go b/mihomo/transport/vless/encryption/xor.go index caad12bf07..88bd76982e 100644 --- a/mihomo/transport/vless/encryption/xor.go +++ b/mihomo/transport/vless/encryption/xor.go @@ -3,13 +3,21 @@ package encryption import ( "crypto/aes" "crypto/cipher" + "crypto/ecdh" "crypto/rand" + "errors" "io" "net" + + "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/sha3" ) type XorConn struct { net.Conn + Divide bool + + head []byte key []byte ctr cipher.Stream peerCtr cipher.Stream @@ -25,8 +33,55 @@ type XorConn struct { in_skip int } -func NewXorConn(conn net.Conn, key []byte) *XorConn { - return &XorConn{Conn: conn, key: key} +func NewCTR(key, iv []byte, isServer bool) cipher.Stream { + info := "CLIENT" + if isServer { + info = "SERVER" // avoids attackers sending traffic back to the client, though the encryption layer has its own protection + } + hkdf.New(sha3.New256, key, iv, []byte(info)).Read(key) // avoids using pKey directly if attackers sent the basepoint, or whaterver they like + block, _ := aes.NewCipher(key) + return cipher.NewCTR(block, iv) +} + +func NewXorConn(conn net.Conn, mode uint32, pKey *ecdh.PublicKey, sKey *ecdh.PrivateKey) (*XorConn, error) { + if mode == 0 || (pKey == nil && sKey == nil) || (pKey != nil && sKey != nil) { + return nil, errors.New("invalid parameters") + } + c := &XorConn{ + Conn: conn, + Divide: mode == 1, + isHeader: true, + out_header: make([]byte, 0, 5), // important + in_header: make([]byte, 0, 5), // important + } + if pKey != nil { + c.head = make([]byte, 16+32) + rand.Read(c.head) + eSKey, _ := ecdh.X25519().GenerateKey(rand.Reader) + NewCTR(pKey.Bytes(), c.head[:16], false).XORKeyStream(c.head[16:], eSKey.PublicKey().Bytes()) // make X25519 public key distinguishable from random bytes + c.key, _ = eSKey.ECDH(pKey) + c.ctr = NewCTR(c.key, c.head[:16], false) + } + if sKey != nil { + peerHead := make([]byte, 16+32) + if _, err := io.ReadFull(c.Conn, peerHead); err != nil { + return nil, err + } + NewCTR(sKey.PublicKey().Bytes(), peerHead[:16], false).XORKeyStream(peerHead[16:], peerHead[16:]) // we don't use buggy elligator, because we have PSK :) + ePKey, err := ecdh.X25519().NewPublicKey(peerHead[16:]) + if err != nil { + return nil, err + } + key, err := sKey.ECDH(ePKey) + if err != nil { + return nil, err + } + c.peerCtr = NewCTR(key, peerHead[:16], false) + c.head = make([]byte, 16) + rand.Read(c.head) // make sure the server always replies random bytes even when received replays, though it is not important + c.ctr = NewCTR(key, c.head, true) // the same key links the upload & download, though the encryption layer has its own link + } + return c, nil //chacha20.NewUnauthenticatedCipher() } @@ -35,13 +90,6 @@ func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records return 0, nil } if !c.out_after0 { - var iv []byte - if c.ctr == nil { - block, _ := aes.NewCipher(c.key) - iv = make([]byte, 16) - rand.Read(iv) - c.ctr = cipher.NewCTR(block, iv) - } t, l, _ := DecodeHeader(b) if t == 23 { // single 23 l = 5 @@ -49,20 +97,24 @@ func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records l += 10 if t == 0 { c.out_after0 = true - c.out_header = make([]byte, 0, 5) // important + if c.Divide { + l -= 5 + } } } c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard b - if iv != nil { - b = append(iv, b...) + l = len(b) + if c.head != nil { + b = append(c.head, b...) + c.head = nil } if _, err := c.Conn.Write(b); err != nil { return 0, err } - if iv != nil { - b = b[16:] // for len(b) - } - return len(b), nil + return l, nil + } + if c.Divide { + return c.Conn.Write(b) } for p := b; ; { // for XTLS if len(p) <= c.out_skip { @@ -93,14 +145,12 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes... return 0, nil } if !c.in_after0 || !c.isHeader { - if c.peerCtr == nil { + if c.peerCtr == nil { // for client peerIv := make([]byte, 16) if _, err := io.ReadFull(c.Conn, peerIv); err != nil { return 0, err } - block, _ := aes.NewCipher(c.key) - c.peerCtr = cipher.NewCTR(block, peerIv) - c.isHeader = true + c.peerCtr = NewCTR(c.key, peerIv, true) } if _, err := io.ReadFull(c.Conn, b); err != nil { return 0, err @@ -117,7 +167,6 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes... c.isHeader = false if t == 0 { c.in_after0 = true - c.in_header = make([]byte, 0, 5) // important } } } else { @@ -125,6 +174,9 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes... } return len(b), nil } + if c.Divide { + return c.Conn.Read(b) + } n, err := c.Conn.Read(b) for p := b[:n]; ; { // for XTLS if len(p) <= c.in_skip { @@ -146,3 +198,27 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes... } return n, err } + +func (c *XorConn) WriterReplaceable() bool { + if !c.Divide { // never replaceable + return false + } + if !c.out_after0 { + return false + } + return true +} + +func (c *XorConn) ReaderReplaceable() bool { + if !c.Divide { // never replaceable + return false + } + if !c.in_after0 || !c.isHeader { + return false + } + return true +} + +func (c *XorConn) Upstream() any { + return c.Conn +} diff --git a/openwrt-packages/adguardhome/Makefile b/openwrt-packages/adguardhome/Makefile index de562c370e..eba032d5d6 100644 --- a/openwrt-packages/adguardhome/Makefile +++ b/openwrt-packages/adguardhome/Makefile @@ -6,12 +6,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=adguardhome -PKG_VERSION:=0.107.64 +PKG_VERSION:=0.107.65 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/AdguardTeam/AdGuardHome/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=a6b61d3c102fa47072a7230382d438f3d408f74ffae3aff7d330adad90ed169c +PKG_HASH:=b770007696de88ab4de0008502002a4966a30aca88c9b030c142183a1c2a5830 PKG_BUILD_DIR:=$(BUILD_DIR)/AdGuardHome-$(PKG_VERSION) PKG_LICENSE:=GPL-3.0-only @@ -58,7 +58,7 @@ define Download/adguardhome-frontend URL:=https://github.com/AdguardTeam/AdGuardHome/releases/download/v$(PKG_VERSION)/ URL_FILE:=AdGuardHome_frontend.tar.gz FILE:=$(FRONTEND_FILE) - HASH:=f0c1e4d6e673d4d26d52947cdb220682aa554158d331fa044576d1794b82e325 + HASH:=e838234d8391a23f86b32a26a5c68112a975c7ee83b79646f0597c001f6cab3f endef define Build/Prepare diff --git a/openwrt-packages/ddns-go/Makefile b/openwrt-packages/ddns-go/Makefile index ba3de41f62..5b223dd6ea 100644 --- a/openwrt-packages/ddns-go/Makefile +++ b/openwrt-packages/ddns-go/Makefile @@ -8,12 +8,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ddns-go -PKG_VERSION:=6.12.2 +PKG_VERSION:=6.12.4 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=a3ea99ad74212fc3bd1380e5ad444a1c5fe6bb1bb656624a441551034a17edaa +PKG_HASH:=04f65f7f1ccc18b23dd108f915810146e8b655cd19e7d8ee488cf557222c3fee PKG_LICENSE:=MIT PKG_LICENSE_FILES:=LICENSE diff --git a/openwrt-packages/quickstart/Makefile b/openwrt-packages/quickstart/Makefile index 55cadb112e..d211b3297b 100644 --- a/openwrt-packages/quickstart/Makefile +++ b/openwrt-packages/quickstart/Makefile @@ -10,11 +10,11 @@ include $(TOPDIR)/rules.mk PKG_ARCH_quickstart:=$(ARCH) PKG_NAME:=quickstart -PKG_VERSION:=0.11.2 +PKG_VERSION:=0.11.3 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://github.com/linkease/istore-packages/releases/download/prebuilt/ -PKG_HASH:=b22f430f08fb12739179e4b983133afb84a37be1e702d5b4e9fb30b1e701824d +PKG_HASH:=fee17158398f7867eb20bf187fbfb2d12d46e44d2f9e882376173392a8979d14 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-binary-$(PKG_VERSION) diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua index bb2248bd30..2834edacd8 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua @@ -58,7 +58,8 @@ for k, e in ipairs(api.get_valid_nodes()) do nodes_table[#nodes_table + 1] = { id = e[".name"], remark = e["remark"], - type = e["type"] + type = e["type"], + chain_proxy = e["chain_proxy"] } end if e.protocol == "_balancing" then @@ -696,7 +697,7 @@ o = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Onl o:depends({ [_n("chain_proxy")] = "2" }) for k, v in pairs(nodes_table) do - if v.type == "Xray" and v.id ~= arg[1] then + if v.type == "Xray" and v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then s.fields[_n("preproxy_node")]:value(v.id, v.remark) s.fields[_n("to_node")]:value(v.id, v.remark) end diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua index 2b93635a75..86d9be4f0c 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua @@ -75,7 +75,8 @@ for k, e in ipairs(api.get_valid_nodes()) do nodes_table[#nodes_table + 1] = { id = e[".name"], remark = e["remark"], - type = e["type"] + type = e["type"], + chain_proxy = e["chain_proxy"] } end if e.protocol == "_iface" then @@ -753,7 +754,7 @@ o = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Onl o:depends({ [_n("chain_proxy")] = "2" }) for k, v in pairs(nodes_table) do - if v.type == "sing-box" and v.id ~= arg[1] then + if v.type == "sing-box" and v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then s.fields[_n("preproxy_node")]:value(v.id, v.remark) s.fields[_n("to_node")]:value(v.id, v.remark) end diff --git a/sing-box/clients/android/version.properties b/sing-box/clients/android/version.properties index 0df1610ec6..9d950133d5 100644 --- a/sing-box/clients/android/version.properties +++ b/sing-box/clients/android/version.properties @@ -1,3 +1,3 @@ -VERSION_CODE=554 -VERSION_NAME=1.12.2 +VERSION_CODE=556 +VERSION_NAME=1.12.3 GO_VERSION=go1.25.0 diff --git a/sing-box/dns/transport/local/local_darwin.go b/sing-box/dns/transport/local/local_darwin.go index 5a8b4113e8..6754d94626 100644 --- a/sing-box/dns/transport/local/local_darwin.go +++ b/sing-box/dns/transport/local/local_darwin.go @@ -7,7 +7,6 @@ import ( "errors" "net" - mDNS "github.com/miekg/dns" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" @@ -20,6 +19,8 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" ) func RegisterTransport(registry *dns.TransportRegistry) { diff --git a/sing-box/dns/transport/local/local_resolved_linux.go b/sing-box/dns/transport/local/local_resolved_linux.go index 125a5d32fb..279f9c8eae 100644 --- a/sing-box/dns/transport/local/local_resolved_linux.go +++ b/sing-box/dns/transport/local/local_resolved_linux.go @@ -2,15 +2,19 @@ package local import ( "context" + "errors" "os" "sync" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/service/resolved" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/atomic" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" "github.com/godbus/dbus/v5" @@ -18,11 +22,18 @@ import ( ) type DBusResolvedResolver struct { - logger logger.ContextLogger - interfaceMonitor tun.DefaultInterfaceMonitor - systemBus *dbus.Conn - resoledObject atomic.TypedValue[dbus.BusObject] - closeOnce sync.Once + ctx context.Context + logger logger.ContextLogger + interfaceMonitor tun.DefaultInterfaceMonitor + interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] + systemBus *dbus.Conn + resoledObject atomic.Pointer[ResolvedObject] + closeOnce sync.Once +} + +type ResolvedObject struct { + dbus.BusObject + InterfaceIndex int32 } func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) { @@ -35,6 +46,7 @@ func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (Reso return nil, err } return &DBusResolvedResolver{ + ctx: ctx, logger: logger, interfaceMonitor: interfaceMonitor, systemBus: systemBus, @@ -43,6 +55,7 @@ func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (Reso func (t *DBusResolvedResolver) Start() error { t.updateStatus() + t.interfaceCallback = t.interfaceMonitor.RegisterCallback(t.updateDefaultInterface) err := t.systemBus.BusObject().AddMatchSignal( "org.freedesktop.DBus", "NameOwnerChanged", @@ -58,6 +71,9 @@ func (t *DBusResolvedResolver) Start() error { func (t *DBusResolvedResolver) Close() error { t.closeOnce.Do(func() { + if t.interfaceCallback != nil { + t.interfaceMonitor.UnregisterCallback(t.interfaceCallback) + } if t.systemBus != nil { _ = t.systemBus.Close() } @@ -70,22 +86,23 @@ func (t *DBusResolvedResolver) Object() any { } func (t *DBusResolvedResolver) Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - defaultInterface := t.interfaceMonitor.DefaultInterface() - if defaultInterface == nil { - return nil, E.New("missing default interface") - } question := message.Question[0] - call := object.(*dbus.Object).CallWithContext( + resolvedObject := object.(*ResolvedObject) + call := resolvedObject.CallWithContext( ctx, "org.freedesktop.resolve1.Manager.ResolveRecord", 0, - int32(defaultInterface.Index), + resolvedObject.InterfaceIndex, question.Name, question.Qclass, question.Qtype, uint64(0), ) if call.Err != nil { + var dbusError dbus.Error + if errors.As(call.Err, &dbusError) && dbusError.Name == "org.freedesktop.resolve1.NoNameServers" { + t.updateStatus() + } return nil, E.Cause(call.Err, " resolve record via resolved") } var ( @@ -137,14 +154,76 @@ func (t *DBusResolvedResolver) loopUpdateStatus() { } func (t *DBusResolvedResolver) updateStatus() { - dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1") - err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err + dbusObject, err := t.checkResolved(context.Background()) + oldValue := t.resoledObject.Swap(dbusObject) if err != nil { - if t.resoledObject.Swap(nil) != nil { + var dbusErr dbus.Error + if !errors.As(err, &dbusErr) || dbusErr.Name != "org.freedesktop.DBus.Error.NameHasNoOwnerCould" { + t.logger.Debug(E.Cause(err, "systemd-resolved service unavailable")) + } + if oldValue != nil { t.logger.Debug("systemd-resolved service is gone") } return + } else if oldValue == nil { + t.logger.Debug("using systemd-resolved service as resolver") } - t.resoledObject.Store(dbusObject) - t.logger.Debug("using systemd-resolved service as resolver") +} + +func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*ResolvedObject, error) { + dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1") + err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err + if err != nil { + return nil, err + } + defaultInterface := t.interfaceMonitor.DefaultInterface() + if defaultInterface == nil { + return nil, E.New("missing default interface") + } + call := dbusObject.(*dbus.Object).CallWithContext( + ctx, + "org.freedesktop.resolve1.Manager.GetLink", + 0, + int32(defaultInterface.Index), + ) + if call.Err != nil { + return nil, err + } + var linkPath dbus.ObjectPath + err = call.Store(&linkPath) + if err != nil { + return nil, err + } + linkObject := t.systemBus.Object("org.freedesktop.resolve1", linkPath) + if linkObject == nil { + return nil, E.New("missing link object for default interface") + } + dnsProp, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNS") + if err != nil { + return nil, err + } + var linkDNS []resolved.LinkDNS + err = dnsProp.Store(&linkDNS) + if err != nil { + return nil, err + } + if len(linkDNS) == 0 { + for _, inbound := range service.FromContext[adapter.InboundManager](t.ctx).Inbounds() { + if inbound.Type() == C.TypeTun { + return nil, E.New("No appropriate name servers or networks for name found") + } + } + return &ResolvedObject{ + BusObject: dbusObject, + }, nil + } else { + return &ResolvedObject{ + BusObject: dbusObject, + InterfaceIndex: int32(defaultInterface.Index), + }, nil + } +} + +func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) { + t.updateStatus() } diff --git a/sing-box/docs/changelog.md b/sing-box/docs/changelog.md index 85f792f9c1..1f5ae1504a 100644 --- a/sing-box/docs/changelog.md +++ b/sing-box/docs/changelog.md @@ -6,6 +6,14 @@ icon: material/alert-decagram * Fixes and improvements +#### 1.12.3 + +* Fixes and improvements + +#### 1.13.0-alpha.4 + +* Fixes and improvements + #### 1.12.2 * Fixes and improvements diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js index 8c61d6dbf9..271edffdbd 100644 --- a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js +++ b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js @@ -861,8 +861,8 @@ return view.extend({ so = ss.taboption('direct_list', hm.TextValue, 'direct_list.yaml', null); so.rows = 20; - so.default = 'FQDN:\nIPCIDR:\nIPCIDR6:\n'; - so.placeholder = "FQDN:\n- mask.icloud.com\n- mask-h2.icloud.com\n- mask.apple-dns.net\nIPCIDR:\n- '223.0.0.0/12'\nIPCIDR6:\n- '2400:3200::/32'\n"; + so.default = 'DOMAIN:\nIPCIDR:\nIPCIDR6:\n'; + so.placeholder = "DOMAIN:\n- mask.icloud.com\n- mask-h2.icloud.com\n- mask.apple-dns.net\nIPCIDR:\n- '223.0.0.0/12'\nIPCIDR6:\n- '2400:3200::/32'\n"; so.load = function(section_id) { return L.resolveDefault(hm.readFile('resources', this.option), ''); } @@ -879,8 +879,8 @@ return view.extend({ so = ss.taboption('proxy_list', hm.TextValue, 'proxy_list.yaml', null); so.rows = 20; - so.default = 'FQDN:\nIPCIDR:\nIPCIDR6:\n'; - so.placeholder = "FQDN:\n- www.google.com\nIPCIDR:\n- '91.105.192.0/23'\nIPCIDR6:\n- '2001:67c:4e8::/48'\n"; + so.default = 'DOMAIN:\nIPCIDR:\nIPCIDR6:\n'; + so.placeholder = "DOMAIN:\n- www.google.com\n- '.googlevideo.com'\n- google.com\nIPCIDR:\n- '91.105.192.0/23'\nIPCIDR6:\n- '2001:67c:4e8::/48'\n"; so.load = function(section_id) { return L.resolveDefault(hm.readFile('resources', this.option), ''); } diff --git a/small/luci-app-fchomo/root/etc/init.d/fchomo b/small/luci-app-fchomo/root/etc/init.d/fchomo index 622134cafc..d9524bf5eb 100755 --- a/small/luci-app-fchomo/root/etc/init.d/fchomo +++ b/small/luci-app-fchomo/root/etc/init.d/fchomo @@ -186,7 +186,7 @@ start_service() { local yaml="$5" if [ -n "$yaml" ]; then - yq '.[] |= with(select(. == null); . = []) | .FQDN[]' "$src" | \ + yq '.[] |= with(select(. == null); . = []) | .DOMAIN[]' "$src" | \ sed "s|^|nftset=/|;s|$|/${family}#inet#fchomo#${set_name}|" > "$dst" else sed "s|^|nftset=/|;s|$|/${family}#inet#fchomo#${set_name}|" "$src" > "$dst" diff --git a/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo b/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo index 0ed821956d..15177df473 100755 --- a/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo +++ b/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo @@ -14,7 +14,7 @@ fi # Initialize the default direct list if [ ! -s "/etc/fchomo/resources/direct_list.yaml" ]; then cat <<- EOF > "/etc/fchomo/resources/direct_list.yaml" - FQDN: + DOMAIN: IPCIDR: - '223.0.0.0/12' IPCIDR6: @@ -25,8 +25,10 @@ fi # Initialize the default proxy list if [ ! -s "/etc/fchomo/resources/proxy_list.yaml" ]; then cat <<- EOF > "/etc/fchomo/resources/proxy_list.yaml" - FQDN: + DOMAIN: - www.google.com + - '.googlevideo.com' + - google.com IPCIDR: - '91.105.192.0/23' - '91.108.4.0/22' diff --git a/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo-migration b/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo-migration index 93b8f6cf37..c16063cc3b 100755 --- a/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo-migration +++ b/small/luci-app-fchomo/root/etc/uci-defaults/99_luci-app-fchomo-migration @@ -1,5 +1,8 @@ #!/bin/sh +sed -i 's|^FQDN:$|DOMAIN:|' "/etc/fchomo/resources/direct_list.yaml" +sed -i 's|^FQDN:$|DOMAIN:|' "/etc/fchomo/resources/proxy_list.yaml" + default_proxy=$(uci -q get fchomo.routing.default_proxy) if [ -n "$default_proxy" ]; then uci -q batch <<-EOF >"/dev/null" diff --git a/small/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js b/small/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js index 57d8e3926c..9e5ea4d655 100644 --- a/small/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js +++ b/small/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js @@ -107,6 +107,9 @@ return baseclass.extend({ const profile = await callNikkiProfile({ 'external-controller': null, 'secret': null }); const apiListen = profile['external-controller']; const apiSecret = profile['secret'] ?? ''; + if (!apiListen) { + return Promise.reject('API has not been configured'); + } const apiPort = apiListen.substring(apiListen.lastIndexOf(':') + 1); const url = `http://${window.location.hostname}:${apiPort}${path}`; return request.request(url, { @@ -114,7 +117,7 @@ return baseclass.extend({ headers: { 'Authorization': `Bearer ${apiSecret}` }, query: query, content: body - }) + }); }, openDashboard: async function () { @@ -122,6 +125,9 @@ return baseclass.extend({ const uiName = profile['external-ui-name']; const apiListen = profile['external-controller']; const apiSecret = profile['secret'] ?? ''; + if (!apiListen) { + return Promise.reject('API has not been configured'); + } const apiPort = apiListen.substring(apiListen.lastIndexOf(':') + 1); const params = { host: window.location.hostname, @@ -137,6 +143,7 @@ return baseclass.extend({ url = `http://${window.location.hostname}:${apiPort}/ui/?${query}`; } setTimeout(function () { window.open(url, '_blank') }, 0); + return Promise.resolve(); }, updateDashboard: function () { diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua index bb2248bd30..2834edacd8 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua @@ -58,7 +58,8 @@ for k, e in ipairs(api.get_valid_nodes()) do nodes_table[#nodes_table + 1] = { id = e[".name"], remark = e["remark"], - type = e["type"] + type = e["type"], + chain_proxy = e["chain_proxy"] } end if e.protocol == "_balancing" then @@ -696,7 +697,7 @@ o = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Onl o:depends({ [_n("chain_proxy")] = "2" }) for k, v in pairs(nodes_table) do - if v.type == "Xray" and v.id ~= arg[1] then + if v.type == "Xray" and v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then s.fields[_n("preproxy_node")]:value(v.id, v.remark) s.fields[_n("to_node")]:value(v.id, v.remark) end 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 2b93635a75..86d9be4f0c 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 @@ -75,7 +75,8 @@ for k, e in ipairs(api.get_valid_nodes()) do nodes_table[#nodes_table + 1] = { id = e[".name"], remark = e["remark"], - type = e["type"] + type = e["type"], + chain_proxy = e["chain_proxy"] } end if e.protocol == "_iface" then @@ -753,7 +754,7 @@ o = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Onl o:depends({ [_n("chain_proxy")] = "2" }) for k, v in pairs(nodes_table) do - if v.type == "sing-box" and v.id ~= arg[1] then + if v.type == "sing-box" and v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then s.fields[_n("preproxy_node")]:value(v.id, v.remark) s.fields[_n("to_node")]:value(v.id, v.remark) end diff --git a/small/nikki/files/nikki.conf b/small/nikki/files/nikki.conf index 7e71b121db..2ea1c45536 100644 --- a/small/nikki/files/nikki.conf +++ b/small/nikki/files/nikki.conf @@ -38,11 +38,13 @@ config mixin 'mixin' option 'redir_port' '7891' option 'tproxy_port' '7892' option 'authentication' '1' + option 'tun_enabled' '1' option 'tun_device' 'nikki' option 'tun_stack' 'mixed' option 'tun_dns_hijack' '0' list 'tun_dns_hijacks' 'tcp://any:53' list 'tun_dns_hijacks' 'udp://any:53' + option 'dns_enabled' '1' option 'dns_listen' '[::]:1053' option 'dns_ipv6' '1' option 'dns_mode' 'fake-ip' diff --git a/small/sing-box/Makefile b/small/sing-box/Makefile index d576f10678..3906c20b37 100644 --- a/small/sing-box/Makefile +++ b/small/sing-box/Makefile @@ -6,12 +6,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=sing-box -PKG_VERSION:=1.12.2 +PKG_VERSION:=1.12.3 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=95d902c008ed0b414ab29408dc565310fffe435a15753e02d10ca5c8e6837ce5 +PKG_HASH:=3dce8ee383655908451f7f193714f0c8f90b8fd4baecb8e7e3948d263d766359 PKG_LICENSE:=GPL-3.0-or-later PKG_LICENSE_FILES:=LICENSE diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index d1309b424b..33371c3f79 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -21,13 +21,13 @@ define Download/geoip HASH:=54761d8691a5756fdb08d2cd4d0a9c889dbaab786e1cf758592e09fb00377f53 endef -GEOSITE_VER:=20250820044243 +GEOSITE_VER:=20250821075639 GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER) define Download/geosite URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/ URL_FILE:=dlc.dat FILE:=$(GEOSITE_FILE) - HASH:=08eaf7b6e93ff4422eac2919673ec53f5840643ab318e891981e0f3bd51100f9 + HASH:=b35fcc137b86b4db2ef23919a05c7f90267dc4783186b4e416ff10213cc62ff5 endef GEOSITE_IRAN_VER:=202508180044 diff --git a/v2rayn/.github/workflows/build-linux.yml b/v2rayn/.github/workflows/build-linux.yml index f793a1cbe8..0fb8574fe0 100644 --- a/v2rayn/.github/workflows/build-linux.yml +++ b/v2rayn/.github/workflows/build-linux.yml @@ -98,4 +98,38 @@ jobs: file: ${{ github.workspace }}/v2rayN*.zip tag: ${{ github.event.inputs.release_tag }} file_glob: true - prerelease: true \ No newline at end of file + prerelease: true + + # release RHEL package + - name: Package RPM (RHEL-family) + if: github.event.inputs.release_tag != '' + run: | + chmod 755 package-rhel.sh + # Build for both x86_64 and aarch64 in one go (explicit version passed; no --buildfrom) + ./package-rhel.sh "${{ github.event.inputs.release_tag }}" --arch all + + - name: Collect RPMs into workspace + if: github.event.inputs.release_tag != '' + run: | + mkdir -p "${{ github.workspace }}/dist/rpm" + rsync -av "$HOME/rpmbuild/RPMS/" "${{ github.workspace }}/dist/rpm/" + # Rename to requested filenames + find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.x86_64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-x64.rpm" \; || true + find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.aarch64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true + + - name: Upload RPM artifacts + if: github.event.inputs.release_tag != '' + uses: actions/upload-artifact@v4.6.2 + with: + name: v2rayN-rpm + path: | + ${{ github.workspace }}/dist/rpm/**/*.rpm + + - name: Upload RPMs to release + uses: svenstaro/upload-release-action@v2 + if: github.event.inputs.release_tag != '' + with: + file: ${{ github.workspace }}/dist/rpm/**/*.rpm + tag: ${{ github.event.inputs.release_tag }} + file_glob: true + prerelease: true diff --git a/v2rayn/package-rhel.sh b/v2rayn/package-rhel.sh index e852d17487..0eeee8c781 100644 --- a/v2rayn/package-rhel.sh +++ b/v2rayn/package-rhel.sh @@ -332,6 +332,7 @@ download_xray() { # Download Xray core and install to outdir/xray local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip" mkdir -p "$outdir" + if [[ -n "${XRAY_VER:-}" ]]; then ver="${XRAY_VER}"; fi if [[ -z "$ver" ]]; then ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \ | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true @@ -353,6 +354,7 @@ download_singbox() { # Download sing-box core and install to outdir/sing-box local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin mkdir -p "$outdir" + if [[ -n "${SING_VER:-}" ]]; then ver="${SING_VER}"; fi if [[ -z "$ver" ]]; then ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \ | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true @@ -372,6 +374,22 @@ download_singbox() { install -Dm755 "$bin" "$outdir/sing-box" } +# ---- NEW: download_mihomo (REQUIRED in --netcore mode) ---- +download_mihomo() { + # Download mihomo into outroot/bin/mihomo/mihomo + local outroot="$1" + local url="" + if [[ "$RID_DIR" == "linux-arm64" ]]; then + url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64/bin/mihomo/mihomo" + else + url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64/bin/mihomo/mihomo" + fi + echo "[+] Download mihomo: $url" + mkdir -p "$outroot/bin/mihomo" + curl -fL "$url" -o "$outroot/bin/mihomo/mihomo" + chmod +x "$outroot/bin/mihomo/mihomo" || true +} + # Move geo files to a unified path: outroot/bin/xray/ unify_geo_layout() { local outroot="$1" @@ -451,7 +469,8 @@ download_v2rayn_bundle() { fi rm -f "$outroot/v2rayn.zip" 2>/dev/null || true - find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true + # keep mihomo + # find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true local nested_dir nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)" @@ -561,6 +580,8 @@ build_for_arch() { download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)" fi download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)" + # ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ---- + download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)" fi # Tarball @@ -583,6 +604,7 @@ Release: 1%{?dist} Summary: v2rayN (Avalonia) GUI client for Linux (x86_64/aarch64) License: GPL-3.0-only URL: https://github.com/2dust/v2rayN +BugURL: https://github.com/2dust/v2rayN/issues ExclusiveArch: aarch64 x86_64 Source0: __PKGROOT__.tar.gz @@ -591,10 +613,11 @@ Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrende Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL %description -v2rayN GUI client built with Avalonia. -Installs self-contained publish under /opt/v2rayN and a launcher 'v2rayn'. -Cores (if bundled): /opt/v2rayN/bin/xray, /opt/v2rayN/bin/sing_box. -Geo files for Xray are placed at /opt/v2rayN/bin/xray; launcher will symlink them into user's XDG data dir on first run. +v2rayN Linux for Red Hat Enterprise Linux +Support vless / vmess / Trojan / http / socks / Anytls / Hysteria2 / Shadowsocks / tuic / WireGuard +Support Red Hat Enterprise Linux / Fedora Linux / Rocky Linux / AlmaLinux / CentOS +For more information, Please visit our website +https://github.com/2dust/v2rayN %prep %setup -q -n __PKGROOT__ @@ -645,7 +668,7 @@ cat > %{buildroot}%{_datadir}/applications/v2rayn.desktop << 'EOF' [Desktop Entry] Type=Application Name=v2rayN -Comment=GUI client for Xray / sing-box +Comment=v2rayN for Red Hat Enterprise Linux Exec=v2rayn Icon=v2rayn Terminal=false diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Assets/GlobalResources.axaml b/v2rayn/v2rayN/v2rayN.Desktop/Assets/GlobalResources.axaml index cdfff10883..f58db043f0 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Assets/GlobalResources.axaml +++ b/v2rayn/v2rayN/v2rayN.Desktop/Assets/GlobalResources.axaml @@ -10,6 +10,7 @@ 32 32 + 1000 2 4,0 diff --git a/xray-core/go.mod b/xray-core/go.mod index 701196ac11..e983e336a4 100644 --- a/xray-core/go.mod +++ b/xray-core/go.mod @@ -27,7 +27,7 @@ require ( golang.org/x/sys v0.35.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 google.golang.org/grpc v1.75.0 - google.golang.org/protobuf v1.36.7 + google.golang.org/protobuf v1.36.8 gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 h12.io/socks v1.0.3 lukechampine.com/blake3 v1.4.1 diff --git a/xray-core/go.sum b/xray-core/go.sum index d0e015fcd8..1b4675278c 100644 --- a/xray-core/go.sum +++ b/xray-core/go.sum @@ -145,8 +145,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/yt-dlp/yt_dlp/extractor/youtube/_base.py b/yt-dlp/yt_dlp/extractor/youtube/_base.py index 64980650ec..5ccafff57a 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_base.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_base.py @@ -306,7 +306,8 @@ INNERTUBE_CLIENTS = { 'client': { 'clientName': 'TVHTML5', 'clientVersion': '7.20250312.16.00', - 'userAgent': 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/Version', + # See: https://github.com/youtube/cobalt/blob/main/cobalt/browser/user_agent/user_agent_platform_info.cc#L506 + 'userAgent': 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/25.lts.30.1034943-gold (unlike Gecko), Unknown_TV_Unknown_0/Unknown (Unknown, Unknown)', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 7,