Update On Tue Aug 12 20:40:50 CEST 2025

This commit is contained in:
github-action[bot]
2025-08-12 20:40:51 +02:00
parent 7a3097a4a2
commit 94f030fce0
58 changed files with 1108 additions and 854 deletions

1
.github/update.log vendored
View File

@@ -1087,3 +1087,4 @@ Update On Fri Aug 8 20:38:56 CEST 2025
Update On Sat Aug 9 20:40:00 CEST 2025
Update On Sun Aug 10 20:40:02 CEST 2025
Update On Mon Aug 11 20:43:53 CEST 2025
Update On Tue Aug 12 20:40:42 CEST 2025

View File

@@ -3,14 +3,11 @@ package outbound
import (
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/metacubex/mihomo/common/convert"
N "github.com/metacubex/mihomo/common/net"
@@ -456,41 +453,11 @@ func NewVless(option VlessOption) (*Vless, error) {
option: &option,
}
if s := strings.SplitN(option.Encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
var i int
i, err = strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
var b []byte
b, err = base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
if len(b) == encryption.MLKEM768ClientLength {
v.encryption = &encryption.ClientInstance{}
if err = v.encryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
} else {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
v.encryption, err = encryption.NewClient(option.Encryption)
if err != nil {
return nil, err
}
if v.encryption != nil {
if option.Flow != "" {
return nil, errors.New(`vless "encryption" doesn't support "flow" yet`)
}

View File

@@ -10,11 +10,13 @@ import (
"net"
"net/http"
"net/netip"
"strconv"
"sync"
"testing"
"time"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/pool"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
@@ -30,7 +32,7 @@ import (
)
var httpPath = "/inbound_test"
var httpData = make([]byte, 10240)
var httpData = make([]byte, 2*pool.RelayBufferSize)
var remoteAddr = netip.MustParseAddr("1.2.3.4")
var userUUID = utils.NewUUIDV4().String()
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
@@ -134,14 +136,21 @@ func NewHttpTestTunnel() *TestTunnel {
r := chi.NewRouter()
r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) {
render.Data(w, r, httpData)
query := r.URL.Query()
size, err := strconv.Atoi(query.Get("size"))
if err != nil {
render.Status(r, http.StatusBadRequest)
render.PlainText(w, r, err.Error())
return
}
render.Data(w, r, httpData[:size])
})
h2Server := &http2.Server{}
server := http.Server{Handler: r}
_ = http2.ConfigureServer(&server, h2Server)
go server.Serve(ln)
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string, size int) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s?size=%d", proto, remoteAddr, httpPath, size), nil)
if !assert.NoError(t, err) {
return
}
@@ -200,7 +209,7 @@ func NewHttpTestTunnel() *TestTunnel {
if !assert.NoError(t, err) {
return
}
assert.Equal(t, httpData, data)
assert.Equal(t, httpData[:size], data)
}
tunnel := &TestTunnel{
HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) {
@@ -241,27 +250,30 @@ func NewHttpTestTunnel() *TestTunnel {
},
CloseFn: ln.Close,
DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) {
// Sequential testing for debugging
t.Run("Sequential", func(t *testing.T) {
testFn(t, proxy, "http")
testFn(t, proxy, "https")
testFn(t, proxy, "http", len(httpData))
testFn(t, proxy, "https", len(httpData))
})
// Concurrent testing to detect stress
t.Run("Concurrent", func(t *testing.T) {
wg := sync.WaitGroup{}
const num = 50
for i := 0; i < num; i++ {
num := len(httpData) / 1024
for i := 1; i <= num; i++ {
i := i
wg.Add(1)
go func() {
testFn(t, proxy, "https")
testFn(t, proxy, "https", i*1024)
defer wg.Done()
}()
}
for i := 0; i < num; i++ {
for i := 1; i <= num; i++ {
i := i
wg.Add(1)
go func() {
testFn(t, proxy, "http")
testFn(t, proxy, "http", i*1024)
defer wg.Done()
}()
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert"
)
var singMuxProtocolList = []string{"h2mux", "smux"} // don't test "yamux" because it has some confused bugs
var singMuxProtocolList = []string{"smux"} // don't test "h2mux" and "yamux" because it has some confused bugs
// notCloseProxyAdapter is a proxy adapter that does not close the underlying outbound.ProxyAdapter.
// The outbound.SingMux will close the underlying outbound.ProxyAdapter when it is closed, but we don't want to close it.

View File

@@ -2,15 +2,11 @@ package sing_vless
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net"
"net/http"
"reflect"
"strconv"
"strings"
"time"
"unsafe"
"github.com/metacubex/mihomo/adapter/inbound"
@@ -88,42 +84,11 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
sl = &Listener{config: config, service: service}
if s := strings.SplitN(config.Decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
var i int
i, err = strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
var b []byte
b, err = base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
if len(b) == encryption.MLKEM768SeedLength {
sl.decryption = &encryption.ServerInstance{}
if err = sl.decryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
} else {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
sl.decryption, err = encryption.NewServer(config.Decryption)
if err != nil {
return nil, err
}
if sl.decryption != nil {
defer func() { // decryption must be closed to avoid the goroutine leak
if err != nil {
_ = sl.decryption.Close()

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"io"
"net"
@@ -13,7 +12,6 @@ import (
"time"
"github.com/metacubex/utls/mlkem"
"golang.org/x/crypto/hkdf"
"golang.org/x/sys/cpu"
)
@@ -30,19 +28,20 @@ var (
var ClientCipher byte
func init() {
if !HasAESGCMHardwareSupport {
if HasAESGCMHardwareSupport {
ClientCipher = 1
}
}
type ClientInstance struct {
sync.RWMutex
eKeyNfs *mlkem.EncapsulationKey768
xor uint32
minutes time.Duration
expire time.Time
baseKey []byte
ticket []byte
nfsEKey *mlkem.EncapsulationKey768
nfsEKeyBytes []byte
xor uint32
minutes time.Duration
expire time.Time
baseKey []byte
ticket []byte
}
type ClientConn struct {
@@ -58,19 +57,22 @@ type ClientConn struct {
peerCache []byte
}
func (i *ClientInstance) Init(eKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) {
i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData)
i.xor = xor
func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) {
i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes)
if xor > 0 {
i.nfsEKeyBytes = nfsEKeyBytes
i.xor = xor
}
i.minutes = minutes
return
}
func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
if i.eKeyNfs == nil {
if i.nfsEKey == nil {
return nil, errors.New("uninitialized")
}
if i.xor == 1 {
conn = NewXorConn(conn, i.eKeyNfs.Bytes())
if i.xor > 0 {
conn = NewXorConn(conn, i.nfsEKeyBytes)
}
c := &ClientConn{Conn: conn}
@@ -86,18 +88,19 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
i.RUnlock()
}
nfsKey, encapsulatedNfsKey := i.eKeyNfs.Encapsulate()
seed := make([]byte, 64)
rand.Read(seed)
dKeyPfs, _ := mlkem.NewDecapsulationKey768(seed)
eKeyPfs := dKeyPfs.EncapsulationKey().Bytes()
padding := randBetween(100, 1000)
pfsDKeySeed := make([]byte, 64)
rand.Read(pfsDKeySeed)
pfsDKey, _ := mlkem.NewDecapsulationKey768(pfsDKeySeed)
pfsEKeyBytes := pfsDKey.EncapsulationKey().Bytes()
nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate()
paddingLen := randBetween(100, 1000)
clientHello := make([]byte, 1088+1184+1+5+padding)
copy(clientHello, encapsulatedNfsKey)
copy(clientHello[1088:], eKeyPfs)
clientHello[2272] = ClientCipher
encodeHeader(clientHello[2273:], int(padding))
clientHello := make([]byte, 1+1184+1088+5+paddingLen)
clientHello[0] = ClientCipher
copy(clientHello[1:], pfsEKeyBytes)
copy(clientHello[1185:], encapsulatedNfsKey)
EncodeHeader(clientHello[2273:], int(paddingLen))
rand.Read(clientHello[2278:])
if _, err := c.Conn.Write(clientHello); err != nil {
return nil, err
@@ -111,17 +114,15 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
encapsulatedPfsKey := peerServerHello[:1088]
c.ticket = peerServerHello[1088:]
pfsKey, err := dKeyPfs.Decapsulate(encapsulatedPfsKey)
pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey)
if err != nil {
return nil, err
}
c.baseKey = append(nfsKey, pfsKey...)
c.baseKey = append(pfsKey, nfsKey...)
authKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfs).Read(authKey)
nonce := make([]byte, 12)
VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce, c.ticket, encapsulatedPfsKey)
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message
nonce := [12]byte{ClientCipher}
VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, nonce[:], c.ticket, pfsEKeyBytes)
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more messages
return nil, errors.New("invalid server")
}
@@ -141,32 +142,39 @@ func (c *ClientConn) Write(b []byte) (int, error) {
return 0, nil
}
var data []byte
if c.aead == nil {
c.random = make([]byte, 32)
rand.Read(c.random)
key := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, c.random, c.ticket).Read(key)
c.aead = newAead(ClientCipher, key)
c.nonce = make([]byte, 12)
data = make([]byte, 21+32+5+len(b)+16)
copy(data, c.ticket)
copy(data[21:], c.random)
encodeHeader(data[53:], len(b)+16)
c.aead.Seal(data[:58], c.nonce, b, data[53:58])
} else {
data = make([]byte, 5+len(b)+16)
encodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
}
increaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
for n := 0; n < len(b); {
b := b[n:]
if len(b) > 8192 {
b = b[:8192] // for avoiding another copy() in server's Read()
}
n += len(b)
if c.aead == nil {
c.random = make([]byte, 32)
rand.Read(c.random)
c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket)
c.nonce = make([]byte, 12)
data = make([]byte, 21+32+5+len(b)+16)
copy(data, c.ticket)
copy(data[21:], c.random)
EncodeHeader(data[53:], len(b)+16)
c.aead.Seal(data[:58], c.nonce, b, data[53:58])
} else {
data = make([]byte, 5+len(b)+16)
EncodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
if bytes.Equal(c.nonce, MaxNonce) {
c.aead = NewAead(ClientCipher, c.baseKey, data[5:], data[:5])
}
}
IncreaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
}
return len(b), nil
}
func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
func (c *ClientConn) Read(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
@@ -177,11 +185,11 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerPadding, _ := decodeHeader(peerHeader)
if peerPadding == 0 {
peerPaddingLen, _ := DecodeHeader(peerHeader)
if peerPaddingLen == 0 {
break
}
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil {
return 0, err
}
}
@@ -196,11 +204,9 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
return 0, err
}
if c.random == nil {
return 0, errors.New("can not Read() first")
return 0, errors.New("empty c.random")
}
peerKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey)
c.peerAead = newAead(ClientCipher, peerKey)
c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandom, c.random)
c.peerNonce = make([]byte, 12)
}
if len(c.peerCache) != 0 {
@@ -211,7 +217,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerLength, err := decodeHeader(peerHeader) // 17~17000
peerLength, err := DecodeHeader(peerHeader) // 17~17000
if err != nil {
if c.instance != nil {
c.instance.Lock()
@@ -228,10 +234,17 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
}
dst := peerData[:peerLength-16]
if len(dst) <= len(b) {
dst = b[:len(dst)] // max=8192 is recommended for peer
dst = b[:len(dst)] // avoids another copy()
}
var peerAead cipher.AEAD
if bytes.Equal(c.peerNonce, MaxNonce) {
peerAead = NewAead(ClientCipher, c.baseKey, peerData, peerHeader)
}
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
increaseNonce(c.peerNonce)
if peerAead != nil {
c.peerAead = peerAead
}
IncreaseNonce(c.peerNonce)
if err != nil {
return 0, err
}

View File

@@ -1,17 +1,22 @@
package encryption
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"math/big"
"strconv"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
)
func encodeHeader(b []byte, l int) {
var MaxNonce = bytes.Repeat([]byte{255}, 12)
func EncodeHeader(b []byte, l int) {
b[0] = 23
b[1] = 3
b[2] = 3
@@ -19,10 +24,10 @@ func encodeHeader(b []byte, l int) {
b[4] = byte(l)
}
func decodeHeader(b []byte) (int, error) {
func DecodeHeader(b []byte) (int, error) {
if b[0] == 23 && b[1] == 3 && b[2] == 3 {
l := int(b[3])<<8 | int(b[4])
if l < 17 || l > 17000 { // TODO
if l < 17 || l > 17000 { // TODO: TLSv1.3 max length
return 0, errors.New("invalid length in record's header: " + strconv.Itoa(l))
}
return l, nil
@@ -30,29 +35,24 @@ func decodeHeader(b []byte) (int, error) {
return 0, errors.New("invalid record's header")
}
func newAead(c byte, k []byte) cipher.AEAD {
switch c {
case 0:
if block, err := aes.NewCipher(k); err == nil {
aead, _ := cipher.NewGCM(block)
return aead
}
case 1:
aead, _ := chacha20poly1305.New(k)
return aead
func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) {
key := make([]byte, 32)
hkdf.New(sha256.New, secret, salt, info).Read(key)
if c&1 == 1 {
block, _ := aes.NewCipher(key)
aead, _ = cipher.NewGCM(block)
} else {
aead, _ = chacha20poly1305.New(key)
}
return nil
return
}
func increaseNonce(nonce []byte) {
func IncreaseNonce(nonce []byte) {
for i := 0; i < 12; i++ {
nonce[11-i]++
if nonce[11-i] != 0 {
break
}
if i == 11 {
// TODO
}
}
}

View File

@@ -2,4 +2,7 @@
// https://github.com/XTLS/Xray-core/commit/f61c14e9c63dc41a8a09135db3aea337974f3f37
// https://github.com/XTLS/Xray-core/commit/3e19bf9233bdd9bafc073a71c65b737cc1ffba5e
// https://github.com/XTLS/Xray-core/commit/7ffb555fc8ec51bd1e3e60f26f1d6957984dba80
// https://github.com/XTLS/Xray-core/commit/ec1cc35188c1a5f38a2ff75e88b5d043ffdc59da
// https://github.com/XTLS/Xray-core/commit/5c611420487a92f931faefc01d4bf03869f477f6
// https://github.com/XTLS/Xray-core/commit/23d7aad461d232bc5bed52dd6aaa731ecd88ad35
package encryption

View File

@@ -0,0 +1,99 @@
package encryption
import (
"encoding/base64"
"fmt"
"strconv"
"strings"
"time"
)
// NewClient new client from encryption string
// maybe return a nil *ClientInstance without any error, that means don't need to encrypt
func NewClient(encryption string) (*ClientInstance, error) {
switch encryption {
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
return nil, nil
}
if s := strings.SplitN(encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
i, err := strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
b, err := base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
if len(b) == MLKEM768ClientLength {
client := &ClientInstance{}
if err = client.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
return client, nil
} else {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
}
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
// NewServer new server from decryption string
// maybe return a nil *ServerInstance without any error, that means don't need to decrypt
func NewServer(decryption string) (*ServerInstance, error) {
switch decryption {
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
return nil, nil
}
if s := strings.SplitN(decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
i, err := strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
b, err := base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
if len(b) == MLKEM768SeedLength {
server := &ServerInstance{}
if err = server.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
return server, nil
} else {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
}
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"io"
"net"
@@ -12,7 +11,6 @@ import (
"time"
"github.com/metacubex/utls/mlkem"
"golang.org/x/crypto/hkdf"
)
type ServerSession struct {
@@ -24,11 +22,12 @@ type ServerSession struct {
type ServerInstance struct {
sync.RWMutex
dKeyNfs *mlkem.DecapsulationKey768
xor uint32
minutes time.Duration
sessions map[[21]byte]*ServerSession
stop bool
nfsDKey *mlkem.DecapsulationKey768
nfsEKeyBytes []byte
xor uint32
minutes time.Duration
sessions map[[21]byte]*ServerSession
closed bool
}
type ServerConn struct {
@@ -44,9 +43,12 @@ type ServerConn struct {
nonce []byte
}
func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) {
i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData)
i.xor = xor
func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Duration) (err error) {
i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed)
if xor > 0 {
i.nfsEKeyBytes = i.nfsDKey.EncapsulationKey().Bytes()
i.xor = xor
}
if minutes > 0 {
i.minutes = minutes
i.sessions = make(map[[21]byte]*ServerSession)
@@ -55,12 +57,13 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat
time.Sleep(time.Minute)
now := time.Now()
i.Lock()
if i.stop {
if i.closed {
i.Unlock()
return
}
for index, session := range i.sessions {
for ticket, session := range i.sessions {
if now.After(session.expire) {
delete(i.sessions, index)
delete(i.sessions, ticket)
}
}
i.Unlock()
@@ -72,17 +75,17 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat
func (i *ServerInstance) Close() (err error) {
i.Lock()
defer i.Unlock()
i.stop = true
i.closed = true
i.Unlock()
return
}
func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
if i.dKeyNfs == nil {
if i.nfsDKey == nil {
return nil, errors.New("uninitialized")
}
if i.xor == 1 {
conn = NewXorConn(conn, i.dKeyNfs.EncapsulationKey().Bytes())
if i.xor > 0 {
conn = NewXorConn(conn, i.nfsEKeyBytes)
}
c := &ServerConn{Conn: conn}
@@ -109,50 +112,49 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return nil, err
}
if l, _ := decodeHeader(peerHeader); l != 0 {
c.Conn.Write(make([]byte, randBetween(100, 1000))) // make client do new handshake
if l, _ := DecodeHeader(peerHeader); l != 0 {
noise := make([]byte, randBetween(100, 1000))
rand.Read(noise)
c.Conn.Write(noise) // make client do new handshake
return nil, errors.New("invalid ticket")
}
peerClientHello := make([]byte, 1088+1184+1)
peerClientHello := make([]byte, 1+1184+1088)
copy(peerClientHello, peerTicketHello)
copy(peerClientHello[53:], peerHeader)
if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil {
return nil, err
}
encapsulatedNfsKey := peerClientHello[:1088]
eKeyPfsData := peerClientHello[1088:2272]
c.cipher = peerClientHello[2272]
if c.cipher != 0 && c.cipher != 1 {
return nil, errors.New("invalid cipher")
}
c.cipher = peerClientHello[0]
pfsEKeyBytes := peerClientHello[1:1185]
encapsulatedNfsKey := peerClientHello[1185:2273]
nfsKey, err := i.dKeyNfs.Decapsulate(encapsulatedNfsKey)
pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes)
if err != nil {
return nil, err
}
eKeyPfs, err := mlkem.NewEncapsulationKey768(eKeyPfsData)
nfsKey, err := i.nfsDKey.Decapsulate(encapsulatedNfsKey)
if err != nil {
return nil, err
}
pfsKey, encapsulatedPfsKey := eKeyPfs.Encapsulate()
c.baseKey = append(nfsKey, pfsKey...)
pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate()
c.baseKey = append(pfsKey, nfsKey...)
authKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfsData).Read(authKey)
nonce := make([]byte, 12)
c.ticket = newAead(c.cipher, authKey).Seal(nil, nonce, []byte("VLESS"), encapsulatedPfsKey)
nonce := [12]byte{c.cipher}
c.ticket = NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, nonce[:], []byte("VLESS"), pfsEKeyBytes)
padding := randBetween(100, 1000)
paddingLen := randBetween(100, 1000)
serverHello := make([]byte, 1088+21+5+padding)
serverHello := make([]byte, 1088+21+5+paddingLen)
copy(serverHello, encapsulatedPfsKey)
copy(serverHello[1088:], c.ticket)
encodeHeader(serverHello[1109:], int(padding))
EncodeHeader(serverHello[1109:], int(paddingLen))
rand.Read(serverHello[1114:])
if _, err := c.Conn.Write(serverHello); err != nil {
return nil, err
}
// we can send more padding if needed
if i.minutes > 0 {
i.Lock()
@@ -178,11 +180,11 @@ func (c *ServerConn) Read(b []byte) (int, error) {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerPadding, _ := decodeHeader(peerHeader)
if peerPadding == 0 {
peerPaddingLen, _ := DecodeHeader(peerHeader)
if peerPaddingLen == 0 {
break
}
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil {
return 0, err
}
}
@@ -199,9 +201,7 @@ func (c *ServerConn) Read(b []byte) (int, error) {
return 0, err
}
}
peerKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, c.peerRandom, c.ticket).Read(peerKey)
c.peerAead = newAead(c.cipher, peerKey)
c.peerAead = NewAead(c.cipher, c.baseKey, c.peerRandom, c.ticket)
c.peerNonce = make([]byte, 12)
}
if len(c.peerCache) != 0 {
@@ -212,7 +212,7 @@ func (c *ServerConn) Read(b []byte) (int, error) {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerLength, err := decodeHeader(peerHeader) // 17~17000
peerLength, err := DecodeHeader(peerHeader) // 17~17000
if err != nil {
return 0, err
}
@@ -222,10 +222,17 @@ func (c *ServerConn) Read(b []byte) (int, error) {
}
dst := peerData[:peerLength-16]
if len(dst) <= len(b) {
dst = b[:len(dst)] // max=8192 is recommended for peer
dst = b[:len(dst)] // avoids another copy()
}
var peerAead cipher.AEAD
if bytes.Equal(c.peerNonce, MaxNonce) {
peerAead = NewAead(c.cipher, c.baseKey, peerData, peerHeader)
}
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
increaseNonce(c.peerNonce)
if peerAead != nil {
c.peerAead = peerAead
}
IncreaseNonce(c.peerNonce)
if err != nil {
return 0, errors.New("error")
}
@@ -236,31 +243,39 @@ func (c *ServerConn) Read(b []byte) (int, error) {
return len(dst), nil
}
func (c *ServerConn) Write(b []byte) (int, error) { // after first Read()
func (c *ServerConn) Write(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
var data []byte
if c.aead == nil {
if c.peerRandom == nil {
return 0, errors.New("can not Write() first")
for n := 0; n < len(b); {
b := b[n:]
if len(b) > 8192 {
b = b[:8192] // for avoiding another copy() in client's Read()
}
n += len(b)
if c.aead == nil {
if c.peerRandom == nil {
return 0, errors.New("empty c.peerRandom")
}
data = make([]byte, 32+5+len(b)+16)
rand.Read(data[:32])
c.aead = NewAead(c.cipher, c.baseKey, data[:32], c.peerRandom)
c.nonce = make([]byte, 12)
EncodeHeader(data[32:], len(b)+16)
c.aead.Seal(data[:37], c.nonce, b, data[32:37])
} else {
data = make([]byte, 5+len(b)+16)
EncodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
if bytes.Equal(c.nonce, MaxNonce) {
c.aead = NewAead(c.cipher, c.baseKey, data[5:], data[:5])
}
}
IncreaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
data = make([]byte, 32+5+len(b)+16)
rand.Read(data[:32])
key := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, data[:32], c.peerRandom).Read(key)
c.aead = newAead(c.cipher, key)
c.nonce = make([]byte, 12)
encodeHeader(data[32:], len(b)+16)
c.aead.Seal(data[:37], c.nonce, b, data[32:37])
} else {
data = make([]byte, 5+len(b)+16)
encodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
}
increaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
return len(b), nil
}

View File

@@ -38,7 +38,7 @@ func (c *XorConn) Write(b []byte) (int, error) {
return 0, err
}
if iv != nil {
b = b[16:]
b = b[16:] // for len(b)
}
return len(b), nil
}

View File

@@ -22,6 +22,6 @@
},
"devDependencies": {
"@types/lodash-es": "4.17.12",
"@types/react": "19.1.9"
"@types/react": "19.1.10"
}
}

View File

@@ -59,9 +59,9 @@
"@iconify/json": "2.2.371",
"@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.84.2",
"@tanstack/react-router": "1.131.3",
"@tanstack/react-router-devtools": "1.131.3",
"@tanstack/router-plugin": "1.131.3",
"@tanstack/react-router": "1.131.5",
"@tanstack/react-router-devtools": "1.131.5",
"@tanstack/router-plugin": "1.131.5",
"@tauri-apps/plugin-clipboard-manager": "2.3.0",
"@tauri-apps/plugin-dialog": "2.3.0",
"@tauri-apps/plugin-fs": "2.4.0",
@@ -70,7 +70,7 @@
"@tauri-apps/plugin-process": "2.3.0",
"@tauri-apps/plugin-shell": "2.3.0",
"@tauri-apps/plugin-updater": "2.9.0",
"@types/react": "19.1.9",
"@types/react": "19.1.10",
"@types/react-dom": "19.1.7",
"@types/validator": "13.15.2",
"@vitejs/plugin-legacy": "7.2.1",

View File

@@ -19,7 +19,7 @@
"@radix-ui/react-scroll-area": "1.2.9",
"@tauri-apps/api": "2.6.0",
"@types/d3": "7.4.3",
"@types/react": "19.1.9",
"@types/react": "19.1.10",
"@vitejs/plugin-react": "4.7.0",
"ahooks": "3.9.0",
"d3": "7.9.0",

View File

@@ -2,10 +2,10 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.12",
"mihomo_alpha": "alpha-3a0d267",
"mihomo_alpha": "alpha-eca5a27",
"clash_rs": "v0.8.2",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.8.2-alpha+sha.ec9134e"
"clash_rs_alpha": "0.8.2-alpha+sha.df9f591"
},
"arch_template": {
"mihomo": {
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2025-08-10T22:21:15.754Z"
"updated_at": "2025-08-11T22:21:19.847Z"
}

View File

@@ -204,8 +204,8 @@ importers:
specifier: 4.17.12
version: 4.17.12
'@types/react':
specifier: 19.1.9
version: 19.1.9
specifier: 19.1.10
version: 19.1.10
frontend/nyanpasu:
dependencies:
@@ -220,7 +220,7 @@ importers:
version: 3.2.2(react@19.1.1)
'@emotion/styled':
specifier: 11.14.1
version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@juggle/resize-observer':
specifier: 3.4.0
version: 3.4.0
@@ -229,16 +229,16 @@ importers:
version: 0.3.0
'@mui/icons-material':
specifier: 7.3.1
version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/lab':
specifier: 7.0.0-beta.16
version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material':
specifier: 7.3.1
version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/x-date-pickers':
specifier: 8.10.0
version: 8.10.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
version: 8.10.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@nyanpasu/interface':
specifier: workspace:^
version: link:../interface
@@ -250,7 +250,7 @@ importers:
version: 4.1.11
'@tanstack/router-zod-adapter':
specifier: 1.81.5
version: 1.81.5(@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(zod@4.0.17)
version: 1.81.5(@tanstack/react-router@1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(zod@4.0.17)
'@tauri-apps/api':
specifier: 2.6.0
version: 2.6.0
@@ -280,19 +280,19 @@ importers:
version: 25.3.4(typescript@5.9.2)
jotai:
specifier: 2.13.1
version: 2.13.1(@babel/core@7.28.0)(@babel/template@7.27.2)(@types/react@19.1.9)(react@19.1.1)
version: 2.13.1(@babel/core@7.28.0)(@babel/template@7.27.2)(@types/react@19.1.10)(react@19.1.1)
json-schema:
specifier: 0.4.0
version: 0.4.0
material-react-table:
specifier: npm:@greenhat616/material-react-table@4.0.0
version: '@greenhat616/material-react-table@4.0.0(ee367b75520587f2d436c002311f838b)'
version: '@greenhat616/material-react-table@4.0.0(098023d060c5c1c6a96afb3bdaa051b0)'
monaco-editor:
specifier: 0.52.2
version: 0.52.2
mui-color-input:
specifier: 7.0.0
version: 7.0.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
version: 7.0.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react:
specifier: 19.1.1
version: 19.1.1
@@ -307,13 +307,13 @@ importers:
version: 1.6.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-hook-form-mui:
specifier: 7.6.2
version: 7.6.2(dc790e3d871a3fe7e1d22dc6e321e397)
version: 7.6.2(aea177882beb7723aeada5c99e57089b)
react-i18next:
specifier: 15.6.1
version: 15.6.1(i18next@25.3.4(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2)
react-markdown:
specifier: 10.1.0
version: 10.1.0(@types/react@19.1.9)(react@19.1.1)
version: 10.1.0(@types/react@19.1.10)(react@19.1.1)
react-split-grid:
specifier: 1.0.4
version: 1.0.4(react@19.1.1)
@@ -341,7 +341,7 @@ importers:
version: 11.13.5
'@emotion/react':
specifier: 11.14.0
version: 11.14.0(@types/react@19.1.9)(react@19.1.1)
version: 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@iconify/json':
specifier: 2.2.371
version: 2.2.371
@@ -352,14 +352,14 @@ importers:
specifier: 5.84.2
version: 5.84.2(react@19.1.1)
'@tanstack/react-router':
specifier: 1.131.3
version: 1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
specifier: 1.131.5
version: 1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/react-router-devtools':
specifier: 1.131.3
version: 1.131.3(@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.3)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)(tiny-invariant@1.3.3)
specifier: 1.131.5
version: 1.131.5(@tanstack/react-router@1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.5)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)(tiny-invariant@1.3.3)
'@tanstack/router-plugin':
specifier: 1.131.3
version: 1.131.3(@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.1(@types/node@22.17.1)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))
specifier: 1.131.5
version: 1.131.5(@tanstack/react-router@1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.1(@types/node@22.17.1)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))
'@tauri-apps/plugin-clipboard-manager':
specifier: 2.3.0
version: 2.3.0
@@ -385,11 +385,11 @@ importers:
specifier: 2.9.0
version: 2.9.0
'@types/react':
specifier: 19.1.9
version: 19.1.9
specifier: 19.1.10
version: 19.1.10
'@types/react-dom':
specifier: 19.1.7
version: 19.1.7(@types/react@19.1.9)
version: 19.1.7(@types/react@19.1.10)
'@types/validator':
specifier: 13.15.2
version: 13.15.2
@@ -464,19 +464,19 @@ importers:
version: 0.3.0
'@mui/icons-material':
specifier: 7.3.1
version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/lab':
specifier: 7.0.0-beta.16
version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material':
specifier: 7.3.1
version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-portal':
specifier: 1.1.9
version: 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
version: 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-scroll-area':
specifier: 1.2.9
version: 1.2.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
version: 1.2.9(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tauri-apps/api':
specifier: 2.6.0
version: 2.6.0
@@ -484,8 +484,8 @@ importers:
specifier: 7.4.3
version: 7.4.3
'@types/react':
specifier: 19.1.9
version: 19.1.9
specifier: 19.1.10
version: 19.1.10
'@vitejs/plugin-react':
specifier: 4.7.0
version: 4.7.0(vite@7.1.1(@types/node@22.17.1)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))
@@ -525,7 +525,7 @@ importers:
devDependencies:
'@emotion/react':
specifier: 11.14.0
version: 11.14.0(@types/react@19.1.9)(react@19.1.1)
version: 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@types/d3-interpolate-path':
specifier: 2.0.3
version: 2.0.3
@@ -2939,16 +2939,16 @@ packages:
peerDependencies:
react: ^18 || ^19
'@tanstack/react-router-devtools@1.131.3':
resolution: {integrity: sha512-opouR8dbBrDnkHdiyTVPb3rTfSRxWN+ZGi1YuEXNGUXgirvnpQUTWNP6kzDv1at29DxymwwfRRYD602MjZOOlA==}
'@tanstack/react-router-devtools@1.131.5':
resolution: {integrity: sha512-3LaEbWDYGnzw4J8DM7KX32qRslGrSt67Cg7uoAYReBfDlLpWVRnjpyG2fef7nHDqn5HaAuV0IbY1n6Duwp5IyQ==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/react-router': ^1.131.3
'@tanstack/react-router': ^1.131.5
react: '>=18.0.0 || >=19.0.0'
react-dom: '>=18.0.0 || >=19.0.0'
'@tanstack/react-router@1.131.3':
resolution: {integrity: sha512-1wxsStYJai0/ssOQeO+8mh5VmMmJWIZOtIEEqDxIhQX4dHyurKl6khl34+qLtDvWFsj6zgiMwjID3GQj5SMz1w==}
'@tanstack/react-router@1.131.5':
resolution: {integrity: sha512-71suJGuCmrHN9PLLRUDB3CGnW5RNcEEfgfX616TOpKamHs977H8P4/75BgWPRWcLHCga/1kkA6c7bddCwZ35Fw==}
engines: {node: '>=12'}
peerDependencies:
react: '>=18.0.0 || >=19.0.0'
@@ -2973,15 +2973,15 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@tanstack/router-core@1.131.3':
resolution: {integrity: sha512-8sby4n2pgRJ7qZrFvuS6v21oBDHCRoFM8p+/HAIra2d32cD5wS5k9DeNltWFUAJFXdsdwpdGVA0iXSAc0WfIHw==}
'@tanstack/router-core@1.131.5':
resolution: {integrity: sha512-XVfZdnKNQbWfkQ6G7I9ml2wHp98Wy7wgTboP5SfrJHfOE+kPeHeZRJqF/pp5oqLZ2feBJqsDDKNWo9323L7sWQ==}
engines: {node: '>=12'}
'@tanstack/router-devtools-core@1.131.3':
resolution: {integrity: sha512-GQHVCE0ywJ0ajz9K52RLAhbDSuziD3MVRYYp5S5w2i31Mb9xkzITJe/CZ7WIgOuY+XQm18P8drbJnxdHTNOcWQ==}
'@tanstack/router-devtools-core@1.131.5':
resolution: {integrity: sha512-kH3cZz7UfnVQW9vMZJ/CAx15pu+iGkn10N4rRKBVWCEJZFPX3GZvYEwkeALHASsV0Io7yUvKDcWfPsc+UowyzQ==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/router-core': ^1.131.3
'@tanstack/router-core': ^1.131.5
csstype: ^3.0.10
solid-js: '>=1.9.5'
tiny-invariant: ^1.3.3
@@ -2989,16 +2989,16 @@ packages:
csstype:
optional: true
'@tanstack/router-generator@1.131.3':
resolution: {integrity: sha512-sTsi9lSxBCpAExQWyh3k2X40biiRHjIRISxVvko5sPG/+NKYFjGaQwgXQ/1jiDSTqe8i7/b/2l94ZJPTa6VPxQ==}
'@tanstack/router-generator@1.131.5':
resolution: {integrity: sha512-5+/zyp/R9WN8tHNVIEYQZpRMzcsOrNH06HoPnPMiLiB9T4WsOLFJCcHdyso9ofGQq+hoxB4M9SUBXVBbJVWbSw==}
engines: {node: '>=12'}
'@tanstack/router-plugin@1.131.3':
resolution: {integrity: sha512-PLCjxTTHBf5H9TqH+jTNvgSnnCZhvrLj61C5XAtONA7NUv+Lh4xJ/u0nn83ZYb7uFM4rMg1JmpU5mG8iNbGHZw==}
'@tanstack/router-plugin@1.131.5':
resolution: {integrity: sha512-Px+GSijNv1cbSm74+U+kEbuSFsspL+/BakzytAJFdh2O4342G32tha1cMFMlzXbDd9SW1FaLWgu3VqNHjGIpOg==}
engines: {node: '>=12'}
peerDependencies:
'@rsbuild/core': '>=1.0.2'
'@tanstack/react-router': ^1.131.3
'@tanstack/react-router': ^1.131.5
vite: '>=5.0.0 || >=6.0.0'
vite-plugin-solid: ^2.11.2
webpack: '>=5.92.0'
@@ -3360,8 +3360,8 @@ packages:
peerDependencies:
'@types/react': '*'
'@types/react@19.1.9':
resolution: {integrity: sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==}
'@types/react@19.1.10':
resolution: {integrity: sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==}
'@types/responselike@1.0.3':
resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
@@ -9728,7 +9728,7 @@ snapshots:
'@emotion/memoize@0.9.0': {}
'@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1)':
'@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.26.0
'@emotion/babel-plugin': 11.13.5
@@ -9740,7 +9740,7 @@ snapshots:
hoist-non-react-statics: 3.3.2
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
transitivePeerDependencies:
- supports-color
@@ -9754,18 +9754,18 @@ snapshots:
'@emotion/sheet@1.4.0': {}
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)':
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.27.6
'@emotion/babel-plugin': 11.13.5
'@emotion/is-prop-valid': 1.3.0
'@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1)
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/serialize': 1.3.3
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1)
'@emotion/utils': 1.4.2
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
transitivePeerDependencies:
- supports-color
@@ -9911,13 +9911,13 @@ snapshots:
'@fastify/busboy@2.1.1': {}
'@greenhat616/material-react-table@4.0.0(ee367b75520587f2d436c002311f838b)':
'@greenhat616/material-react-table@4.0.0(098023d060c5c1c6a96afb3bdaa051b0)':
dependencies:
'@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/x-date-pickers': 8.10.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/x-date-pickers': 8.10.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/match-sorter-utils': 8.19.4
'@tanstack/react-table': 8.21.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/react-virtual': 3.13.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -10059,39 +10059,39 @@ snapshots:
'@mui/core-downloads-tracker@7.3.1': {}
'@mui/icons-material@7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)':
'@mui/icons-material@7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@mui/lab@7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
'@mui/lab@7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@mui/types': 7.4.5(@types/react@19.1.9)
'@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1)
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/types': 7.4.5(@types/react@19.1.10)
'@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1)
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@types/react': 19.1.9
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@types/react': 19.1.10
'@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
'@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@mui/core-downloads-tracker': 7.3.1
'@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@mui/types': 7.4.5(@types/react@19.1.9)
'@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1)
'@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/types': 7.4.5(@types/react@19.1.10)
'@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1)
'@popperjs/core': 2.11.8
'@types/react-transition-group': 4.4.12(@types/react@19.1.9)
'@types/react-transition-group': 4.4.12(@types/react@19.1.10)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
@@ -10100,20 +10100,20 @@ snapshots:
react-is: 19.1.1
react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@types/react': 19.1.9
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@types/react': 19.1.10
'@mui/private-theming@7.3.1(@types/react@19.1.9)(react@19.1.1)':
'@mui/private-theming@7.3.1(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1)
'@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1)
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@mui/styled-engine@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(react@19.1.1)':
'@mui/styled-engine@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@emotion/cache': 11.14.0
@@ -10123,67 +10123,67 @@ snapshots:
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)':
'@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@mui/private-theming': 7.3.1(@types/react@19.1.9)(react@19.1.1)
'@mui/styled-engine': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(react@19.1.1)
'@mui/types': 7.4.5(@types/react@19.1.9)
'@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1)
'@mui/private-theming': 7.3.1(@types/react@19.1.10)(react@19.1.1)
'@mui/styled-engine': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(react@19.1.1)
'@mui/types': 7.4.5(@types/react@19.1.10)
'@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@types/react': 19.1.9
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@types/react': 19.1.10
'@mui/types@7.4.5(@types/react@19.1.9)':
'@mui/types@7.4.5(@types/react@19.1.10)':
dependencies:
'@babel/runtime': 7.28.2
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@mui/utils@7.3.1(@types/react@19.1.9)(react@19.1.1)':
'@mui/utils@7.3.1(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@mui/types': 7.4.5(@types/react@19.1.9)
'@mui/types': 7.4.5(@types/react@19.1.10)
'@types/prop-types': 15.7.15
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-is: 19.1.1
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@mui/x-date-pickers@8.10.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
'@mui/x-date-pickers@8.10.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1)
'@mui/x-internals': 8.10.0(@types/react@19.1.9)(react@19.1.1)
'@types/react-transition-group': 4.4.12(@types/react@19.1.9)
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1)
'@mui/x-internals': 8.10.0(@types/react@19.1.10)(react@19.1.1)
'@types/react-transition-group': 4.4.12(@types/react@19.1.10)
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
dayjs: 1.11.13
transitivePeerDependencies:
- '@types/react'
'@mui/x-internals@8.10.0(@types/react@19.1.9)(react@19.1.1)':
'@mui/x-internals@8.10.0(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@mui/utils': 7.3.1(@types/react@19.1.9)(react@19.1.1)
'@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1)
react: 19.1.1
reselect: 5.1.1
use-sync-external-store: 1.5.0(react@19.1.1)
@@ -10578,88 +10578,88 @@ snapshots:
'@radix-ui/primitive@1.1.2': {}
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.9)(react@19.1.1)':
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.10)(react@19.1.1)':
dependencies:
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@radix-ui/react-context@1.1.2(@types/react@19.1.9)(react@19.1.1)':
'@radix-ui/react-context@1.1.2(@types/react@19.1.10)(react@19.1.1)':
dependencies:
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@radix-ui/react-direction@1.1.1(@types/react@19.1.9)(react@19.1.1)':
'@radix-ui/react-direction@1.1.1(@types/react@19.1.10)(react@19.1.1)':
dependencies:
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
'@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.10)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
'@types/react': 19.1.9
'@types/react-dom': 19.1.7(@types/react@19.1.9)
'@types/react': 19.1.10
'@types/react-dom': 19.1.7(@types/react@19.1.10)
'@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
'@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.10)(react@19.1.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.10)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
'@types/react': 19.1.9
'@types/react-dom': 19.1.7(@types/react@19.1.9)
'@types/react': 19.1.10
'@types/react-dom': 19.1.7(@types/react@19.1.10)
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@radix-ui/react-slot': 1.2.3(@types/react@19.1.9)(react@19.1.1)
'@radix-ui/react-slot': 1.2.3(@types/react@19.1.10)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
'@types/react': 19.1.9
'@types/react-dom': 19.1.7(@types/react@19.1.9)
'@types/react': 19.1.10
'@types/react-dom': 19.1.7(@types/react@19.1.10)
'@radix-ui/react-scroll-area@1.2.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
'@radix-ui/react-scroll-area@1.2.9(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@radix-ui/number': 1.1.1
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1)
'@radix-ui/react-context': 1.1.2(@types/react@19.1.9)(react@19.1.1)
'@radix-ui/react-direction': 1.1.1(@types/react@19.1.9)(react@19.1.1)
'@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.9)(react@19.1.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.10)(react@19.1.1)
'@radix-ui/react-context': 1.1.2(@types/react@19.1.10)(react@19.1.1)
'@radix-ui/react-direction': 1.1.1(@types/react@19.1.10)(react@19.1.1)
'@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.10)(react@19.1.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.10)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
'@types/react': 19.1.9
'@types/react-dom': 19.1.7(@types/react@19.1.9)
'@types/react': 19.1.10
'@types/react-dom': 19.1.7(@types/react@19.1.10)
'@radix-ui/react-slot@1.2.3(@types/react@19.1.9)(react@19.1.1)':
'@radix-ui/react-slot@1.2.3(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.9)(react@19.1.1)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.10)(react@19.1.1)
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.9)(react@19.1.1)':
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.10)(react@19.1.1)':
dependencies:
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.9)(react@19.1.1)':
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.10)(react@19.1.1)':
dependencies:
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@rolldown/pluginutils@1.0.0-beta.27': {}
@@ -11040,10 +11040,10 @@ snapshots:
'@tanstack/query-core': 5.83.1
react: 19.1.1
'@tanstack/react-router-devtools@1.131.3(@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.3)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)(tiny-invariant@1.3.3)':
'@tanstack/react-router-devtools@1.131.5(@tanstack/react-router@1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.5)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)(tiny-invariant@1.3.3)':
dependencies:
'@tanstack/react-router': 1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/router-devtools-core': 1.131.3(@tanstack/router-core@1.131.3)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)
'@tanstack/react-router': 1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/router-devtools-core': 1.131.5(@tanstack/router-core@1.131.5)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
transitivePeerDependencies:
@@ -11052,11 +11052,11 @@ snapshots:
- solid-js
- tiny-invariant
'@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
'@tanstack/react-router@1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@tanstack/history': 1.131.2
'@tanstack/react-store': 0.7.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/router-core': 1.131.3
'@tanstack/router-core': 1.131.5
isbot: 5.1.28
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
@@ -11082,7 +11082,7 @@ snapshots:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
'@tanstack/router-core@1.131.3':
'@tanstack/router-core@1.131.5':
dependencies:
'@tanstack/history': 1.131.2
'@tanstack/store': 0.7.0
@@ -11092,9 +11092,9 @@ snapshots:
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
'@tanstack/router-devtools-core@1.131.3(@tanstack/router-core@1.131.3)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)':
'@tanstack/router-devtools-core@1.131.5(@tanstack/router-core@1.131.5)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)':
dependencies:
'@tanstack/router-core': 1.131.3
'@tanstack/router-core': 1.131.5
clsx: 2.1.1
goober: 2.1.16(csstype@3.1.3)
solid-js: 1.9.5
@@ -11102,9 +11102,9 @@ snapshots:
optionalDependencies:
csstype: 3.1.3
'@tanstack/router-generator@1.131.3':
'@tanstack/router-generator@1.131.5':
dependencies:
'@tanstack/router-core': 1.131.3
'@tanstack/router-core': 1.131.5
'@tanstack/router-utils': 1.131.2
'@tanstack/virtual-file-routes': 1.131.2
prettier: 3.6.2
@@ -11115,7 +11115,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@tanstack/router-plugin@1.131.3(@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.1(@types/node@22.17.1)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))':
'@tanstack/router-plugin@1.131.5(@tanstack/react-router@1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.1(@types/node@22.17.1)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))':
dependencies:
'@babel/core': 7.28.0
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0)
@@ -11123,8 +11123,8 @@ snapshots:
'@babel/template': 7.27.2
'@babel/traverse': 7.28.0
'@babel/types': 7.28.1
'@tanstack/router-core': 1.131.3
'@tanstack/router-generator': 1.131.3
'@tanstack/router-core': 1.131.5
'@tanstack/router-generator': 1.131.5
'@tanstack/router-utils': 1.131.2
'@tanstack/virtual-file-routes': 1.131.2
babel-dead-code-elimination: 1.0.10
@@ -11132,7 +11132,7 @@ snapshots:
unplugin: 2.3.5
zod: 3.25.76
optionalDependencies:
'@tanstack/react-router': 1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/react-router': 1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
vite: 7.1.1(@types/node@22.17.1)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
@@ -11148,9 +11148,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(zod@4.0.17)':
'@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(zod@4.0.17)':
dependencies:
'@tanstack/react-router': 1.131.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/react-router': 1.131.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
zod: 4.0.17
'@tanstack/store@0.7.0': {}
@@ -11496,15 +11496,15 @@ snapshots:
'@types/prop-types@15.7.15': {}
'@types/react-dom@19.1.7(@types/react@19.1.9)':
'@types/react-dom@19.1.7(@types/react@19.1.10)':
dependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@types/react-transition-group@4.4.12(@types/react@19.1.9)':
'@types/react-transition-group@4.4.12(@types/react@19.1.10)':
dependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
'@types/react@19.1.9':
'@types/react@19.1.10':
dependencies:
csstype: 3.1.3
@@ -14464,11 +14464,11 @@ snapshots:
jju@1.4.0: {}
jotai@2.13.1(@babel/core@7.28.0)(@babel/template@7.27.2)(@types/react@19.1.9)(react@19.1.1):
jotai@2.13.1(@babel/core@7.28.0)(@babel/template@7.27.2)(@types/react@19.1.10)(react@19.1.1):
optionalDependencies:
'@babel/core': 7.28.0
'@babel/template': 7.27.2
'@types/react': 19.1.9
'@types/react': 19.1.10
react: 19.1.1
js-cookie@2.2.1: {}
@@ -15109,16 +15109,16 @@ snapshots:
muggle-string@0.4.1: {}
mui-color-input@7.0.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
mui-color-input@7.0.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
'@ctrl/tinycolor': 4.1.0
'@emotion/react': 11.14.0(@types/react@19.1.9)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
'@types/react': 19.1.9
'@types/react': 19.1.10
nano-css@5.6.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
@@ -15704,14 +15704,14 @@ snapshots:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
react-hook-form-mui@7.6.2(dc790e3d871a3fe7e1d22dc6e321e397):
react-hook-form-mui@7.6.2(aea177882beb7723aeada5c99e57089b):
dependencies:
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
react-hook-form: 7.52.1(react@19.1.1)
optionalDependencies:
'@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.9)(react@19.1.1)
'@mui/x-date-pickers': 8.10.0(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(react@19.1.1))(@types/react@19.1.9)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/x-date-pickers': 8.10.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-hook-form@7.52.1(react@19.1.1):
dependencies:
@@ -15731,11 +15731,11 @@ snapshots:
react-is@19.1.1: {}
react-markdown@10.1.0(@types/react@19.1.9)(react@19.1.1):
react-markdown@10.1.0(@types/react@19.1.10)(react@19.1.1):
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.3
'@types/react': 19.1.9
'@types/react': 19.1.10
devlop: 1.1.0
hast-util-to-jsx-runtime: 2.3.0
html-url-attributes: 3.0.0

View File

@@ -51,6 +51,7 @@ image:
make image EXTRA_IMAGE_NAME="<string>" # Add this to the output image filename (sanitized)
make image DISABLED_SERVICES="<svc1> [<svc2> [<svc3> ..]]" # Which services in /etc/init.d/ should be disabled
make image ADD_LOCAL_KEY=1 # store locally generated signing key in built images
make image ROOTFS_PARTSIZE="<size>" # override the default rootfs partition size in MegaBytes
manifest:
List "all" packages which get installed into the image.
@@ -261,7 +262,8 @@ image:
$(if $(FILES),USER_FILES="$(FILES)") \
$(if $(PACKAGES),USER_PACKAGES="$(PACKAGES)") \
$(if $(BIN_DIR),BIN_DIR="$(BIN_DIR)") \
$(if $(DISABLED_SERVICES),DISABLED_SERVICES="$(DISABLED_SERVICES)"))
$(if $(DISABLED_SERVICES),DISABLED_SERVICES="$(DISABLED_SERVICES)") \
$(if $(ROOTFS_PARTSIZE),CONFIG_TARGET_ROOTFS_PARTSIZE="$(ROOTFS_PARTSIZE)"))
manifest: FORCE
$(MAKE) -s _check_profile

View File

@@ -17,12 +17,10 @@ package testtool
import (
"bytes"
"errors"
"io"
mrand "math/rand"
"net"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/enfein/mieru/v3/pkg/common"
@@ -32,12 +30,17 @@ import (
func BufPipe() (net.Conn, net.Conn) {
var buf1, buf2 bytes.Buffer
var lock1, lock2 sync.Mutex
cond1 := sync.NewCond(&lock1) // endpoint 1 has data to read
cond2 := sync.NewCond(&lock2) // endpoint 2 has data to read
ep1 := &ioEndpoint{
direction: forward,
buf1: &buf1,
buf2: &buf2,
lock1: &lock1,
lock2: &lock2,
cond1: cond1,
cond2: cond2,
}
ep2 := &ioEndpoint{
direction: backward,
@@ -45,7 +48,11 @@ func BufPipe() (net.Conn, net.Conn) {
buf2: &buf2,
lock1: &lock1,
lock2: &lock2,
cond1: cond1,
cond2: cond2,
}
ep1.peer = ep2
ep2.peer = ep1
return ep1, ep2
}
@@ -62,56 +69,83 @@ type ioEndpoint struct {
buf2 *bytes.Buffer // backward writes to here
lock1 *sync.Mutex // lock of buf1
lock2 *sync.Mutex // lock of buf2
closed bool
cond1 *sync.Cond
cond2 *sync.Cond
closed atomic.Bool
peer *ioEndpoint
}
var _ net.Conn = &ioEndpoint{}
func (e *ioEndpoint) Read(b []byte) (n int, err error) {
if e.closed {
if e.closed.Load() {
return 0, io.EOF
}
var buffer *bytes.Buffer
var lock *sync.Mutex
var cond *sync.Cond
if e.direction == forward {
e.lock2.Lock()
n, err = e.buf2.Read(b)
e.lock2.Unlock()
buffer = e.buf2
lock = e.lock2
cond = e.cond2
} else {
e.lock1.Lock()
n, err = e.buf1.Read(b)
e.lock1.Unlock()
buffer = e.buf1
lock = e.lock1
cond = e.cond1
}
if errors.Is(err, io.EOF) {
// io.ReadFull() with partial result will not fail.
err = nil
action := mrand.Intn(2)
if action == 0 {
// Allow the writer to catch up.
runtime.Gosched()
} else {
time.Sleep(time.Microsecond)
lock.Lock()
defer lock.Unlock()
for buffer.Len() == 0 {
if e.closed.Load() || e.peer.closed.Load() {
return 0, io.EOF
}
cond.Wait()
}
return
return buffer.Read(b)
}
func (e *ioEndpoint) Write(b []byte) (n int, err error) {
if e.closed {
if e.closed.Load() {
return 0, io.ErrClosedPipe
}
var buffer *bytes.Buffer
var lock *sync.Mutex
var cond *sync.Cond
if e.direction == forward {
e.lock1.Lock()
n, err = e.buf1.Write(b)
e.lock1.Unlock()
buffer = e.buf1
lock = e.lock1
cond = e.cond1
} else {
e.lock2.Lock()
n, err = e.buf2.Write(b)
e.lock2.Unlock()
buffer = e.buf2
lock = e.lock2
cond = e.cond2
}
lock.Lock()
defer lock.Unlock()
if e.peer.closed.Load() {
return 0, io.ErrClosedPipe
}
n, err = buffer.Write(b)
cond.Signal()
return
}
func (e *ioEndpoint) Close() error {
e.closed = true
if e.closed.Swap(true) {
return nil
}
e.cond1.Broadcast()
e.cond2.Broadcast()
return nil
}

View File

@@ -3,14 +3,11 @@ package outbound
import (
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/metacubex/mihomo/common/convert"
N "github.com/metacubex/mihomo/common/net"
@@ -456,41 +453,11 @@ func NewVless(option VlessOption) (*Vless, error) {
option: &option,
}
if s := strings.SplitN(option.Encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
var i int
i, err = strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
var b []byte
b, err = base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
if len(b) == encryption.MLKEM768ClientLength {
v.encryption = &encryption.ClientInstance{}
if err = v.encryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
} else {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
v.encryption, err = encryption.NewClient(option.Encryption)
if err != nil {
return nil, err
}
if v.encryption != nil {
if option.Flow != "" {
return nil, errors.New(`vless "encryption" doesn't support "flow" yet`)
}

View File

@@ -10,11 +10,13 @@ import (
"net"
"net/http"
"net/netip"
"strconv"
"sync"
"testing"
"time"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/pool"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
@@ -30,7 +32,7 @@ import (
)
var httpPath = "/inbound_test"
var httpData = make([]byte, 10240)
var httpData = make([]byte, 2*pool.RelayBufferSize)
var remoteAddr = netip.MustParseAddr("1.2.3.4")
var userUUID = utils.NewUUIDV4().String()
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
@@ -134,14 +136,21 @@ func NewHttpTestTunnel() *TestTunnel {
r := chi.NewRouter()
r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) {
render.Data(w, r, httpData)
query := r.URL.Query()
size, err := strconv.Atoi(query.Get("size"))
if err != nil {
render.Status(r, http.StatusBadRequest)
render.PlainText(w, r, err.Error())
return
}
render.Data(w, r, httpData[:size])
})
h2Server := &http2.Server{}
server := http.Server{Handler: r}
_ = http2.ConfigureServer(&server, h2Server)
go server.Serve(ln)
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string, size int) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s?size=%d", proto, remoteAddr, httpPath, size), nil)
if !assert.NoError(t, err) {
return
}
@@ -200,7 +209,7 @@ func NewHttpTestTunnel() *TestTunnel {
if !assert.NoError(t, err) {
return
}
assert.Equal(t, httpData, data)
assert.Equal(t, httpData[:size], data)
}
tunnel := &TestTunnel{
HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) {
@@ -241,27 +250,30 @@ func NewHttpTestTunnel() *TestTunnel {
},
CloseFn: ln.Close,
DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) {
// Sequential testing for debugging
t.Run("Sequential", func(t *testing.T) {
testFn(t, proxy, "http")
testFn(t, proxy, "https")
testFn(t, proxy, "http", len(httpData))
testFn(t, proxy, "https", len(httpData))
})
// Concurrent testing to detect stress
t.Run("Concurrent", func(t *testing.T) {
wg := sync.WaitGroup{}
const num = 50
for i := 0; i < num; i++ {
num := len(httpData) / 1024
for i := 1; i <= num; i++ {
i := i
wg.Add(1)
go func() {
testFn(t, proxy, "https")
testFn(t, proxy, "https", i*1024)
defer wg.Done()
}()
}
for i := 0; i < num; i++ {
for i := 1; i <= num; i++ {
i := i
wg.Add(1)
go func() {
testFn(t, proxy, "http")
testFn(t, proxy, "http", i*1024)
defer wg.Done()
}()
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert"
)
var singMuxProtocolList = []string{"h2mux", "smux"} // don't test "yamux" because it has some confused bugs
var singMuxProtocolList = []string{"smux"} // don't test "h2mux" and "yamux" because it has some confused bugs
// notCloseProxyAdapter is a proxy adapter that does not close the underlying outbound.ProxyAdapter.
// The outbound.SingMux will close the underlying outbound.ProxyAdapter when it is closed, but we don't want to close it.

View File

@@ -2,15 +2,11 @@ package sing_vless
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net"
"net/http"
"reflect"
"strconv"
"strings"
"time"
"unsafe"
"github.com/metacubex/mihomo/adapter/inbound"
@@ -88,42 +84,11 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
sl = &Listener{config: config, service: service}
if s := strings.SplitN(config.Decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
var i int
i, err = strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
var b []byte
b, err = base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
if len(b) == encryption.MLKEM768SeedLength {
sl.decryption = &encryption.ServerInstance{}
if err = sl.decryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
} else {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
sl.decryption, err = encryption.NewServer(config.Decryption)
if err != nil {
return nil, err
}
if sl.decryption != nil {
defer func() { // decryption must be closed to avoid the goroutine leak
if err != nil {
_ = sl.decryption.Close()

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"io"
"net"
@@ -13,7 +12,6 @@ import (
"time"
"github.com/metacubex/utls/mlkem"
"golang.org/x/crypto/hkdf"
"golang.org/x/sys/cpu"
)
@@ -30,19 +28,20 @@ var (
var ClientCipher byte
func init() {
if !HasAESGCMHardwareSupport {
if HasAESGCMHardwareSupport {
ClientCipher = 1
}
}
type ClientInstance struct {
sync.RWMutex
eKeyNfs *mlkem.EncapsulationKey768
xor uint32
minutes time.Duration
expire time.Time
baseKey []byte
ticket []byte
nfsEKey *mlkem.EncapsulationKey768
nfsEKeyBytes []byte
xor uint32
minutes time.Duration
expire time.Time
baseKey []byte
ticket []byte
}
type ClientConn struct {
@@ -58,19 +57,22 @@ type ClientConn struct {
peerCache []byte
}
func (i *ClientInstance) Init(eKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) {
i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData)
i.xor = xor
func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) {
i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes)
if xor > 0 {
i.nfsEKeyBytes = nfsEKeyBytes
i.xor = xor
}
i.minutes = minutes
return
}
func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
if i.eKeyNfs == nil {
if i.nfsEKey == nil {
return nil, errors.New("uninitialized")
}
if i.xor == 1 {
conn = NewXorConn(conn, i.eKeyNfs.Bytes())
if i.xor > 0 {
conn = NewXorConn(conn, i.nfsEKeyBytes)
}
c := &ClientConn{Conn: conn}
@@ -86,18 +88,19 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
i.RUnlock()
}
nfsKey, encapsulatedNfsKey := i.eKeyNfs.Encapsulate()
seed := make([]byte, 64)
rand.Read(seed)
dKeyPfs, _ := mlkem.NewDecapsulationKey768(seed)
eKeyPfs := dKeyPfs.EncapsulationKey().Bytes()
padding := randBetween(100, 1000)
pfsDKeySeed := make([]byte, 64)
rand.Read(pfsDKeySeed)
pfsDKey, _ := mlkem.NewDecapsulationKey768(pfsDKeySeed)
pfsEKeyBytes := pfsDKey.EncapsulationKey().Bytes()
nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate()
paddingLen := randBetween(100, 1000)
clientHello := make([]byte, 1088+1184+1+5+padding)
copy(clientHello, encapsulatedNfsKey)
copy(clientHello[1088:], eKeyPfs)
clientHello[2272] = ClientCipher
encodeHeader(clientHello[2273:], int(padding))
clientHello := make([]byte, 1+1184+1088+5+paddingLen)
clientHello[0] = ClientCipher
copy(clientHello[1:], pfsEKeyBytes)
copy(clientHello[1185:], encapsulatedNfsKey)
EncodeHeader(clientHello[2273:], int(paddingLen))
rand.Read(clientHello[2278:])
if _, err := c.Conn.Write(clientHello); err != nil {
return nil, err
@@ -111,17 +114,15 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
encapsulatedPfsKey := peerServerHello[:1088]
c.ticket = peerServerHello[1088:]
pfsKey, err := dKeyPfs.Decapsulate(encapsulatedPfsKey)
pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey)
if err != nil {
return nil, err
}
c.baseKey = append(nfsKey, pfsKey...)
c.baseKey = append(pfsKey, nfsKey...)
authKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfs).Read(authKey)
nonce := make([]byte, 12)
VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce, c.ticket, encapsulatedPfsKey)
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message
nonce := [12]byte{ClientCipher}
VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, nonce[:], c.ticket, pfsEKeyBytes)
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more messages
return nil, errors.New("invalid server")
}
@@ -141,32 +142,39 @@ func (c *ClientConn) Write(b []byte) (int, error) {
return 0, nil
}
var data []byte
if c.aead == nil {
c.random = make([]byte, 32)
rand.Read(c.random)
key := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, c.random, c.ticket).Read(key)
c.aead = newAead(ClientCipher, key)
c.nonce = make([]byte, 12)
data = make([]byte, 21+32+5+len(b)+16)
copy(data, c.ticket)
copy(data[21:], c.random)
encodeHeader(data[53:], len(b)+16)
c.aead.Seal(data[:58], c.nonce, b, data[53:58])
} else {
data = make([]byte, 5+len(b)+16)
encodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
}
increaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
for n := 0; n < len(b); {
b := b[n:]
if len(b) > 8192 {
b = b[:8192] // for avoiding another copy() in server's Read()
}
n += len(b)
if c.aead == nil {
c.random = make([]byte, 32)
rand.Read(c.random)
c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket)
c.nonce = make([]byte, 12)
data = make([]byte, 21+32+5+len(b)+16)
copy(data, c.ticket)
copy(data[21:], c.random)
EncodeHeader(data[53:], len(b)+16)
c.aead.Seal(data[:58], c.nonce, b, data[53:58])
} else {
data = make([]byte, 5+len(b)+16)
EncodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
if bytes.Equal(c.nonce, MaxNonce) {
c.aead = NewAead(ClientCipher, c.baseKey, data[5:], data[:5])
}
}
IncreaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
}
return len(b), nil
}
func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
func (c *ClientConn) Read(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
@@ -177,11 +185,11 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerPadding, _ := decodeHeader(peerHeader)
if peerPadding == 0 {
peerPaddingLen, _ := DecodeHeader(peerHeader)
if peerPaddingLen == 0 {
break
}
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil {
return 0, err
}
}
@@ -196,11 +204,9 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
return 0, err
}
if c.random == nil {
return 0, errors.New("can not Read() first")
return 0, errors.New("empty c.random")
}
peerKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey)
c.peerAead = newAead(ClientCipher, peerKey)
c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandom, c.random)
c.peerNonce = make([]byte, 12)
}
if len(c.peerCache) != 0 {
@@ -211,7 +217,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerLength, err := decodeHeader(peerHeader) // 17~17000
peerLength, err := DecodeHeader(peerHeader) // 17~17000
if err != nil {
if c.instance != nil {
c.instance.Lock()
@@ -228,10 +234,17 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
}
dst := peerData[:peerLength-16]
if len(dst) <= len(b) {
dst = b[:len(dst)] // max=8192 is recommended for peer
dst = b[:len(dst)] // avoids another copy()
}
var peerAead cipher.AEAD
if bytes.Equal(c.peerNonce, MaxNonce) {
peerAead = NewAead(ClientCipher, c.baseKey, peerData, peerHeader)
}
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
increaseNonce(c.peerNonce)
if peerAead != nil {
c.peerAead = peerAead
}
IncreaseNonce(c.peerNonce)
if err != nil {
return 0, err
}

View File

@@ -1,17 +1,22 @@
package encryption
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"math/big"
"strconv"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
)
func encodeHeader(b []byte, l int) {
var MaxNonce = bytes.Repeat([]byte{255}, 12)
func EncodeHeader(b []byte, l int) {
b[0] = 23
b[1] = 3
b[2] = 3
@@ -19,10 +24,10 @@ func encodeHeader(b []byte, l int) {
b[4] = byte(l)
}
func decodeHeader(b []byte) (int, error) {
func DecodeHeader(b []byte) (int, error) {
if b[0] == 23 && b[1] == 3 && b[2] == 3 {
l := int(b[3])<<8 | int(b[4])
if l < 17 || l > 17000 { // TODO
if l < 17 || l > 17000 { // TODO: TLSv1.3 max length
return 0, errors.New("invalid length in record's header: " + strconv.Itoa(l))
}
return l, nil
@@ -30,29 +35,24 @@ func decodeHeader(b []byte) (int, error) {
return 0, errors.New("invalid record's header")
}
func newAead(c byte, k []byte) cipher.AEAD {
switch c {
case 0:
if block, err := aes.NewCipher(k); err == nil {
aead, _ := cipher.NewGCM(block)
return aead
}
case 1:
aead, _ := chacha20poly1305.New(k)
return aead
func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) {
key := make([]byte, 32)
hkdf.New(sha256.New, secret, salt, info).Read(key)
if c&1 == 1 {
block, _ := aes.NewCipher(key)
aead, _ = cipher.NewGCM(block)
} else {
aead, _ = chacha20poly1305.New(key)
}
return nil
return
}
func increaseNonce(nonce []byte) {
func IncreaseNonce(nonce []byte) {
for i := 0; i < 12; i++ {
nonce[11-i]++
if nonce[11-i] != 0 {
break
}
if i == 11 {
// TODO
}
}
}

View File

@@ -2,4 +2,7 @@
// https://github.com/XTLS/Xray-core/commit/f61c14e9c63dc41a8a09135db3aea337974f3f37
// https://github.com/XTLS/Xray-core/commit/3e19bf9233bdd9bafc073a71c65b737cc1ffba5e
// https://github.com/XTLS/Xray-core/commit/7ffb555fc8ec51bd1e3e60f26f1d6957984dba80
// https://github.com/XTLS/Xray-core/commit/ec1cc35188c1a5f38a2ff75e88b5d043ffdc59da
// https://github.com/XTLS/Xray-core/commit/5c611420487a92f931faefc01d4bf03869f477f6
// https://github.com/XTLS/Xray-core/commit/23d7aad461d232bc5bed52dd6aaa731ecd88ad35
package encryption

View File

@@ -0,0 +1,99 @@
package encryption
import (
"encoding/base64"
"fmt"
"strconv"
"strings"
"time"
)
// NewClient new client from encryption string
// maybe return a nil *ClientInstance without any error, that means don't need to encrypt
func NewClient(encryption string) (*ClientInstance, error) {
switch encryption {
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
return nil, nil
}
if s := strings.SplitN(encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
i, err := strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
b, err := base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
if len(b) == MLKEM768ClientLength {
client := &ClientInstance{}
if err = client.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
return client, nil
} else {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
}
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
// NewServer new server from decryption string
// maybe return a nil *ServerInstance without any error, that means don't need to decrypt
func NewServer(decryption string) (*ServerInstance, error) {
switch decryption {
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
return nil, nil
}
if s := strings.SplitN(decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
i, err := strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
b, err := base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
if len(b) == MLKEM768SeedLength {
server := &ServerInstance{}
if err = server.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
return server, nil
} else {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
}
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"io"
"net"
@@ -12,7 +11,6 @@ import (
"time"
"github.com/metacubex/utls/mlkem"
"golang.org/x/crypto/hkdf"
)
type ServerSession struct {
@@ -24,11 +22,12 @@ type ServerSession struct {
type ServerInstance struct {
sync.RWMutex
dKeyNfs *mlkem.DecapsulationKey768
xor uint32
minutes time.Duration
sessions map[[21]byte]*ServerSession
stop bool
nfsDKey *mlkem.DecapsulationKey768
nfsEKeyBytes []byte
xor uint32
minutes time.Duration
sessions map[[21]byte]*ServerSession
closed bool
}
type ServerConn struct {
@@ -44,9 +43,12 @@ type ServerConn struct {
nonce []byte
}
func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) {
i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData)
i.xor = xor
func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Duration) (err error) {
i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed)
if xor > 0 {
i.nfsEKeyBytes = i.nfsDKey.EncapsulationKey().Bytes()
i.xor = xor
}
if minutes > 0 {
i.minutes = minutes
i.sessions = make(map[[21]byte]*ServerSession)
@@ -55,12 +57,13 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat
time.Sleep(time.Minute)
now := time.Now()
i.Lock()
if i.stop {
if i.closed {
i.Unlock()
return
}
for index, session := range i.sessions {
for ticket, session := range i.sessions {
if now.After(session.expire) {
delete(i.sessions, index)
delete(i.sessions, ticket)
}
}
i.Unlock()
@@ -72,17 +75,17 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat
func (i *ServerInstance) Close() (err error) {
i.Lock()
defer i.Unlock()
i.stop = true
i.closed = true
i.Unlock()
return
}
func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
if i.dKeyNfs == nil {
if i.nfsDKey == nil {
return nil, errors.New("uninitialized")
}
if i.xor == 1 {
conn = NewXorConn(conn, i.dKeyNfs.EncapsulationKey().Bytes())
if i.xor > 0 {
conn = NewXorConn(conn, i.nfsEKeyBytes)
}
c := &ServerConn{Conn: conn}
@@ -109,50 +112,49 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return nil, err
}
if l, _ := decodeHeader(peerHeader); l != 0 {
c.Conn.Write(make([]byte, randBetween(100, 1000))) // make client do new handshake
if l, _ := DecodeHeader(peerHeader); l != 0 {
noise := make([]byte, randBetween(100, 1000))
rand.Read(noise)
c.Conn.Write(noise) // make client do new handshake
return nil, errors.New("invalid ticket")
}
peerClientHello := make([]byte, 1088+1184+1)
peerClientHello := make([]byte, 1+1184+1088)
copy(peerClientHello, peerTicketHello)
copy(peerClientHello[53:], peerHeader)
if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil {
return nil, err
}
encapsulatedNfsKey := peerClientHello[:1088]
eKeyPfsData := peerClientHello[1088:2272]
c.cipher = peerClientHello[2272]
if c.cipher != 0 && c.cipher != 1 {
return nil, errors.New("invalid cipher")
}
c.cipher = peerClientHello[0]
pfsEKeyBytes := peerClientHello[1:1185]
encapsulatedNfsKey := peerClientHello[1185:2273]
nfsKey, err := i.dKeyNfs.Decapsulate(encapsulatedNfsKey)
pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes)
if err != nil {
return nil, err
}
eKeyPfs, err := mlkem.NewEncapsulationKey768(eKeyPfsData)
nfsKey, err := i.nfsDKey.Decapsulate(encapsulatedNfsKey)
if err != nil {
return nil, err
}
pfsKey, encapsulatedPfsKey := eKeyPfs.Encapsulate()
c.baseKey = append(nfsKey, pfsKey...)
pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate()
c.baseKey = append(pfsKey, nfsKey...)
authKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfsData).Read(authKey)
nonce := make([]byte, 12)
c.ticket = newAead(c.cipher, authKey).Seal(nil, nonce, []byte("VLESS"), encapsulatedPfsKey)
nonce := [12]byte{c.cipher}
c.ticket = NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, nonce[:], []byte("VLESS"), pfsEKeyBytes)
padding := randBetween(100, 1000)
paddingLen := randBetween(100, 1000)
serverHello := make([]byte, 1088+21+5+padding)
serverHello := make([]byte, 1088+21+5+paddingLen)
copy(serverHello, encapsulatedPfsKey)
copy(serverHello[1088:], c.ticket)
encodeHeader(serverHello[1109:], int(padding))
EncodeHeader(serverHello[1109:], int(paddingLen))
rand.Read(serverHello[1114:])
if _, err := c.Conn.Write(serverHello); err != nil {
return nil, err
}
// we can send more padding if needed
if i.minutes > 0 {
i.Lock()
@@ -178,11 +180,11 @@ func (c *ServerConn) Read(b []byte) (int, error) {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerPadding, _ := decodeHeader(peerHeader)
if peerPadding == 0 {
peerPaddingLen, _ := DecodeHeader(peerHeader)
if peerPaddingLen == 0 {
break
}
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil {
return 0, err
}
}
@@ -199,9 +201,7 @@ func (c *ServerConn) Read(b []byte) (int, error) {
return 0, err
}
}
peerKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, c.peerRandom, c.ticket).Read(peerKey)
c.peerAead = newAead(c.cipher, peerKey)
c.peerAead = NewAead(c.cipher, c.baseKey, c.peerRandom, c.ticket)
c.peerNonce = make([]byte, 12)
}
if len(c.peerCache) != 0 {
@@ -212,7 +212,7 @@ func (c *ServerConn) Read(b []byte) (int, error) {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerLength, err := decodeHeader(peerHeader) // 17~17000
peerLength, err := DecodeHeader(peerHeader) // 17~17000
if err != nil {
return 0, err
}
@@ -222,10 +222,17 @@ func (c *ServerConn) Read(b []byte) (int, error) {
}
dst := peerData[:peerLength-16]
if len(dst) <= len(b) {
dst = b[:len(dst)] // max=8192 is recommended for peer
dst = b[:len(dst)] // avoids another copy()
}
var peerAead cipher.AEAD
if bytes.Equal(c.peerNonce, MaxNonce) {
peerAead = NewAead(c.cipher, c.baseKey, peerData, peerHeader)
}
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
increaseNonce(c.peerNonce)
if peerAead != nil {
c.peerAead = peerAead
}
IncreaseNonce(c.peerNonce)
if err != nil {
return 0, errors.New("error")
}
@@ -236,31 +243,39 @@ func (c *ServerConn) Read(b []byte) (int, error) {
return len(dst), nil
}
func (c *ServerConn) Write(b []byte) (int, error) { // after first Read()
func (c *ServerConn) Write(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
var data []byte
if c.aead == nil {
if c.peerRandom == nil {
return 0, errors.New("can not Write() first")
for n := 0; n < len(b); {
b := b[n:]
if len(b) > 8192 {
b = b[:8192] // for avoiding another copy() in client's Read()
}
n += len(b)
if c.aead == nil {
if c.peerRandom == nil {
return 0, errors.New("empty c.peerRandom")
}
data = make([]byte, 32+5+len(b)+16)
rand.Read(data[:32])
c.aead = NewAead(c.cipher, c.baseKey, data[:32], c.peerRandom)
c.nonce = make([]byte, 12)
EncodeHeader(data[32:], len(b)+16)
c.aead.Seal(data[:37], c.nonce, b, data[32:37])
} else {
data = make([]byte, 5+len(b)+16)
EncodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
if bytes.Equal(c.nonce, MaxNonce) {
c.aead = NewAead(c.cipher, c.baseKey, data[5:], data[:5])
}
}
IncreaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
data = make([]byte, 32+5+len(b)+16)
rand.Read(data[:32])
key := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, data[:32], c.peerRandom).Read(key)
c.aead = newAead(c.cipher, key)
c.nonce = make([]byte, 12)
encodeHeader(data[32:], len(b)+16)
c.aead.Seal(data[:37], c.nonce, b, data[32:37])
} else {
data = make([]byte, 5+len(b)+16)
encodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
}
increaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
return len(b), nil
}

View File

@@ -38,7 +38,7 @@ func (c *XorConn) Write(b []byte) (int, error) {
return 0, err
}
if iv != nil {
b = b[16:]
b = b[16:] // for len(b)
}
return len(b), nil
}

View File

@@ -579,18 +579,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.43"
version = "4.5.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.43"
version = "4.5.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8"
dependencies = [
"anstream",
"anstyle",
@@ -800,7 +800,7 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
dependencies = [
"thiserror 2.0.12",
"thiserror 2.0.14",
]
[[package]]
@@ -1004,7 +1004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -1403,7 +1403,7 @@ dependencies = [
"ring",
"rustls",
"serde",
"thiserror 2.0.12",
"thiserror 2.0.14",
"tinyvec",
"tokio",
"tokio-rustls",
@@ -1431,7 +1431,7 @@ dependencies = [
"rustls",
"serde",
"smallvec",
"thiserror 2.0.12",
"thiserror 2.0.14",
"tokio",
"tokio-rustls",
"tracing",
@@ -1942,7 +1942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.53.2",
"windows-targets 0.48.5",
]
[[package]]
@@ -2449,7 +2449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
dependencies = [
"memchr",
"thiserror 2.0.12",
"thiserror 2.0.14",
"ucd-trie",
]
@@ -2668,7 +2668,7 @@ dependencies = [
"rustc-hash",
"rustls",
"socket2 0.5.10",
"thiserror 2.0.12",
"thiserror 2.0.14",
"tokio",
"tracing",
"web-time",
@@ -2689,7 +2689,7 @@ dependencies = [
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.12",
"thiserror 2.0.14",
"tinyvec",
"tracing",
"web-time",
@@ -2706,7 +2706,7 @@ dependencies = [
"once_cell",
"socket2 0.5.10",
"tracing",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -2806,7 +2806,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
dependencies = [
"getrandom 0.2.16",
"libredox",
"thiserror 2.0.12",
"thiserror 2.0.14",
]
[[package]]
@@ -3021,7 +3021,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -3320,7 +3320,7 @@ dependencies = [
"shadowsocks-crypto",
"socket2 0.6.0",
"spin",
"thiserror 2.0.12",
"thiserror 2.0.14",
"tokio",
"tokio-tfo",
"trait-variant",
@@ -3387,7 +3387,7 @@ dependencies = [
"snmalloc-rs",
"sysexits",
"tcmalloc",
"thiserror 2.0.12",
"thiserror 2.0.14",
"time",
"tokio",
"tracing",
@@ -3436,7 +3436,7 @@ dependencies = [
"smoltcp",
"socket2 0.6.0",
"spin",
"thiserror 2.0.12",
"thiserror 2.0.14",
"tokio",
"tokio-native-tls",
"tokio-rustls",
@@ -3703,7 +3703,7 @@ dependencies = [
"getrandom 0.3.3",
"once_cell",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -3727,11 +3727,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.12"
version = "2.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e"
dependencies = [
"thiserror-impl 2.0.12",
"thiserror-impl 2.0.14",
]
[[package]]
@@ -3747,9 +3747,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.12"
version = "2.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227"
dependencies = [
"proc-macro2",
"quote",
@@ -4053,7 +4053,7 @@ dependencies = [
"libc",
"log",
"nix",
"thiserror 2.0.12",
"thiserror 2.0.14",
"tokio",
"tokio-util",
"windows-sys 0.60.2",
@@ -4343,7 +4343,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -4737,7 +4737,7 @@ dependencies = [
"futures",
"libloading",
"log",
"thiserror 2.0.12",
"thiserror 2.0.14",
"windows-sys 0.60.2",
"winreg 0.55.0",
]

View File

@@ -475,30 +475,30 @@ if singbox_tags:find("with_utls") then
o:value("firefox")
o:value("edge")
o:value("safari")
-- o:value("360")
o:value("360")
o:value("qq")
o:value("ios")
-- o:value("android")
o:value("android")
o:value("random")
-- o:value("randomized")
o:value("randomized")
o.default = "chrome"
o:depends({ [_n("tls")] = true, [_n("utls")] = true })
o:depends({ [_n("utls")] = true })
-- [[ REALITY部分 ]] --
o = s:option(Flag, _n("reality"), translate("REALITY"))
o.default = 0
o:depends({ [_n("protocol")] = "vless", [_n("utls")] = true })
o:depends({ [_n("protocol")] = "vmess", [_n("utls")] = true })
o:depends({ [_n("protocol")] = "shadowsocks", [_n("utls")] = true })
o:depends({ [_n("protocol")] = "socks", [_n("utls")] = true })
o:depends({ [_n("protocol")] = "trojan", [_n("utls")] = true })
o:depends({ [_n("protocol")] = "anytls", [_n("utls")] = true })
o:depends({ [_n("protocol")] = "vless", [_n("tls")] = true })
o:depends({ [_n("protocol")] = "vmess", [_n("tls")] = true })
o:depends({ [_n("protocol")] = "shadowsocks", [_n("tls")] = true })
o:depends({ [_n("protocol")] = "socks", [_n("tls")] = true })
o:depends({ [_n("protocol")] = "trojan", [_n("tls")] = true })
o:depends({ [_n("protocol")] = "anytls", [_n("tls")] = true })
o = s:option(Value, _n("reality_publicKey"), translate("Public Key"))
o:depends({ [_n("utls")] = true, [_n("reality")] = true })
o:depends({ [_n("reality")] = true })
o = s:option(Value, _n("reality_shortId"), translate("Short Id"))
o:depends({ [_n("utls")] = true, [_n("reality")] = true })
o:depends({ [_n("reality")] = true })
end
o = s:option(ListValue, _n("transport"), translate("Transport"))

View File

@@ -17,6 +17,9 @@ local xray_fragment = ucursor:get_all("shadowsocksr", "@global_xray_fragment[0]"
local xray_noise = ucursor:get_all("shadowsocksr", "@xray_noise_packets[0]") or {}
local outbound_settings = nil
local node_id = server_section
local remarks = server.alias or ""
function vmess_vless()
outbound_settings = {
vnext = {
@@ -238,7 +241,7 @@ end
rawSettings = (server.transport == "raw" or server.transport == "tcp") and {
-- tcp
header = {
type = server.tcp_guise or "none",
type = server.tcp_guise,
request = (server.tcp_guise == "http") and {
-- request
path = {server.http_path} or {"/"},
@@ -317,7 +320,8 @@ end
tcpMptcp = (server.mptcp == "1") and true or nil, -- MPTCP
Penetrate = (server.mptcp == "1") and true or nil, -- Penetrate MPTCP
tcpcongestion = server.custom_tcpcongestion, -- 连接服务器节点的 TCP 拥塞控制算法
dialerProxy = (xray_fragment.fragment == "1" or xray_fragment.noise == "1") and "dialerproxy" or nil
dialerProxy = (xray_fragment.fragment == "1" or xray_fragment.noise == "1") and
((remarks ~= nil and remarks ~= "") and (node_id .. "." .. remarks) or node_id) or nil
}
} or nil,
mux = (server.v2ray_protocol ~= "wireguard") and {
@@ -334,7 +338,7 @@ end
if xray_fragment.fragment ~= "0" or (xray_fragment.noise ~= "0" and xray_noise.enabled ~= "0") then
table.insert(Xray.outbounds, {
protocol = "freedom",
tag = "dialerproxy",
tag = (remarks ~= nil and remarks ~= "") and (node_id .. "." .. remarks) or node_id,
settings = {
domainStrategy = (xray_fragment.noise == "1" and xray_noise.enabled == "1") and xray_noise.domainStrategy,
fragment = (xray_fragment.fragment == "1") and {

View File

@@ -142,6 +142,7 @@ public class CoreTypeItem
public class TunModeItem
{
public bool EnableTun { get; set; }
public bool AutoRoute { get; set; } = true;
public bool StrictRoute { get; set; } = true;
public string Stack { get; set; }
public int Mtu { get; set; }

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -1099,13 +1099,13 @@
<value>Отмена тестирования...</value>
</data>
<data name="TransportRequestHostTip5" xml:space="preserve">
<value>*gRPC Authority</value>
<value>* gRPC Authority (HTTP/2 псевдозаголовок :authority)</value>
</data>
<data name="menuAddHttpServer" xml:space="preserve">
<value>Добавить сервер [HTTP]</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>which conflicts with the group previous proxy</value>
<value>что конфликтует с предыдущим прокси группы</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>Включить фрагментацию (Fragment)</value>
@@ -1318,13 +1318,13 @@
<value>Пароль sudo системы</value>
</data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart.</value>
<value>Пароль sudo будет проверен в терминале. Если из-за ошибки проверки приложение начнёт работать некорректно, перезапустите его. Пароль не сохраняется — его нужно вводить после каждого перезапуска.</value>
</data>
<data name="TransportHeaderTypeTip5" xml:space="preserve">
<value>*XHTTP-режим</value>
</data>
<data name="TransportExtraTip" xml:space="preserve">
<value>Дополнительный XHTTP сырой JSON, формат: { XHTTPObject }</value>
<value>Дополнительный сырой JSON для XHTTP, формат: { XHTTP Object }</value>
</data>
<data name="TbSettingsHide2TrayWhenClose" xml:space="preserve">
<value>Скрыть в трее при закрытии окна</value>
@@ -1393,10 +1393,10 @@
<value>URL для тестирования текущего соединения</value>
</data>
<data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>Can fill in the configuration remarks, please make sure it exist and are unique</value>
<value>Можно указать название (Remarks) из конфигурации, убедитесь, что оно существует и уникально</value>
</data>
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
<value>Incorrect password, please try again.</value>
<value>Неверный пароль, попробуйте ещё раз.</value>
</data>
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
@@ -1405,99 +1405,99 @@
<value>Добавить сервер [Anytls]</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>Remote DNS</value>
<value>Удалённый DNS</value>
</data>
<data name="TbDomesticDNS" xml:space="preserve">
<value>Domestic DNS</value>
<value>Внутренний DNS</value>
</data>
<data name="TbSBOutboundsResolverDNS" xml:space="preserve">
<value>Outbound DNS Resolution (sing-box)</value>
<value>Резолвер DNS для исходящих (sing-box)</value>
</data>
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
<value>Resolve Outbound Domains</value>
<value>Разрешать домены для исходящих соединений</value>
</data>
<data name="TbSBDoHResolverServer" xml:space="preserve">
<value>sing-box DoH Resolver Server</value>
<value>Сервер DoH-резолвера (sing-box)</value>
</data>
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
<value>Fallback DNS Resolution, Suggest IP</value>
<value>Резервное DNS-разрешение (рекомендуется указывать IP)</value>
</data>
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
<value>xray Freedom Resolution Strategy</value>
<value>Стратегия резолвинга Freedom (Xray)</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
<value>sing-box Direct Resolution Strategy</value>
<value>Стратегия прямого резолвинга (sing-box)</value>
</data>
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
<value>sing-box Remote Resolution Strategy</value>
<value>Стратегия удалённого резолвинга (sing-box)</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Add Common DNS Hosts</value>
<value>Добавить стандартные записи hosts (DNS)</value>
</data>
<data name="TbSBDoHOverride" xml:space="preserve">
<value>The sing-box DoH resolution server can be overwritten</value>
<value>Сервер DoH-резолвера sing-box можно переопределить</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
<value>Block SVCB and HTTPS Queries</value>
<value>Блокировать DNS-запросы SVCB и HTTPS</value>
</data>
<data name="TbDNSHostsConfig" xml:space="preserve">
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
<value>DNS hosts: (каждая строка в формате "domain1 ip1 ip2")</value>
</data>
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
<value>Apply to Proxy Domains Only</value>
<value>Применять только к доменам через прокси</value>
</data>
<data name="ThBasicDNSSettings" xml:space="preserve">
<value>Basic DNS Settings</value>
<value>Базовые настройки DNS</value>
</data>
<data name="ThAdvancedDNSSettings" xml:space="preserve">
<value>Advanced DNS Settings</value>
<value>Расширенные настройки DNS</value>
</data>
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
<value>Validate Regional Domain IPs</value>
<value>Проверять IP-адреса региональных доменов</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs</value>
<value>При включении проверяет IP-адреса, возвращаемые для региональных доменов (например, geosite:cn), и оставляет только ожидаемые IP-адреса</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>Enable Custom DNS</value>
<value>Включить пользовательский DNS</value>
</data>
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
<value>Включён пользовательский DNS — настройки на этой странице не применяются</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>Prevent domain-based routing rules from failing</value>
<value>Предотвращает сбои доменных правил маршрутизации</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>Please fill in the correct config template</value>
<value>Пожалуйста, заполните корректный шаблон конфигурации</value>
</data>
<data name="menuFullConfigTemplate" xml:space="preserve">
<value>Full Config Template Setting</value>
<value>Настройка полного шаблона конфигурации</value>
</data>
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
<value>Enable Full Config Template</value>
<value>Включить полный шаблон конфигурации</value>
</data>
<data name="TbRayFullConfigTemplate" xml:space="preserve">
<value>v2ray Full Config Template</value>
<value>Полный шаблон конфигурации v2ray</value>
</data>
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document</value>
<value>Добавляет только конфигурацию исходящих (outbound), а также routing.balancers и routing.rules.outboundTag. Нажмите, чтобы открыть документ</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>Do Not Add Non-Proxy Protocol Outbound</value>
<value>Не добавлять исходящие для непрокси-протоколов</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>Set Upstream Proxy Tag</value>
<value>Задать тег верхнего прокси (upstream)</value>
</data>
<data name="TbSBFullConfigTemplate" xml:space="preserve">
<value>sing-box Full Config Template</value>
<value>Полный шаблон конфигурации sing-box</value>
</data>
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
<value>Add Outbound and Endpoint Config Only, Click to view the document</value>
<value>Добавляет только конфигурацию Outbound и Endpoint. Нажмите, чтобы открыть документ</value>
</data>
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
<value>Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную.</value>
</data>
</root>
</root>

View File

@@ -80,8 +80,7 @@ public class CoreConfigSingboxService
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
ret.Data = await ApplyFullConfigTemplate(fullConfigTemplate, singboxConfig);
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
return ret;
}
catch (Exception ex)
@@ -434,8 +433,7 @@ public class CoreConfigSingboxService
ret.Success = true;
var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
ret.Data = await ApplyFullConfigTemplate(fullConfigTemplate, singboxConfig);
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
return ret;
}
catch (Exception ex)
@@ -628,6 +626,7 @@ public class CoreConfigSingboxService
var tunInbound = JsonUtils.Deserialize<Inbound4Sbox>(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { };
tunInbound.interface_name = Utils.IsOSX() ? $"utun{new Random().Next(99)}" : "singbox_tun";
tunInbound.mtu = _config.TunModeItem.Mtu;
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
tunInbound.stack = _config.TunModeItem.Stack;
if (_config.TunModeItem.EnableIPv6Address == false)
@@ -2218,15 +2217,16 @@ public class CoreConfigSingboxService
return 0;
}
private async Task<string> ApplyFullConfigTemplate(FullConfigTemplateItem fullConfigTemplate, SingboxConfig singboxConfig)
private async Task<string> ApplyFullConfigTemplate(SingboxConfig singboxConfig)
{
var fullConfigTemplateItem = fullConfigTemplate.Config;
if (_config.TunModeItem.EnableTun)
var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled)
{
fullConfigTemplateItem = fullConfigTemplate.TunConfig;
return JsonUtils.Serialize(singboxConfig);
}
if (!fullConfigTemplate.Enabled || fullConfigTemplateItem.IsNullOrEmpty())
var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
if (fullConfigTemplateItem.IsNullOrEmpty())
{
return JsonUtils.Serialize(singboxConfig);
}

View File

@@ -3,7 +3,6 @@ using System.Net.NetworkInformation;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using ServiceLib.Models;
namespace ServiceLib.Services.CoreConfig;
@@ -69,9 +68,7 @@ public class CoreConfigV2rayService
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
ret.Data = await ApplyFullConfigTemplate(fullConfigTemplate, v2rayConfig);
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
return ret;
}
catch (Exception ex)
@@ -201,8 +198,7 @@ public class CoreConfigV2rayService
ret.Success = true;
var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
ret.Data = await ApplyFullConfigTemplate(fullConfigTemplate, v2rayConfig, true);
ret.Data = await ApplyFullConfigTemplate(v2rayConfig, true);
return ret;
}
catch (Exception ex)
@@ -1844,9 +1840,10 @@ public class CoreConfigV2rayService
return await Task.FromResult(0);
}
private async Task<string> ApplyFullConfigTemplate(FullConfigTemplateItem fullConfigTemplate, V2rayConfig v2rayConfig, bool handleBalancerAndRules = false)
private async Task<string> ApplyFullConfigTemplate(V2rayConfig v2rayConfig, bool handleBalancerAndRules = false)
{
if (!fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
var fullConfigTemplate = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
{
return JsonUtils.Serialize(v2rayConfig);
}
@@ -1918,6 +1915,7 @@ public class CoreConfigV2rayService
}
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
}
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
}

View File

@@ -84,6 +84,7 @@ public class OptionSettingViewModel : MyReactiveObject
#region Tun mode
[Reactive] public bool TunAutoRoute { get; set; }
[Reactive] public bool TunStrictRoute { get; set; }
[Reactive] public string TunStack { get; set; }
[Reactive] public int TunMtu { get; set; }
@@ -201,6 +202,7 @@ public class OptionSettingViewModel : MyReactiveObject
#region Tun mode
TunAutoRoute = _config.TunModeItem.AutoRoute;
TunStrictRoute = _config.TunModeItem.StrictRoute;
TunStack = _config.TunModeItem.Stack;
TunMtu = _config.TunModeItem.Mtu;
@@ -354,6 +356,7 @@ public class OptionSettingViewModel : MyReactiveObject
_config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol;
//tun mode
_config.TunModeItem.AutoRoute = TunAutoRoute;
_config.TunModeItem.StrictRoute = TunStrictRoute;
_config.TunModeItem.Stack = TunStack;
_config.TunModeItem.Mtu = TunMtu;

View File

@@ -729,71 +729,84 @@
Margin="{StaticResource Margin4}"
ColumnDefinitions="Auto,Auto,Auto"
DockPanel.Dock="Top"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Auto Route" />
<ToggleSwitch
x:Name="togAutoRoute"
Grid.Row="2"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Strict Route" />
<ToggleSwitch
x:Name="togStrictRoute"
Grid.Row="2"
Grid.Row="3"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="3"
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Stack" />
<ComboBox
x:Name="cmbStack"
Grid.Row="3"
Grid.Row="4"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="4"
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Mtu" />
<ComboBox
x:Name="cmbMtu"
Grid.Row="4"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="5"
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsEnableExInbound}" />
<ToggleSwitch
x:Name="togEnableExInbound"
Grid.Row="5"
Grid.Row="6"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="6"
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsEnableIPv6Address}" />
<ToggleSwitch
x:Name="togEnableIPv6Address"
Grid.Row="6"
Grid.Row="7"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />

View File

@@ -105,6 +105,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunAutoRoute, v => v.togAutoRoute.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunStrictRoute, v => v.togStrictRoute.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunStack, v => v.cmbStack.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.SelectedValue).DisposeWith(disposables);

View File

@@ -1023,6 +1023,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@@ -1036,9 +1037,9 @@
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Strict Route" />
Text="Auto Route" />
<ToggleButton
x:Name="togStrictRoute"
x:Name="togAutoRoute"
Grid.Row="2"
Grid.Column="1"
Margin="{StaticResource Margin8}"
@@ -1050,10 +1051,24 @@
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Strict Route" />
<ToggleButton
x:Name="togStrictRoute"
Grid.Row="3"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Stack" />
<ComboBox
x:Name="cmbStack"
Grid.Row="3"
Grid.Row="4"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
@@ -1061,7 +1076,7 @@
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="4"
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
@@ -1069,7 +1084,7 @@
Text="Mtu" />
<ComboBox
x:Name="cmbMtu"
Grid.Row="4"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
@@ -1077,7 +1092,7 @@
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="5"
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
@@ -1085,13 +1100,13 @@
Text="{x:Static resx:ResUI.TbSettingsEnableExInbound}" />
<ToggleButton
x:Name="togEnableExInbound"
Grid.Row="5"
Grid.Row="6"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="6"
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
@@ -1099,7 +1114,7 @@
Text="{x:Static resx:ResUI.TbSettingsEnableIPv6Address}" />
<ToggleButton
x:Name="togEnableIPv6Address"
Grid.Row="6"
Grid.Row="7"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
@@ -1231,3 +1246,4 @@
</TabControl>
</DockPanel>
</base:WindowBase>

View File

@@ -117,6 +117,7 @@ public partial class OptionSettingWindow
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunAutoRoute, v => v.togAutoRoute.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunStrictRoute, v => v.togStrictRoute.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunStack, v => v.cmbStack.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.Text).DisposeWith(disposables);

View File

@@ -12,8 +12,8 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 35
versionCode = 666
versionName = "1.10.16"
versionCode = 667
versionName = "1.10.17"
multiDexEnabled = true
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')

View File

@@ -27,6 +27,7 @@ object AppConfig {
const val PREF_VPN_DNS = "pref_vpn_dns"
const val PREF_VPN_BYPASS_LAN = "pref_vpn_bypass_lan"
const val PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX = "pref_vpn_interface_address_config_index"
const val PREF_VPN_MTU = "pref_vpn_mtu"
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
const val PREF_ROUTING_RULESET = "pref_routing_ruleset"
const val PREF_MUX_ENABLED = "pref_mux_enabled"

View File

@@ -370,4 +370,11 @@ object SettingsManager {
val selectedIndex = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX, "0")?.toInt()
return VpnInterfaceAddressConfig.getConfigByIndex(selectedIndex ?: 0)
}
/**
* Get the VPN MTU from settings, defaulting to AppConfig.VPN_MTU.
*/
fun getVpnMtu(): Int {
return Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_MTU), AppConfig.VPN_MTU)
}
}

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.os.ParcelFileDescriptor
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.VPN_MTU
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import java.io.File
@@ -60,7 +59,7 @@ class TProxyService(
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
return buildString {
appendLine("tunnel:")
appendLine(" mtu: $VPN_MTU")
appendLine(" mtu: ${SettingsManager.getVpnMtu()}")
appendLine(" ipv4: ${vpnConfig.ipv4Client}")
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {

View File

@@ -6,7 +6,6 @@ import android.net.LocalSocketAddress
import android.os.ParcelFileDescriptor
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.VPN_MTU
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.Utils
@@ -42,7 +41,7 @@ class Tun2SocksService(
"--netif-ipaddr", vpnConfig.ipv4Router,
"--netif-netmask", "255.255.255.252",
"--socks-server-addr", "${AppConfig.LOOPBACK}:${socksPort}",
"--tunmtu", VPN_MTU.toString(),
"--tunmtu", SettingsManager.getVpnMtu().toString(),
"--sock-path", "sock_path",
"--enable-udprelay",
"--loglevel", "notice"

View File

@@ -17,7 +17,6 @@ import android.util.Log
import androidx.annotation.RequiresApi
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.AppConfig.VPN_MTU
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.NotificationManager
@@ -185,7 +184,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
val bypassLan = SettingsManager.routingRulesetsBypassLan()
// Configure IPv4 settings
builder.setMtu(VPN_MTU)
builder.setMtu(SettingsManager.getVpnMtu())
builder.addAddress(vpnConfig.ipv4Client, 30)
// Configure routing rules

View File

@@ -45,6 +45,7 @@ class SettingsActivity : BaseActivity() {
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
private val vpnBypassLan by lazy { findPreference<ListPreference>(AppConfig.PREF_VPN_BYPASS_LAN) }
private val vpnInterfaceAddress by lazy { findPreference<ListPreference>(AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX) }
private val vpnMtu by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_MTU) }
private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) }
private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) }
@@ -93,6 +94,12 @@ class SettingsActivity : BaseActivity() {
true
}
vpnMtu?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
vpnMtu?.summary = if (TextUtils.isEmpty(nval)) AppConfig.VPN_MTU.toString() else nval
true
}
mux?.setOnPreferenceChangeListener { _, newValue ->
updateMux(newValue as Boolean)
true
@@ -196,6 +203,7 @@ class SettingsActivity : BaseActivity() {
appendHttpProxy?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_APPEND_HTTP_PROXY, false)
localDnsPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
vpnDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_DNS, AppConfig.DNS_VPN)
vpnMtu?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_MTU, AppConfig.VPN_MTU.toString())
updateMux(MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false))
mux?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false)
@@ -229,6 +237,7 @@ class SettingsActivity : BaseActivity() {
listOf(
localDnsPort,
vpnDns,
vpnMtu,
muxConcurrency,
muxXudpConcurrency,
fragmentLength,
@@ -298,6 +307,7 @@ class SettingsActivity : BaseActivity() {
vpnDns?.isEnabled = vpn
vpnBypassLan?.isEnabled = vpn
vpnInterfaceAddress?.isEnabled = vpn
vpnMtu?.isEnabled = vpn
if (vpn) {
updateLocalDns(
MmkvManager.decodeSettingsBool(

View File

@@ -81,6 +81,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id" translatable="false">المعرّف القصير</string>
<string name="server_lab_spider_x" translatable="false">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key" translatable="false">المفتاح السري</string>
<string name="server_lab_reserved">محجوز (اختياري)</string>
<string name="server_lab_local_address">العنوان المحلي (اختياري IPv4/IPv6، مفصولة بفواصل)</string>
@@ -183,6 +184,7 @@
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">DNS المحلي (اختياري)</string>
<string name="summary_pref_domestic_dns">DNS</string>

View File

@@ -184,6 +184,7 @@
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">ঘরোয়া DNS (ঐচ্ছিক)</string>
<string name="summary_pref_domestic_dns">DNS</string>

View File

@@ -184,6 +184,7 @@
<string name="title_pref_vpn_bypass_lan">VPN ز شبکه مهلی اگوڌرته؟</string>
<string name="title_pref_vpn_interface_address">نشۊوی رابت VPN</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">DNS منی (اختیاری)</string>
<string name="summary_pref_domestic_dns">DNS</string>

View File

@@ -182,6 +182,7 @@
<string name="title_pref_vpn_bypass_lan">آیا VPN از شبکه محلی عبور می کند؟</string>
<string name="title_pref_vpn_interface_address">آدرس واسط VPN</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">DNS داخلی (اختیاری)</string>
<string name="summary_pref_domestic_dns">DNS</string>

View File

@@ -183,6 +183,7 @@
<string name="title_pref_vpn_bypass_lan">VPN обходит LAN</string>
<string name="title_pref_vpn_interface_address">Адрес интерфейса VPN</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">Внутренняя DNS (необязательно)</string>
<string name="summary_pref_domestic_dns">DNS</string>

View File

@@ -184,6 +184,7 @@
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">DNS nội địa (Không bắt buộc)</string>
<string name="summary_pref_domestic_dns">DNS</string>

View File

@@ -181,6 +181,7 @@
<string name="title_pref_vpn_bypass_lan">VPN 是否绕过局域网</string>
<string name="title_pref_vpn_interface_address">VPN 接口地址</string>
<string name="title_pref_vpn_mtu">VPN MTU (默认 1500)</string>
<string name="title_pref_domestic_dns">境内 DNS (可选)</string>
<string name="summary_pref_domestic_dns">DNS</string>

View File

@@ -183,6 +183,7 @@
<string name="title_pref_vpn_bypass_lan">VPN 是否繞過區域網</string>
<string name="title_pref_vpn_interface_address">VPN 介面位址</string>
<string name="title_pref_vpn_mtu">VPN MTU (預設 1500)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_domestic_dns">境内 DNS (可选)</string>

View File

@@ -185,6 +185,7 @@
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
<string name="summary_pref_domestic_dns">DNS</string>

View File

@@ -72,6 +72,12 @@
android:summary="%s"
android:title="@string/title_pref_vpn_interface_address" />
<EditTextPreference
android:inputType="number"
android:key="pref_vpn_mtu"
android:summary="1500"
android:title="@string/title_pref_vpn_mtu" />
<CheckBoxPreference
android:key="pref_use_hev_tunnel"
android:summary="@string/summary_pref_use_hev_tunnel"

View File

@@ -1,4 +1,5 @@
import os
import sys
from .common import PostProcessor
from ..utils import (
@@ -54,8 +55,8 @@ class XAttrMetadataPP(PostProcessor):
if infoname == 'upload_date':
value = hyphenate_date(value)
elif xattrname == 'com.apple.metadata:kMDItemWhereFroms':
# NTFS ADS doesn't support colons in names
if os.name == 'nt':
# Colon in xattr name throws errors on Windows/NTFS and Linux
if sys.platform != 'darwin':
continue
value = self.APPLE_PLIST_TEMPLATE % value
write_xattr(info['filepath'], xattrname, value.encode())