Chore: merge branch 'with-tun' into plus-pro
This commit is contained in:
commit
d77ef6a525
34
README.md
34
README.md
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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) {
|
|
||||||
n, err := uc.Conn.Read(uc.cache[:])
|
|
||||||
if err != nil {
|
|
||||||
return n, uc.rAddr, err
|
|
||||||
}
|
}
|
||||||
n, err = uc.Conn.Read(b)
|
|
||||||
return n, uc.rAddr, err
|
if total < maxLength {
|
||||||
|
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 {
|
||||||
|
return offset + n, 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
39
test/config/trojan-xtls.json
Normal file
39
test/config/trojan-xtls.json
Normal 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
35
test/config/vless-ws.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -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"
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
@ -79,6 +107,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) {
|
||||||
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user