1
0

Feature: add basic support for the VLESS protocol (#2891)

This commit is contained in:
crwnet 2023-08-24 13:24:45 +08:00 committed by GitHub
parent 651a36964e
commit 8a4c46ae77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 893 additions and 25 deletions

10
adapter/outbound/vless.go Normal file
View File

@ -0,0 +1,10 @@
package outbound
type (
Vless = Vmess
VlessOption = VmessOption
)
func NewVless(option VlessOption) (*Vless, error) {
return newVmess(option, true)
}

View File

@ -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,

View File

@ -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)

View File

@ -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:

View 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"
}
}

View 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"
}
}

View 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"
}
}

View 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"
}
}

View 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"
}
]
}

View 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"
}
]
}

View 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
View 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
View 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
View 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)
}

View File

@ -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

View File

@ -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
}