Skip to content

Commit 1b0c72b

Browse files
committed
feat: support vless encryption
1 parent e89af72 commit 1b0c72b

File tree

14 files changed

+726
-10
lines changed

14 files changed

+726
-10
lines changed

adapter/outbound/vless.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ package outbound
33
import (
44
"context"
55
"crypto/tls"
6+
"encoding/base64"
7+
"errors"
68
"fmt"
79
"net"
810
"net/http"
911
"strconv"
12+
"strings"
13+
"time"
1014

1115
"github.com/metacubex/mihomo/common/convert"
1216
N "github.com/metacubex/mihomo/common/net"
@@ -19,6 +23,7 @@ import (
1923
C "github.com/metacubex/mihomo/constant"
2024
"github.com/metacubex/mihomo/transport/gun"
2125
"github.com/metacubex/mihomo/transport/vless"
26+
"github.com/metacubex/mihomo/transport/vless/encryption"
2227
"github.com/metacubex/mihomo/transport/vmess"
2328

2429
vmessSing "github.com/metacubex/sing-vmess"
@@ -31,6 +36,8 @@ type Vless struct {
3136
client *vless.Client
3237
option *VlessOption
3338

39+
encryption *encryption.ClientInstance
40+
3441
// for gun mux
3542
gunTLSConfig *tls.Config
3643
gunConfig *gun.Config
@@ -53,6 +60,7 @@ type VlessOption struct {
5360
PacketAddr bool `proxy:"packet-addr,omitempty"`
5461
XUDP bool `proxy:"xudp,omitempty"`
5562
PacketEncoding string `proxy:"packet-encoding,omitempty"`
63+
Encryption string `proxy:"encryption,omitempty"`
5664
Network string `proxy:"network,omitempty"`
5765
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
5866
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
@@ -164,6 +172,12 @@ func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.M
164172
done := N.SetupContextForConn(ctx, c)
165173
defer done(&err)
166174
}
175+
if v.encryption != nil {
176+
c, err = v.encryption.Handshake(c)
177+
if err != nil {
178+
return
179+
}
180+
}
167181
if metadata.NetWork == C.UDP {
168182
if v.option.PacketAddr {
169183
metadata = &C.Metadata{
@@ -442,6 +456,36 @@ func NewVless(option VlessOption) (*Vless, error) {
442456
option: &option,
443457
}
444458

459+
if s := strings.Split(option.Encryption, "-mlkem768client-"); len(s) == 2 {
460+
var minutes uint32
461+
if s[0] != "1rtt" {
462+
t := strings.TrimSuffix(s[0], "min")
463+
if t == s[0] {
464+
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
465+
}
466+
i, err := strconv.Atoi(t)
467+
if err != nil {
468+
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
469+
}
470+
minutes = uint32(i)
471+
}
472+
b, err := base64.RawURLEncoding.DecodeString(s[1])
473+
if err != nil {
474+
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
475+
}
476+
if len(b) == 1184 {
477+
v.encryption = &encryption.ClientInstance{}
478+
if err := v.encryption.Init(b, time.Duration(minutes)*time.Minute); err != nil {
479+
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
480+
}
481+
} else {
482+
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
483+
}
484+
if option.Flow != "" {
485+
return nil, errors.New(`VLESS users: "encryption" doesn't support "flow" yet`)
486+
}
487+
}
488+
445489
v.realityConfig, err = v.option.RealityOpts.Parse()
446490
if err != nil {
447491
return nil, err

component/generater/cmd.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import (
55
"fmt"
66

77
"github.com/metacubex/mihomo/component/ech"
8+
"github.com/metacubex/mihomo/transport/vless/encryption"
89

910
"github.com/gofrs/uuid/v5"
1011
)
1112

1213
func Main(args []string) {
1314
if len(args) < 1 {
14-
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair")
15+
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768")
1516
}
1617
switch args[0] {
1718
case "uuid":
@@ -45,5 +46,16 @@ func Main(args []string) {
4546
}
4647
fmt.Println("Config:", configBase64)
4748
fmt.Println("Key:", keyPem)
49+
case "vless-mlkem768":
50+
var seed string
51+
if len(args) > 1 {
52+
seed = args[1]
53+
}
54+
seedBase64, pubBase64, err := encryption.GenMLKEM768(seed)
55+
if err != nil {
56+
panic(err)
57+
}
58+
fmt.Println("Seed: " + seedBase64)
59+
fmt.Println("Client: " + pubBase64)
4860
}
4961
}

docs/config.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,16 @@ proxies: # socks5
632632
# fingerprint: xxxx
633633
# skip-cert-verify: true
634634

635+
- name: "vless-encryption"
636+
type: vless
637+
server: server
638+
port: 443
639+
uuid: uuid
640+
network: tcp
641+
encryption: "8min-mlkem768client-bas64RawURLEncoding" # 复用八分钟后协商新的 sharedKey,需小于服务端的值
642+
tls: false #可以不开启tls
643+
udp: true
644+
635645
- name: "vless-reality-vision"
636646
type: vless
637647
server: server
@@ -1336,6 +1346,7 @@ listeners:
13361346
flow: xtls-rprx-vision
13371347
# ws-path: "/" # 如果不为空则开启 websocket 传输层
13381348
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
1349+
# decryption: "10min-mlkem768seed-bas64RawURLEncoding" # 同时允许 1-RTT 模式与十分钟复用的 0-RTT 模式
13391350
# 下面两项如果填写则开启 tls(需要同时填写)
13401351
# certificate: ./server.crt
13411352
# private-key: ./server.key
@@ -1364,7 +1375,7 @@ listeners:
13641375
after-bytes: 0 # 传输指定字节后开始限速
13651376
bytes-per-sec: 0 # 基准速率(字节/秒)
13661377
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
1367-
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ###
1378+
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “decryption” 的其中一项 ###
13681379

13691380
- name: anytls-in-1
13701381
type: anytls

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ require (
3535
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
3636
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
3737
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4
38-
github.com/metacubex/utls v1.8.0
38+
github.com/metacubex/utls v1.8.1-0.20250810142204-d0e55ab2e852
3939
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
4040
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
4141
github.com/mroth/weightedrand/v2 v2.1.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nU
141141
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
142142
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
143143
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
144+
github.com/metacubex/utls v1.8.1-0.20250810142204-d0e55ab2e852 h1:MLHUGmASNH7/AeoGmSrVM2RutRZAqIDSbQWBp0P7ItE=
145+
github.com/metacubex/utls v1.8.1-0.20250810142204-d0e55ab2e852/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
144146
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
145147
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
146148
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=

listener/config/vless.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type VlessServer struct {
1717
Enable bool
1818
Listen string
1919
Users []VlessUser
20+
Decryption string
2021
WsPath string
2122
GrpcServiceName string
2223
Certificate string

listener/inbound/vless.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
type VlessOption struct {
1313
BaseOption
1414
Users []VlessUser `inbound:"users"`
15+
Decryption string `inbound:"decryption,omitempty"`
1516
WsPath string `inbound:"ws-path,omitempty"`
1617
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
1718
Certificate string `inbound:"certificate,omitempty"`
@@ -58,6 +59,7 @@ func NewVless(options *VlessOption) (*Vless, error) {
5859
Enable: true,
5960
Listen: base.RawAddress(),
6061
Users: users,
62+
Decryption: options.Decryption,
6163
WsPath: options.WsPath,
6264
GrpcServiceName: options.GrpcServiceName,
6365
Certificate: options.Certificate,

listener/inbound/vless_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/metacubex/mihomo/adapter/outbound"
99
"github.com/metacubex/mihomo/listener/inbound"
10+
"github.com/metacubex/mihomo/transport/vless/encryption"
1011
"github.com/stretchr/testify/assert"
1112
)
1213

@@ -87,6 +88,21 @@ func TestInboundVless_TLS(t *testing.T) {
8788
})
8889
}
8990

91+
func TestInboundVless_Encryption(t *testing.T) {
92+
seedBase64, pubBase64, err := encryption.GenMLKEM768("")
93+
if err != nil {
94+
t.Fatal(err)
95+
return
96+
}
97+
inboundOptions := inbound.VlessOption{
98+
Decryption: "10min-mlkem768seed-" + seedBase64,
99+
}
100+
outboundOptions := outbound.VlessOption{
101+
Encryption: "8min-mlkem768client-" + pubBase64,
102+
}
103+
testInboundVless(t, inboundOptions, outboundOptions)
104+
}
105+
90106
func TestInboundVless_Wss1(t *testing.T) {
91107
inboundOptions := inbound.VlessOption{
92108
Certificate: tlsCertificate,

listener/sing_vless/server.go

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package sing_vless
22

33
import (
44
"context"
5+
"encoding/base64"
56
"errors"
7+
"fmt"
68
"net"
79
"net/http"
810
"reflect"
11+
"strconv"
912
"strings"
13+
"time"
1014
"unsafe"
1115

1216
"github.com/metacubex/mihomo/adapter/inbound"
@@ -19,6 +23,7 @@ import (
1923
"github.com/metacubex/mihomo/listener/sing"
2024
"github.com/metacubex/mihomo/log"
2125
"github.com/metacubex/mihomo/transport/gun"
26+
"github.com/metacubex/mihomo/transport/vless/encryption"
2227
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
2328

2429
"github.com/metacubex/sing-vmess/vless"
@@ -45,10 +50,11 @@ func init() {
4550
}
4651

4752
type Listener struct {
48-
closed bool
49-
config LC.VlessServer
50-
listeners []net.Listener
51-
service *vless.Service[string]
53+
closed bool
54+
config LC.VlessServer
55+
listeners []net.Listener
56+
service *vless.Service[string]
57+
decryption *encryption.ServerInstance
5258
}
5359

5460
func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
@@ -80,7 +86,34 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
8086
return it.Flow
8187
}))
8288

83-
sl = &Listener{false, config, nil, service}
89+
sl = &Listener{config: config, service: service}
90+
91+
if s := strings.Split(config.Decryption, "-mlkem768seed-"); len(s) == 2 {
92+
var minutes uint32
93+
if s[0] != "1rtt" {
94+
t := strings.TrimSuffix(s[0], "min")
95+
if t == s[0] {
96+
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
97+
}
98+
i, err := strconv.Atoi(t)
99+
if err != nil {
100+
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
101+
}
102+
minutes = uint32(i)
103+
}
104+
b, err := base64.RawURLEncoding.DecodeString(s[1])
105+
if err != nil {
106+
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
107+
}
108+
if len(b) == 64 {
109+
sl.decryption = &encryption.ServerInstance{}
110+
if err = sl.decryption.Init(b, time.Duration(minutes)*time.Minute); err != nil {
111+
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
112+
}
113+
} else {
114+
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
115+
}
116+
}
84117

85118
tlsConfig := &tlsC.Config{}
86119
var realityBuilder *reality.Builder
@@ -149,8 +182,8 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
149182
} else {
150183
l = tlsC.NewListener(l, tlsConfig)
151184
}
152-
} else {
153-
return nil, errors.New("disallow using Vless without both certificates/reality config")
185+
} else if sl.decryption == nil {
186+
return nil, errors.New("disallow using Vless without any certificates/reality/decryption config")
154187
}
155188
sl.listeners = append(sl.listeners, l)
156189

@@ -201,6 +234,13 @@ func (l *Listener) AddrList() (addrList []net.Addr) {
201234

202235
func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
203236
ctx := sing.WithAdditions(context.TODO(), additions...)
237+
if l.decryption != nil {
238+
var err error
239+
conn, err = l.decryption.Handshake(conn)
240+
if err != nil {
241+
return
242+
}
243+
}
204244
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
205245
Protocol: "vless",
206246
Source: metadata.SocksaddrFromNet(conn.RemoteAddr()),

0 commit comments

Comments
 (0)