feat: VLESS support packet encodings (#334)
* adjust: Do not use XTLS on H2 connections * feat: VLESS support XUDP fullcone NAT * fix: VLESS with PacketAddr does not work * fix: VLESS XUDP crash
This commit is contained in:
parent
bbe0932e19
commit
0467b2a68d
6 changed files with 87 additions and 109 deletions
|
@ -4,12 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
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"
|
||||||
|
@ -219,7 +219,9 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
Fingerprint: option.Fingerprint,
|
Fingerprint: option.Fingerprint,
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.Network != "ws" && len(option.Flow) >= 16 {
|
switch option.Network {
|
||||||
|
case "", "tcp":
|
||||||
|
if len(option.Flow) >= 16 {
|
||||||
option.Flow = option.Flow[:16]
|
option.Flow = option.Flow[:16]
|
||||||
switch option.Flow {
|
switch option.Flow {
|
||||||
case vless.XRO, vless.XRD, vless.XRS:
|
case vless.XRO, vless.XRD, vless.XRS:
|
||||||
|
@ -228,6 +230,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
|
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if option.SNI != "" {
|
if option.SNI != "" {
|
||||||
tOption.ServerName = option.SNI
|
tOption.ServerName = option.SNI
|
||||||
|
@ -273,11 +276,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
||||||
|
|
|
@ -12,6 +12,10 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
vmessSing "github.com/sagernet/sing-vmess"
|
||||||
|
"github.com/sagernet/sing-vmess/packetaddr"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/convert"
|
"github.com/Dreamacro/clash/common/convert"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
@ -49,6 +53,9 @@ type VlessOption struct {
|
||||||
FlowShow bool `proxy:"flow-show,omitempty"`
|
FlowShow bool `proxy:"flow-show,omitempty"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
|
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||||
|
XUDP bool `proxy:"xudp,omitempty"`
|
||||||
|
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
|
@ -137,11 +144,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
|
|
||||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
if v.isXTLSEnabled() {
|
|
||||||
c, err = gun.StreamGunWithXTLSConn(c, v.gunTLSConfig, v.gunConfig)
|
|
||||||
} else {
|
|
||||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
// default tcp network
|
// default tcp network
|
||||||
// handle TLS And XTLS
|
// handle TLS And XTLS
|
||||||
|
@ -152,21 +155,17 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return v.client.StreamConn(c, parseVlessAddr(metadata))
|
return v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
|
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
|
||||||
host, _, _ := net.SplitHostPort(v.addr)
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
|
|
||||||
if v.isXTLSEnabled() {
|
if v.isXTLSEnabled() && !isH2 {
|
||||||
xtlsOpts := vless.XTLSConfig{
|
xtlsOpts := vless.XTLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
FingerPrint: v.option.Fingerprint,
|
Fingerprint: v.option.Fingerprint,
|
||||||
}
|
|
||||||
|
|
||||||
if isH2 {
|
|
||||||
xtlsOpts.NextProtos = []string{"h2"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
|
@ -212,7 +211,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
|
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -259,7 +258,15 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
|
if v.option.PacketAddr {
|
||||||
|
packetAddrMetadata := *metadata // make a copy
|
||||||
|
packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress
|
||||||
|
packetAddrMetadata.DstPort = "443"
|
||||||
|
|
||||||
|
c, err = v.client.StreamConn(c, parseVlessAddr(&packetAddrMetadata, false))
|
||||||
|
} else {
|
||||||
|
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("new vless client error: %v", err)
|
return nil, fmt.Errorf("new vless client error: %v", err)
|
||||||
|
@ -289,7 +296,15 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
|
if v.option.PacketAddr {
|
||||||
|
packetAddrMetadata := *metadata // make a copy
|
||||||
|
packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress
|
||||||
|
packetAddrMetadata.DstPort = "443"
|
||||||
|
|
||||||
|
c, err = v.StreamConn(c, &packetAddrMetadata)
|
||||||
|
} else {
|
||||||
c, err = v.StreamConn(c, metadata)
|
c, err = v.StreamConn(c, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("new vless client error: %v", err)
|
return nil, fmt.Errorf("new vless client error: %v", err)
|
||||||
|
@ -305,6 +320,17 @@ func (v *Vless) SupportWithDialer() bool {
|
||||||
|
|
||||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||||
func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
|
if v.option.XUDP {
|
||||||
|
return newPacketConn(&threadSafePacketConn{
|
||||||
|
PacketConn: vmessSing.NewXUDPConn(c, M.ParseSocksaddr(metadata.RemoteAddress())),
|
||||||
|
}, v), nil
|
||||||
|
} else if v.option.PacketAddr {
|
||||||
|
return newPacketConn(&threadSafePacketConn{
|
||||||
|
PacketConn: packetaddr.NewConn(&vlessPacketConn{
|
||||||
|
Conn: c, rAddr: metadata.UDPAddr(),
|
||||||
|
}, M.ParseSocksaddr(metadata.RemoteAddress())),
|
||||||
|
}, v), nil
|
||||||
|
}
|
||||||
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,7 +339,7 @@ func (v *Vless) SupportUOT() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
|
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
||||||
var addrType byte
|
var addrType byte
|
||||||
var addr []byte
|
var addr []byte
|
||||||
switch metadata.AddrType() {
|
switch metadata.AddrType() {
|
||||||
|
@ -337,7 +363,8 @@ func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
|
||||||
UDP: metadata.NetWork == C.UDP,
|
UDP: metadata.NetWork == C.UDP,
|
||||||
AddrType: addrType,
|
AddrType: addrType,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Port: uint(port),
|
Port: uint16(port),
|
||||||
|
Mux: metadata.NetWork == C.UDP && xudp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,6 +493,16 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||||
option: &option,
|
option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch option.PacketEncoding {
|
||||||
|
case "packetaddr", "packet":
|
||||||
|
option.PacketAddr = true
|
||||||
|
case "xudp":
|
||||||
|
option.XUDP = true
|
||||||
|
}
|
||||||
|
if option.XUDP {
|
||||||
|
option.PacketAddr = false
|
||||||
|
}
|
||||||
|
|
||||||
switch option.Network {
|
switch option.Network {
|
||||||
case "h2":
|
case "h2":
|
||||||
if len(option.HTTP2Opts.Host) == 0 {
|
if len(option.HTTP2Opts.Host) == 0 {
|
||||||
|
@ -498,12 +535,8 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||||
|
|
||||||
v.gunTLSConfig = tlsConfig
|
v.gunTLSConfig = tlsConfig
|
||||||
v.gunConfig = gunConfig
|
v.gunConfig = gunConfig
|
||||||
if v.isXTLSEnabled() {
|
|
||||||
v.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig)
|
|
||||||
} else {
|
|
||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
// Modified from: https://github.com/Qv2ray/gun-lite
|
|
||||||
// License: MIT
|
|
||||||
|
|
||||||
package gun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
xtls "github.com/xtls/go"
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewHTTP2XTLSClient(dialFn DialFn, tlsConfig *tls.Config) *TransportWrap {
|
|
||||||
wrap := TransportWrap{}
|
|
||||||
dialFunc := func(network, addr string, cfg *tls.Config) (net.Conn, error) {
|
|
||||||
pconn, err := dialFn(network, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
wrap.remoteAddr = pconn.RemoteAddr()
|
|
||||||
xtlsConfig := &xtls.Config{
|
|
||||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
|
||||||
ServerName: cfg.ServerName,
|
|
||||||
}
|
|
||||||
|
|
||||||
cn := xtls.Client(pconn, xtlsConfig)
|
|
||||||
if err := cn.Handshake(); err != nil {
|
|
||||||
pconn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
state := cn.ConnectionState()
|
|
||||||
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
|
||||||
cn.Close()
|
|
||||||
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
|
||||||
}
|
|
||||||
return cn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
wrap.Transport = &http2.Transport{
|
|
||||||
DialTLS: dialFunc,
|
|
||||||
TLSClientConfig: tlsConfig,
|
|
||||||
AllowHTTP: false,
|
|
||||||
DisableCompression: true,
|
|
||||||
PingTimeout: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &wrap
|
|
||||||
}
|
|
||||||
|
|
||||||
func StreamGunWithXTLSConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config) (net.Conn, error) {
|
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
transport := NewHTTP2XTLSClient(dialFn, tlsConfig)
|
|
||||||
return StreamGunWithTransport(transport, cfg)
|
|
||||||
}
|
|
|
@ -51,7 +51,9 @@ func (vc *Conn) sendRequest() error {
|
||||||
buf.WriteByte(0) // addon data length. 0 means no addon data
|
buf.WriteByte(0) // addon data length. 0 means no addon data
|
||||||
}
|
}
|
||||||
|
|
||||||
// command
|
if vc.dst.Mux {
|
||||||
|
buf.WriteByte(CommandMux)
|
||||||
|
} else {
|
||||||
if vc.dst.UDP {
|
if vc.dst.UDP {
|
||||||
buf.WriteByte(CommandUDP)
|
buf.WriteByte(CommandUDP)
|
||||||
} else {
|
} else {
|
||||||
|
@ -59,9 +61,10 @@ func (vc *Conn) sendRequest() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port AddrType Addr
|
// Port AddrType Addr
|
||||||
binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port))
|
binary.Write(buf, binary.BigEndian, vc.dst.Port)
|
||||||
buf.WriteByte(vc.dst.AddrType)
|
buf.WriteByte(vc.dst.AddrType)
|
||||||
buf.Write(vc.dst.Addr)
|
buf.Write(vc.dst.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
_, err := vc.Conn.Write(buf.Bytes())
|
_, err := vc.Conn.Write(buf.Bytes())
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package vless
|
package vless
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ const (
|
||||||
const (
|
const (
|
||||||
CommandTCP byte = 1
|
CommandTCP byte = 1
|
||||||
CommandUDP byte = 2
|
CommandUDP byte = 2
|
||||||
|
CommandMux byte = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
// Addr types
|
// Addr types
|
||||||
|
@ -33,7 +35,8 @@ type DstAddr struct {
|
||||||
UDP bool
|
UDP bool
|
||||||
AddrType byte
|
AddrType byte
|
||||||
Addr []byte
|
Addr []byte
|
||||||
Port uint
|
Port uint16
|
||||||
|
Mux bool // currently used for XUDP only
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is vless connection generator
|
// Client is vless connection generator
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
type XTLSConfig struct {
|
type XTLSConfig struct {
|
||||||
Host string
|
Host string
|
||||||
SkipCertVerify bool
|
SkipCertVerify bool
|
||||||
FingerPrint string
|
Fingerprint string
|
||||||
NextProtos []string
|
NextProtos []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,11 +22,11 @@ func StreamXTLSConn(conn net.Conn, cfg *XTLSConfig) (net.Conn, error) {
|
||||||
InsecureSkipVerify: cfg.SkipCertVerify,
|
InsecureSkipVerify: cfg.SkipCertVerify,
|
||||||
NextProtos: cfg.NextProtos,
|
NextProtos: cfg.NextProtos,
|
||||||
}
|
}
|
||||||
if len(cfg.FingerPrint) == 0 {
|
if len(cfg.Fingerprint) == 0 {
|
||||||
xtlsConfig = tlsC.GetGlobalFingerprintXTLCConfig(xtlsConfig)
|
xtlsConfig = tlsC.GetGlobalFingerprintXTLCConfig(xtlsConfig)
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
if xtlsConfig, err = tlsC.GetSpecifiedFingerprintXTLSConfig(xtlsConfig, cfg.FingerPrint); err != nil {
|
if xtlsConfig, err = tlsC.GetSpecifiedFingerprintXTLSConfig(xtlsConfig, cfg.Fingerprint); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue