From a0bdb861a986df80024d34cc452f3bf42af6bb2d Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Tue, 12 Aug 2025 08:46:44 +0800 Subject: [PATCH] chore: rebuild vless encryption string parsing --- adapter/outbound/vless.go | 43 ++---------- listener/sing_vless/server.go | 45 ++---------- transport/vless/encryption/factory.go | 99 +++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 78 deletions(-) create mode 100644 transport/vless/encryption/factory.go diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index de3c24ab..b26ddb07 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -3,14 +3,11 @@ package outbound import ( "context" "crypto/tls" - "encoding/base64" "errors" "fmt" "net" "net/http" "strconv" - "strings" - "time" "github.com/metacubex/mihomo/common/convert" N "github.com/metacubex/mihomo/common/net" @@ -456,41 +453,11 @@ func NewVless(option VlessOption) (*Vless, error) { option: &option, } - if s := strings.SplitN(option.Encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" { - var minutes uint32 - if s[0] != "1rtt" { - t := strings.TrimSuffix(s[0], "min") - if t == s[0] { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - var i int - i, err = strconv.Atoi(t) - if err != nil { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - minutes = uint32(i) - } - var xor uint32 - switch s[1] { - case "vless": - case "aes128xor": - xor = 1 - default: - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - var b []byte - b, err = base64.RawURLEncoding.DecodeString(s[3]) - if err != nil { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } - if len(b) == encryption.MLKEM768ClientLength { - v.encryption = &encryption.ClientInstance{} - if err = v.encryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { - return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) - } - } else { - return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption) - } + v.encryption, err = encryption.NewClient(option.Encryption) + if err != nil { + return nil, err + } + if v.encryption != nil { if option.Flow != "" { return nil, errors.New(`vless "encryption" doesn't support "flow" yet`) } diff --git a/listener/sing_vless/server.go b/listener/sing_vless/server.go index 41f23f34..0e729cab 100644 --- a/listener/sing_vless/server.go +++ b/listener/sing_vless/server.go @@ -2,15 +2,11 @@ package sing_vless import ( "context" - "encoding/base64" "errors" - "fmt" "net" "net/http" "reflect" - "strconv" "strings" - "time" "unsafe" "github.com/metacubex/mihomo/adapter/inbound" @@ -88,42 +84,11 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) sl = &Listener{config: config, service: service} - if s := strings.SplitN(config.Decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" { - var minutes uint32 - if s[0] != "1rtt" { - t := strings.TrimSuffix(s[0], "min") - if t == s[0] { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - var i int - i, err = strconv.Atoi(t) - if err != nil { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - minutes = uint32(i) - } - var xor uint32 - switch s[1] { - case "vless": - case "aes128xor": - xor = 1 - default: - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - var b []byte - b, err = base64.RawURLEncoding.DecodeString(s[3]) - if err != nil { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - if len(b) == encryption.MLKEM768SeedLength { - sl.decryption = &encryption.ServerInstance{} - if err = sl.decryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { - return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) - } - } else { - return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption) - } - + sl.decryption, err = encryption.NewServer(config.Decryption) + if err != nil { + return nil, err + } + if sl.decryption != nil { defer func() { // decryption must be closed to avoid the goroutine leak if err != nil { _ = sl.decryption.Close() diff --git a/transport/vless/encryption/factory.go b/transport/vless/encryption/factory.go new file mode 100644 index 00000000..18b88c2e --- /dev/null +++ b/transport/vless/encryption/factory.go @@ -0,0 +1,99 @@ +package encryption + +import ( + "encoding/base64" + "fmt" + "strconv" + "strings" + "time" +) + +// NewClient new client from encryption string +// maybe return a nil *ClientInstance without any error, that means don't need to encrypt +func NewClient(encryption string) (*ClientInstance, error) { + switch encryption { + case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility + return nil, nil + } + if s := strings.SplitN(encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" { + var minutes uint32 + if s[0] != "1rtt" { + t := strings.TrimSuffix(s[0], "min") + if t == s[0] { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + i, err := strconv.Atoi(t) + if err != nil { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + minutes = uint32(i) + } + var xor uint32 + switch s[1] { + case "vless": + case "aes128xor": + xor = 1 + default: + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + b, err := base64.RawURLEncoding.DecodeString(s[3]) + if err != nil { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + if len(b) == MLKEM768ClientLength { + client := &ClientInstance{} + if err = client.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { + return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) + } + return client, nil + } else { + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) + } + } + return nil, fmt.Errorf("invaild vless encryption value: %s", encryption) +} + +// NewServer new server from decryption string +// maybe return a nil *ServerInstance without any error, that means don't need to decrypt +func NewServer(decryption string) (*ServerInstance, error) { + switch decryption { + case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility + return nil, nil + } + if s := strings.SplitN(decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" { + var minutes uint32 + if s[0] != "1rtt" { + t := strings.TrimSuffix(s[0], "min") + if t == s[0] { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + i, err := strconv.Atoi(t) + if err != nil { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + minutes = uint32(i) + } + var xor uint32 + switch s[1] { + case "vless": + case "aes128xor": + xor = 1 + default: + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + b, err := base64.RawURLEncoding.DecodeString(s[3]) + if err != nil { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + if len(b) == MLKEM768SeedLength { + server := &ServerInstance{} + if err = server.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil { + return nil, fmt.Errorf("failed to use mlkem768seed: %w", err) + } + return server, nil + } else { + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) + } + } + return nil, fmt.Errorf("invaild vless decryption value: %s", decryption) +}