Chore: merge branch 'with-tun' into plus-pro

This commit is contained in:
yaling888 2022-03-30 00:18:42 +08:00
commit d77ef6a525
11 changed files with 397 additions and 71 deletions

View File

@ -212,21 +212,47 @@ interface Context {
``` ```
### Proxies configuration ### Proxies configuration
Support outbound transport protocol `VLESS`. Support outbound protocol `VLESS`.
The XTLS only support TCP transport by the XRAY-CORE. Support `Trojan` with XTLS.
Currently XTLS only supports TCP transport.
```yaml ```yaml
proxies: proxies:
- name: "vless-tcp" # VLESS
- name: "vless-tls"
type: vless type: vless
server: server server: server
port: 443 port: 443
uuid: uuid uuid: uuid
network: tcp network: tcp
servername: example.com servername: example.com
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS udp: true
# skip-cert-verify: true
- name: "vless-xtls"
type: vless
server: server
port: 443
uuid: uuid
network: tcp
servername: example.com
flow: xtls-rprx-direct # or xtls-rprx-origin
# flow-show: true # print the XTLS direction log
# udp: true # udp: true
# skip-cert-verify: true # skip-cert-verify: true
# Trojan
- name: "trojan-xtls"
type: trojan
server: server
port: 443
password: yourpsk
network: tcp
flow: xtls-rprx-direct # or xtls-rprx-origin
# flow-show: true # print the XTLS direction log
# udp: true
# sni: example.com # aka server name
# skip-cert-verify: true
``` ```
### IPTABLES configuration ### IPTABLES configuration

View File

@ -12,6 +12,7 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/trojan" "github.com/Dreamacro/clash/transport/trojan"
"github.com/Dreamacro/clash/transport/vless"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@ -40,6 +41,8 @@ type TrojanOption struct {
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"`
Flow string `proxy:"flow,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"`
} }
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) { func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
@ -82,6 +85,11 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
} }
c, err = t.instance.PresetXTLSConn(c)
if err != nil {
return nil, err
}
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
return c, err return c, err
} }
@ -95,6 +103,12 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
return nil, err return nil, err
} }
c, err = t.instance.PresetXTLSConn(c)
if err != nil {
c.Close()
return nil, err
}
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil { if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
c.Close() c.Close()
return nil, err return nil, err
@ -160,6 +174,17 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
ALPN: option.ALPN, ALPN: option.ALPN,
ServerName: option.Server, ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify, SkipCertVerify: option.SkipCertVerify,
FlowShow: option.FlowShow,
}
if option.Network != "ws" && len(option.Flow) >= 16 {
option.Flow = option.Flow[:16]
switch option.Flow {
case vless.XRO, vless.XRD, vless.XRS:
tOption.Flow = option.Flow
default:
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
}
} }
if option.SNI != "" { if option.SNI != "" {
@ -196,7 +221,12 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
ServerName: tOption.ServerName, ServerName: tOption.ServerName,
} }
if t.option.Flow != "" {
t.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig)
} else {
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig) t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
}
t.gunTLSConfig = tlsConfig t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{ t.gunConfig = &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName, ServiceName: option.GrpcOpts.GrpcServiceName,

View File

@ -6,9 +6,11 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"sync"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
@ -20,6 +22,11 @@ import (
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
const (
// max packet length
maxLength = 1024 << 3
)
type Vless struct { type Vless struct {
*Base *Base
client *vless.Client client *vless.Client
@ -39,7 +46,6 @@ type VlessOption struct {
UUID string `proxy:"uuid"` UUID string `proxy:"uuid"`
Flow string `proxy:"flow,omitempty"` Flow string `proxy:"flow,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"` FlowShow bool `proxy:"flow-show,omitempty"`
TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
@ -80,9 +86,9 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
wsOpts.Headers = header wsOpts.Headers = header
} }
if v.option.TLS {
wsOpts.TLS = true wsOpts.TLS = true
wsOpts.TLSConfig = &tls.Config{ wsOpts.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: host, ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify, InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"}, NextProtos: []string{"http/1.1"},
@ -92,7 +98,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
} else if host := wsOpts.Headers.Get("Host"); host != "" { } else if host := wsOpts.Headers.Get("Host"); host != "" {
wsOpts.TLSConfig.ServerName = host wsOpts.TLSConfig.ServerName = host
} }
}
c, err = vmess.StreamWebsocketConn(c, wsOpts) c, err = vmess.StreamWebsocketConn(c, wsOpts)
case "http": case "http":
// readability first, so just copy default TLS logic // readability first, so just copy default TLS logic
@ -160,7 +166,7 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
return vless.StreamXTLSConn(conn, &xtlsOpts) return vless.StreamXTLSConn(conn, &xtlsOpts)
} else if v.option.TLS { } else {
tlsOpts := vmess.TLSConfig{ tlsOpts := vmess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
@ -176,8 +182,6 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
return vmess.StreamTLSConn(conn, &tlsOpts) return vmess.StreamTLSConn(conn, &tlsOpts)
} }
return conn, nil
} }
func (v *Vless) isXTLSEnabled() bool { func (v *Vless) isXTLSEnabled() bool {
@ -284,28 +288,95 @@ type vlessPacketConn struct {
net.Conn net.Conn
rAddr net.Addr rAddr net.Addr
cache [2]byte cache [2]byte
remain int
mux sync.Mutex
} }
func (uc *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { func (vc *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
binary.BigEndian.PutUint16(uc.cache[:], uint16(len(b))) total := len(b)
_, _ = uc.Conn.Write(uc.cache[:]) if total == 0 {
return uc.Conn.Write(b) return 0, nil
} }
func (uc *vlessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { if total < maxLength {
n, err := uc.Conn.Read(uc.cache[:]) return vc.writePacket(b)
}
offset := 0
for {
cursor := offset + maxLength
if cursor > total {
cursor = total
}
n, err := vc.writePacket(b[offset:cursor])
if err != nil { if err != nil {
return n, uc.rAddr, err return offset + n, err
} }
n, err = uc.Conn.Read(b)
return n, uc.rAddr, err offset = cursor
if offset == total {
break
}
}
return total, nil
}
func (vc *vlessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
vc.mux.Lock()
defer vc.mux.Unlock()
if vc.remain != 0 {
length := len(b)
if length > vc.remain {
length = vc.remain
}
n, err := vc.Conn.Read(b[:length])
if err != nil {
return 0, vc.rAddr, err
}
vc.remain -= n
return n, vc.rAddr, nil
}
if _, err := vc.Conn.Read(b[:2]); err != nil {
return 0, vc.rAddr, err
}
total := int(binary.BigEndian.Uint16(b[:2]))
if total == 0 {
return 0, vc.rAddr, nil
}
length := len(b)
if length > total {
length = total
}
if _, err := io.ReadFull(vc.Conn, b[:length]); err != nil {
return 0, vc.rAddr, errors.New("read packet error")
}
vc.remain = total - length
return length, vc.rAddr, nil
}
func (vc *vlessPacketConn) writePacket(payload []byte) (int, error) {
binary.BigEndian.PutUint16(vc.cache[:], uint16(len(payload)))
if _, err := vc.Conn.Write(vc.cache[:]); err != nil {
return 0, err
}
return vc.Conn.Write(payload)
} }
func NewVless(option VlessOption) (*Vless, error) { func NewVless(option VlessOption) (*Vless, error) {
if !option.TLS {
return nil, fmt.Errorf("TLS must be true with vless")
}
var addons *vless.Addons var addons *vless.Addons
if option.Network != "ws" && len(option.Flow) >= 16 { if option.Network != "ws" && len(option.Flow) >= 16 {
option.Flow = option.Flow[:16] option.Flow = option.Flow[:16]
@ -315,7 +386,7 @@ func NewVless(option VlessOption) (*Vless, error) {
Flow: option.Flow, Flow: option.Flow,
} }
default: default:
return nil, fmt.Errorf("unsupported vless flow type: %s", option.Flow) return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
} }
} }

View File

@ -95,7 +95,6 @@ func (t *TUN) Write(packet []byte) (int, error) {
return t.nt.Write(packet, t.offset) return t.nt.Write(packet, t.offset)
} }
_ = t.cache[:t.offset]
packet = append(t.cache[:t.offset], packet...) packet = append(t.cache[:t.offset], packet...)
return t.nt.Write(packet, t.offset) return t.nt.Write(packet, t.offset)

View File

@ -0,0 +1,39 @@
{
"inbounds": [
{
"port": 10002,
"listen": "0.0.0.0",
"protocol": "trojan",
"settings": {
"clients": [
{
"password": "example",
"email": "xtls@example.com",
"flow": "xtls-rprx-direct",
"level": 0
}
]
},
"streamSettings": {
"network": "tcp",
"security": "xtls",
"xtlsSettings": {
"certificates": [
{
"certificateFile": "/etc/ssl/v2ray/fullchain.pem",
"keyFile": "/etc/ssl/v2ray/privkey.pem"
}
]
}
}
}
],
"outbounds": [
{
"protocol": "freedom"
}
],
"log": {
"loglevel": "debug"
}
}

35
test/config/vless-ws.json Normal file
View File

@ -0,0 +1,35 @@
{
"inbounds": [
{
"port": 10002,
"listen": "0.0.0.0",
"protocol": "vless",
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"level": 0,
"email": "ws@example.com"
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"certificates": [
{
"certificateFile": "/etc/ssl/v2ray/fullchain.pem",
"keyFile": "/etc/ssl/v2ray/privkey.pem"
}
]
}
}
}
],
"outbounds": [
{
"protocol": "freedom"
}
]
}

