Feature: add basic support for the VLESS protocol (#2891)
This commit is contained in:
parent
651a36964e
commit
8a4c46ae77
10
adapter/outbound/vless.go
Normal file
10
adapter/outbound/vless.go
Normal file
@ -0,0 +1,10 @@
|
||||
package outbound
|
||||
|
||||
type (
|
||||
Vless = Vmess
|
||||
VlessOption = VmessOption
|
||||
)
|
||||
|
||||
func NewVless(option VlessOption) (*Vless, error) {
|
||||
return newVmess(option, true)
|
||||
}
|
@ -39,8 +39,8 @@ type VmessOption struct {
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
UUID string `proxy:"uuid"`
|
||||
AlterID int `proxy:"alterId"`
|
||||
Cipher string `proxy:"cipher"`
|
||||
AlterID int `proxy:"alterId,omitempty"`
|
||||
Cipher string `proxy:"cipher,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
@ -264,7 +264,14 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
}
|
||||
|
||||
func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
return newVmess(option, false)
|
||||
}
|
||||
|
||||
func newVmess(option VmessOption, isVless bool) (*Vmess, error) {
|
||||
security := strings.ToLower(option.Cipher)
|
||||
if security == "" {
|
||||
security = "auto"
|
||||
}
|
||||
client, err := vmess.NewClient(vmess.Config{
|
||||
UUID: option.UUID,
|
||||
AlterID: uint16(option.AlterID),
|
||||
@ -272,6 +279,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
HostName: option.Server,
|
||||
Port: strconv.Itoa(option.Port),
|
||||
IsAead: option.AlterID == 0,
|
||||
IsVless: isVless,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -284,11 +292,16 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
}
|
||||
}
|
||||
|
||||
tp := C.Vmess
|
||||
if isVless {
|
||||
tp = C.Vless
|
||||
}
|
||||
|
||||
v := &Vmess{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Vmess,
|
||||
tp: tp,
|
||||
udp: option.UDP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
|
@ -48,6 +48,8 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
break
|
||||
}
|
||||
proxy = outbound.NewHttp(*httpOption)
|
||||
case "vless":
|
||||
fallthrough
|
||||
case "vmess":
|
||||
vmessOption := &outbound.VmessOption{
|
||||
HTTPOpts: outbound.HTTPOptions{
|
||||
@ -59,7 +61,11 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewVmess(*vmessOption)
|
||||
if proxyType == "vless" {
|
||||
proxy, err = outbound.NewVless(*vmessOption)
|
||||
} else {
|
||||
proxy, err = outbound.NewVmess(*vmessOption)
|
||||
}
|
||||
case "snell":
|
||||
snellOption := &outbound.SnellOption{}
|
||||
err = decoder.Decode(mapping, snellOption)
|
||||
|
@ -19,6 +19,7 @@ const (
|
||||
Snell
|
||||
Socks5
|
||||
Http
|
||||
Vless
|
||||
Vmess
|
||||
Trojan
|
||||
|
||||
@ -140,6 +141,8 @@ func (at AdapterType) String() string {
|
||||
return "Socks5"
|
||||
case Http:
|
||||
return "Http"
|
||||
case Vless:
|
||||
return "Vless"
|
||||
case Vmess:
|
||||
return "Vmess"
|
||||
case Trojan:
|
||||
|
40
test/config/vless-grpc.json
Normal file
40
test/config/vless-grpc.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 10002,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "grpc",
|
||||
"security": "tls",
|
||||
"tlsSettings": {
|
||||
"certificates": [
|
||||
{
|
||||
"certificateFile": "/etc/ssl/v2ray/fullchain.pem",
|
||||
"keyFile": "/etc/ssl/v2ray/privkey.pem"
|
||||
}
|
||||
]
|
||||
},
|
||||
"grpcSettings": {
|
||||
"serviceName": "example!"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
],
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
}
|
||||
}
|
55
test/config/vless-http.json
Normal file
55
test/config/vless-http.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 10002,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "tcp",
|
||||
"tcpSettings": {
|
||||
"header": {
|
||||
"type": "http",
|
||||
"response": {
|
||||
"version": "1.1",
|
||||
"status": "200",
|
||||
"reason": "OK",
|
||||
"headers": {
|
||||
"Content-Type": [
|
||||
"application/octet-stream",
|
||||
"video/mpeg",
|
||||
"application/x-msdownload",
|
||||
"text/html",
|
||||
"application/x-shockwave-flash"
|
||||
],
|
||||
"Transfer-Encoding": [
|
||||
"chunked"
|
||||
],
|
||||
"Connection": [
|
||||
"keep-alive"
|
||||
],
|
||||
"Pragma": "no-cache"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": "none"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
],
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
}
|
||||
}
|
43
test/config/vless-http2.json
Normal file
43
test/config/vless-http2.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 10002,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "http",
|
||||
"security": "tls",
|
||||
"tlsSettings": {
|
||||
"certificates": [
|
||||
{
|
||||
"certificateFile": "/etc/ssl/v2ray/fullchain.pem",
|
||||
"keyFile": "/etc/ssl/v2ray/privkey.pem"
|
||||
}
|
||||
]
|
||||
},
|
||||
"httpSettings": {
|
||||
"host": [
|
||||
"example.org"
|
||||
],
|
||||
"path": "/test"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
],
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
}
|
||||
}
|
37
test/config/vless-tls.json
Normal file
37
test/config/vless-tls.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 10002,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "tcp",
|
||||
"security": "tls",
|
||||
"tlsSettings": {
|
||||
"certificates": [
|
||||
{
|
||||
"certificateFile": "/etc/ssl/v2ray/fullchain.pem",
|
||||
"keyFile": "/etc/ssl/v2ray/privkey.pem"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
],
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
}
|
||||
}
|
30
test/config/vless-ws-0rtt.json
Normal file
30
test/config/vless-ws-0rtt.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 10002,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "ws",
|
||||
"security": "none",
|
||||
"wsSettings": {
|
||||
"maxEarlyData": 128,
|
||||
"earlyDataHeaderName": "Sec-WebSocket-Protocol"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
]
|
||||
}
|
34
test/config/vless-ws-tls-zero.json
Normal file
34
test/config/vless-ws-tls-zero.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 10002,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "ws",
|
||||
"security": "tls",
|
||||
"tlsSettings": {
|
||||
"certificates": [
|
||||
{
|
||||
"certificateFile": "/etc/ssl/v2ray/fullchain.pem",
|
||||
"keyFile": "/etc/ssl/v2ray/privkey.pem"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
]
|
||||
}
|
34
test/config/vless-ws-tls.json
Normal file
34
test/config/vless-ws-tls.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 10002,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "ws",
|
||||
"security": "tls",
|
||||
"tlsSettings": {
|
||||
"certificates": [
|
||||
{
|
||||
"certificateFile": "/etc/ssl/v2ray/fullchain.pem",
|
||||
"keyFile": "/etc/ssl/v2ray/privkey.pem"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
]
|
||||
}
|
26
test/config/vless-ws.json
Normal file
26
test/config/vless-ws.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 10002,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "ws",
|
||||
"security": "none"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
]
|
||||
}
|
28
test/config/vless.json
Normal file
28
test/config/vless.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 10002,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "tcp"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
],
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
}
|
||||
}
|
452
test/vless_test.go
Normal file
452
test/vless_test.go
Normal file
@ -0,0 +1,452 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
func TestClash_Vless(t *testing.T) {
|
||||
configPath := C.Path.Resolve("vless.json")
|
||||
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
Entrypoint: []string{"/usr/bin/v2ray"},
|
||||
Cmd: []string{"run", "-c", "/etc/v2ray/config.json"},
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vless")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||
Name: "vless",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
UDP: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VlessTLS(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
Entrypoint: []string{"/usr/bin/v2ray"},
|
||||
Cmd: []string{"run", "-c", "/etc/v2ray/config.json"},
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{
|
||||
fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vless-tls.json")),
|
||||
fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")),
|
||||
fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")),
|
||||
},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vless-tls")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||
Name: "vless",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
TLS: true,
|
||||
SkipCertVerify: true,
|
||||
ServerName: "example.org",
|
||||
UDP: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VlessHTTP2(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
Entrypoint: []string{"/usr/bin/v2ray"},
|
||||
Cmd: []string{"run", "-c", "/etc/v2ray/config.json"},
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{
|
||||
fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vless-http2.json")),
|
||||
fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")),
|
||||
fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")),
|
||||
},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vless-http2")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||
Name: "vless",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
Network: "h2",
|
||||
TLS: true,
|
||||
SkipCertVerify: true,
|
||||
ServerName: "example.org",
|
||||
UDP: true,
|
||||
HTTP2Opts: outbound.HTTP2Options{
|
||||
Host: []string{"example.org"},
|
||||
Path: "/test",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VlessHTTP(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
Entrypoint: []string{"/usr/bin/v2ray"},
|
||||
Cmd: []string{"run", "-c", "/etc/v2ray/config.json"},
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{
|
||||
fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vless-http.json")),
|
||||
},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vless-http")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||
Name: "vless",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
Network: "http",
|
||||
UDP: true,
|
||||
HTTPOpts: outbound.HTTPOptions{
|
||||
Method: "GET",
|
||||
Path: []string{"/"},
|
||||
Headers: map[string][]string{
|
||||
"Host": {"www.amazon.com"},
|
||||
"User-Agent": {
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36 Edg/84.0.522.49",
|
||||
},
|
||||
"Accept-Encoding": {
|
||||
"gzip, deflate",
|
||||
},
|
||||
"Connection": {
|
||||
"keep-alive",
|
||||
},
|
||||
"Pragma": {"no-cache"},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VlessWebsocket(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
Entrypoint: []string{"/usr/bin/v2ray"},
|
||||
Cmd: []string{"run", "-c", "/etc/v2ray/config.json"},
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{
|
||||
fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vless-ws.json")),
|
||||
},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vless-ws")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||
Name: "vless",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
Network: "ws",
|
||||
UDP: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VlessWebsocketTLS(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
Entrypoint: []string{"/usr/bin/v2ray"},
|
||||
Cmd: []string{"run", "-c", "/etc/v2ray/config.json"},
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{
|
||||
fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vless-ws-tls.json")),
|
||||
fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")),
|
||||
fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")),
|
||||
},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vless-ws-tls")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||
Name: "vless",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
Network: "ws",
|
||||
TLS: true,
|
||||
SkipCertVerify: true,
|
||||
UDP: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VlessWebsocketTLSZero(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
Entrypoint: []string{"/usr/bin/v2ray"},
|
||||
Cmd: []string{"run", "-c", "/etc/v2ray/config.json"},
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{
|
||||
fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vless-ws-tls-zero.json")),
|
||||
fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")),
|
||||
fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")),
|
||||
},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vless-ws-tls-zero")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||
Name: "vless",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "zero",
|
||||
Network: "ws",
|
||||
TLS: true,
|
||||
SkipCertVerify: true,
|
||||
UDP: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VlessGrpc(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
Entrypoint: []string{"/usr/bin/v2ray"},
|
||||
Cmd: []string{"run", "-c", "/etc/v2ray/config.json"},
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{
|
||||
fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vless-grpc.json")),
|
||||
fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")),
|
||||
fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")),
|
||||
},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vless-grpc")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||
Name: "vless",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
Network: "grpc",
|
||||
TLS: true,
|
||||
SkipCertVerify: true,
|
||||
UDP: true,
|
||||
ServerName: "example.org",
|
||||
GrpcOpts: outbound.GrpcOptions{
|
||||
GrpcServiceName: "example!",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VlessWebsocket0RTT(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
Entrypoint: []string{"/usr/bin/v2ray"},
|
||||
Cmd: []string{"run", "-c", "/etc/v2ray/config.json"},
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{
|
||||
fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vless-ws-0rtt.json")),
|
||||
},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vless-ws-0rtt")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||
Name: "vless",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
Network: "ws",
|
||||
UDP: true,
|
||||
ServerName: "example.org",
|
||||
WSOpts: outbound.WSOptions{
|
||||
MaxEarlyData: 2048,
|
||||
EarlyDataHeaderName: "Sec-WebSocket-Protocol",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VlessWebsocketXray0RTT(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
Image: ImageXray,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{
|
||||
fmt.Sprintf("%s:/etc/xray/config.json", C.Path.Resolve("vless-ws-0rtt.json")),
|
||||
},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vless-xray-ws-0rtt")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||
Name: "vless",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
Network: "ws",
|
||||
UDP: true,
|
||||
ServerName: "example.org",
|
||||
WSOpts: outbound.WSOptions{
|
||||
Path: "/?ed=2048",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func Benchmark_Vless(b *testing.B) {
|
||||
configPath := C.Path.Resolve("vless.json")
|
||||
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
Entrypoint: []string{"/usr/bin/v2ray"},
|
||||
Cmd: []string{"run", "-c", "/etc/v2ray/config.json"},
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vless-bench")
|
||||
require.NoError(b, err)
|
||||
|
||||
b.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||
Name: "vless",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
AlterID: 0,
|
||||
UDP: true,
|
||||
})
|
||||
require.NoError(b, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
benchmarkProxy(b, proxy)
|
||||
}
|
@ -34,6 +34,7 @@ type Conn struct {
|
||||
security byte
|
||||
option byte
|
||||
isAead bool
|
||||
isVless bool
|
||||
|
||||
received bool
|
||||
}
|
||||
@ -55,6 +56,29 @@ func (vc *Conn) Read(b []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (vc *Conn) sendRequest() error {
|
||||
if vc.isVless {
|
||||
buf := protobytes.BytesWriter{}
|
||||
buf.PutUint8(0) // Protocol Version
|
||||
buf.PutSlice(vc.id.UUID.Bytes()) // UUID
|
||||
buf.PutUint8(0) // Addons Length
|
||||
// buf.PutString("") // Addons Data
|
||||
|
||||
// Command
|
||||
if vc.dst.UDP {
|
||||
buf.PutUint8(CommandUDP)
|
||||
} else {
|
||||
buf.PutUint8(CommandTCP)
|
||||
}
|
||||
|
||||
// Port AddrType Addr
|
||||
buf.PutUint16be(uint16(vc.dst.Port))
|
||||
buf.PutUint8(vc.dst.AddrType)
|
||||
buf.PutSlice(vc.dst.Addr)
|
||||
|
||||
_, err := vc.Conn.Write(buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
timestamp := time.Now()
|
||||
|
||||
mbuf := protobytes.BytesWriter{}
|
||||
@ -119,6 +143,24 @@ func (vc *Conn) sendRequest() error {
|
||||
}
|
||||
|
||||
func (vc *Conn) recvResponse() error {
|
||||
if vc.isVless {
|
||||
var buffer [2]byte
|
||||
if _, err := io.ReadFull(vc.Conn, buffer[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if buffer[0] != 0 {
|
||||
return errors.New("unexpected response version")
|
||||
}
|
||||
|
||||
length := int64(buffer[1])
|
||||
if length != 0 { // addon data length > 0
|
||||
io.CopyN(io.Discard, vc.Conn, length) // just discard
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
if !vc.isAead {
|
||||
block, err := aes.NewCipher(vc.respBodyKey[:])
|
||||
@ -194,31 +236,37 @@ func hashTimestamp(t time.Time) []byte {
|
||||
}
|
||||
|
||||
// newConn return a Conn instance
|
||||
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security, isAead bool) (*Conn, error) {
|
||||
randBytes := make([]byte, 33)
|
||||
rand.Read(randBytes)
|
||||
reqBodyIV := make([]byte, 16)
|
||||
reqBodyKey := make([]byte, 16)
|
||||
copy(reqBodyIV[:], randBytes[:16])
|
||||
copy(reqBodyKey[:], randBytes[16:32])
|
||||
respV := randBytes[32]
|
||||
option := OptionChunkStream
|
||||
|
||||
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security, isAead bool, isVless bool) (*Conn, error) {
|
||||
var (
|
||||
reqBodyKey []byte
|
||||
reqBodyIV []byte
|
||||
respBodyKey []byte
|
||||
respBodyIV []byte
|
||||
respV byte
|
||||
option byte
|
||||
)
|
||||
|
||||
if isAead {
|
||||
bodyKey := sha256.Sum256(reqBodyKey)
|
||||
bodyIV := sha256.Sum256(reqBodyIV)
|
||||
respBodyKey = bodyKey[:16]
|
||||
respBodyIV = bodyIV[:16]
|
||||
} else {
|
||||
bodyKey := md5.Sum(reqBodyKey)
|
||||
bodyIV := md5.Sum(reqBodyIV)
|
||||
respBodyKey = bodyKey[:]
|
||||
respBodyIV = bodyIV[:]
|
||||
if !isVless {
|
||||
randBytes := make([]byte, 33)
|
||||
rand.Read(randBytes)
|
||||
reqBodyIV = make([]byte, 16)
|
||||
reqBodyKey = make([]byte, 16)
|
||||
copy(reqBodyIV[:], randBytes[:16])
|
||||
copy(reqBodyKey[:], randBytes[16:32])
|
||||
respV = randBytes[32]
|
||||
option = OptionChunkStream
|
||||
|
||||
if isAead {
|
||||
bodyKey := sha256.Sum256(reqBodyKey)
|
||||
bodyIV := sha256.Sum256(reqBodyIV)
|
||||
respBodyKey = bodyKey[:16]
|
||||
respBodyIV = bodyIV[:16]
|
||||
} else {
|
||||
bodyKey := md5.Sum(reqBodyKey)
|
||||
bodyIV := md5.Sum(reqBodyIV)
|
||||
respBodyKey = bodyKey[:]
|
||||
respBodyIV = bodyIV[:]
|
||||
}
|
||||
}
|
||||
|
||||
var writer io.Writer
|
||||
@ -276,6 +324,7 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security, isAead bool
|
||||
security: security,
|
||||
option: option,
|
||||
isAead: isAead,
|
||||
isVless: isVless,
|
||||
}
|
||||
if err := c.sendRequest(); err != nil {
|
||||
return nil, err
|
||||
|
@ -56,6 +56,7 @@ type Client struct {
|
||||
uuid *uuid.UUID
|
||||
security Security
|
||||
isAead bool
|
||||
isVless bool
|
||||
}
|
||||
|
||||
// Config of vmess
|
||||
@ -66,12 +67,13 @@ type Config struct {
|
||||
Port string
|
||||
HostName string
|
||||
IsAead bool
|
||||
IsVless bool
|
||||
}
|
||||
|
||||
// StreamConn return a Conn with net.Conn and DstAddr
|
||||
func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
|
||||
r := rand.Intn(len(c.user))
|
||||
return newConn(conn, c.user[r], dst, c.security, c.isAead)
|
||||
return newConn(conn, c.user[r], dst, c.security, c.isAead, c.isVless)
|
||||
}
|
||||
|
||||
// NewClient return Client instance
|
||||
@ -81,6 +83,11 @@ func NewClient(config Config) (*Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.IsVless {
|
||||
config.AlterID = 0
|
||||
config.Security = "zero"
|
||||
}
|
||||
|
||||
var security Security
|
||||
switch config.Security {
|
||||
case "aes-128-gcm":
|
||||
@ -105,5 +112,6 @@ func NewClient(config Config) (*Client, error) {
|
||||
uuid: &uid,
|
||||
security: security,
|
||||
isAead: config.IsAead,
|
||||
isVless: config.IsVless,
|
||||
}, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user