View File

@ -8,9 +8,9 @@
"clients": [ "clients": [
{ {
"id": "b831381d-6324-4d53-ad4f-8cda48b30811", "id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"email": "xtls@example.com",
"flow": "xtls-rprx-direct", "flow": "xtls-rprx-direct",
"level": 0, "level": 0
"email": "love@example.com"
} }
], ],
"decryption": "none" "decryption": "none"

View File

@ -131,6 +131,46 @@ func TestClash_TrojanWebsocket(t *testing.T) {
testSuit(t, proxy) testSuit(t, proxy)
} }
func TestClash_TrojanXTLS(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("trojan-xtls.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, "trojan-xtls")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
proxy, err := outbound.NewTrojan(outbound.TrojanOption{
Name: "trojan",
Server: localIP.String(),
Port: 10002,
Password: "example",
SNI: "example.org",
SkipCertVerify: true,
UDP: true,
Network: "tcp",
Flow: "xtls-rprx-direct",
FlowShow: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime)
testSuit(t, proxy)
}
func Benchmark_Trojan(b *testing.B) { func Benchmark_Trojan(b *testing.B) {
cfg := &container.Config{ cfg := &container.Config{
Image: ImageTrojan, Image: ImageTrojan,

View File

@ -37,7 +37,6 @@ func TestClash_VlessTLS(t *testing.T) {
Server: localIP.String(), Server: localIP.String(),
Port: 10002, Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
TLS: true,
SkipCertVerify: true, SkipCertVerify: true,
ServerName: "example.org", ServerName: "example.org",
UDP: true, UDP: true,
@ -75,7 +74,6 @@ func TestClash_VlessXTLS(t *testing.T) {
Server: localIP.String(), Server: localIP.String(),
Port: 10002, Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
TLS: true,
SkipCertVerify: true, SkipCertVerify: true,
ServerName: "example.org", ServerName: "example.org",
UDP: true, UDP: true,
@ -89,3 +87,41 @@ func TestClash_VlessXTLS(t *testing.T) {
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
} }
func TestClash_VlessWS(t *testing.T) {
cfg := &container.Config{
Image: ImageVmess,
ExposedPorts: defaultExposedPorts,
}
hostCfg := &container.HostConfig{
PortBindings: defaultPortBindings,
Binds: []string{
fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vless-ws.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")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
proxy, err := outbound.NewVless(outbound.VlessOption{
Name: "vless",
Server: localIP.String(),
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
SkipCertVerify: true,
ServerName: "example.org",
Network: "ws",
UDP: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime)
testSuit(t, proxy)
}

View File

@ -7,6 +7,7 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
@ -15,7 +16,10 @@ import (
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/vless"
"github.com/Dreamacro/clash/transport/vmess" "github.com/Dreamacro/clash/transport/vmess"
xtls "github.com/xtls/go"
) )
const ( const (
@ -32,9 +36,13 @@ var (
type Command = byte type Command = byte
var ( const (
CommandTCP byte = 1 CommandTCP byte = 1
CommandUDP byte = 3 CommandUDP byte = 3
// for XTLS
commandXRD byte = 0xf0 // XTLS direct mode
commandXRO byte = 0xf1 // XTLS origin mode
) )
type Option struct { type Option struct {
@ -42,6 +50,8 @@ type Option struct {
ALPN []string ALPN []string
ServerName string ServerName string
SkipCertVerify bool SkipCertVerify bool
Flow string
FlowShow bool
} }
type WebsocketOption struct { type WebsocketOption struct {
@ -62,6 +72,24 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
alpn = t.option.ALPN alpn = t.option.ALPN
} }
if t.option.Flow != "" {
xtlsConfig := &xtls.Config{
NextProtos: alpn,
MinVersion: xtls.VersionTLS12,
InsecureSkipVerify: t.option.SkipCertVerify,
ServerName: t.option.ServerName,
}
xtlsConn := xtls.Client(conn, xtlsConfig)
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
if err := xtlsConn.HandshakeContext(ctx); err != nil {
return nil, err
}
return xtlsConn, nil
} else {
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
NextProtos: alpn, NextProtos: alpn,
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
@ -80,6 +108,7 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
return tlsConn, nil return tlsConn, nil
} }
}
func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption) (net.Conn, error) { func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption) (net.Conn, error) {
alpn := defaultWebsocketALPN alpn := defaultWebsocketALPN
@ -104,7 +133,37 @@ func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption)
}) })
} }
func (t *Trojan) PresetXTLSConn(conn net.Conn) (net.Conn, error) {
switch t.option.Flow {
case vless.XRO, vless.XRD, vless.XRS:
if xtlsConn, ok := conn.(*xtls.Conn); ok {
xtlsConn.RPRX = true
xtlsConn.SHOW = t.option.FlowShow
xtlsConn.MARK = "XTLS"
if t.option.Flow == vless.XRS {
t.option.Flow = vless.XRD
}
if t.option.Flow == vless.XRD {
xtlsConn.DirectMode = true
}
} else {
return nil, fmt.Errorf("failed to use %s, maybe \"security\" is not \"xtls\"", t.option.Flow)
}
}
return conn, nil
}
func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error { func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error {
if command == CommandTCP {
if t.option.Flow == vless.XRD {
command = commandXRD
} else if t.option.Flow == vless.XRO {
command = commandXRO
}
}
buf := pool.GetBuffer() buf := pool.GetBuffer()
defer pool.PutBuffer(buf) defer pool.PutBuffer(buf)

View File

@ -35,15 +35,6 @@ type DstAddr struct {
Port uint Port uint
} }
// Config of vless
type Config struct {
UUID string
AlterID uint16
Security string
Port string
HostName string
}
// Client is vless connection generator // Client is vless connection generator
type Client struct { type Client struct {
uuid *uuid.UUID uuid *uuid.UUID