Compare commits
12 Commits
dev-vision
...
dialer
Author | SHA1 | Date | |
---|---|---|---|
7468d08e4d | |||
72ab200faf | |||
8df44a7c3d | |||
cdd91f5132 | |||
05f877a324 | |||
f565edd76d | |||
a3b8c9c233 | |||
de92bc0234 | |||
e6377eac9b | |||
22726c1de8 | |||
bce3aeb218 | |||
81722610d5 |
@ -171,7 +171,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
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() && !isH2 {
|
if v.isLegacyXTLSEnabled() && !isH2 {
|
||||||
xtlsOpts := vless.XTLSConfig{
|
xtlsOpts := vless.XTLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
@ -206,8 +206,8 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vless) isXTLSEnabled() bool {
|
func (v *Vless) isLegacyXTLSEnabled() bool {
|
||||||
return v.client.Addons != nil
|
return v.client.Addons != nil && v.client.Addons.Flow != vless.XRV
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
@ -479,7 +479,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
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]
|
||||||
switch option.Flow {
|
switch option.Flow {
|
||||||
case vless.XRO, vless.XRD, vless.XRS:
|
case vless.XRO, vless.XRD, vless.XRS, vless.XRV:
|
||||||
addons = &vless.Addons{
|
addons = &vless.Addons{
|
||||||
Flow: option.Flow,
|
Flow: option.Flow,
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,15 @@ import (
|
|||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const BufferSize = buf.BufferSize
|
||||||
|
|
||||||
type Buffer = buf.Buffer
|
type Buffer = buf.Buffer
|
||||||
|
|
||||||
|
var New = buf.New
|
||||||
|
var StackNew = buf.StackNew
|
||||||
var StackNewSize = buf.StackNewSize
|
var StackNewSize = buf.StackNewSize
|
||||||
|
var With = buf.With
|
||||||
|
|
||||||
var KeepAlive = common.KeepAlive
|
var KeepAlive = common.KeepAlive
|
||||||
|
|
||||||
//go:norace
|
//go:norace
|
||||||
|
@ -27,6 +27,10 @@ func (c *BufferedConn) Reader() *bufio.Reader {
|
|||||||
return c.r
|
return c.r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *BufferedConn) ResetPeeked() {
|
||||||
|
c.peeked = false
|
||||||
|
}
|
||||||
|
|
||||||
func (c *BufferedConn) Peeked() bool {
|
func (c *BufferedConn) Peeked() bool {
|
||||||
return c.peeked
|
return c.peeked
|
||||||
}
|
}
|
||||||
|
@ -8,20 +8,19 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dialMux sync.Mutex
|
dialMux sync.Mutex
|
||||||
actualSingleDialContext = singleDialContext
|
actualSingleStackDialContext = serialSingleStackDialContext
|
||||||
actualDualStackDialContext = dualStackDialContext
|
actualDualStackDialContext = serialDualStackDialContext
|
||||||
tcpConcurrent = false
|
tcpConcurrent = false
|
||||||
DisableIPv6 = false
|
|
||||||
ErrorInvalidedNetworkStack = errors.New("invalided network stack")
|
ErrorInvalidedNetworkStack = errors.New("invalided network stack")
|
||||||
ErrorDisableIPv6 = errors.New("IPv6 is disabled, dialer cancel")
|
ErrorConnTimeout = errors.New("connect timeout")
|
||||||
|
fallbackTimeout = 300 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
func applyOptions(options ...Option) *option {
|
func applyOptions(options ...Option) *option {
|
||||||
@ -56,7 +55,7 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
|||||||
|
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp4", "tcp6", "udp4", "udp6":
|
case "tcp4", "tcp6", "udp4", "udp6":
|
||||||
return actualSingleDialContext(ctx, network, address, opt)
|
return actualSingleStackDialContext(ctx, network, address, opt)
|
||||||
case "tcp", "udp":
|
case "tcp", "udp":
|
||||||
return actualDualStackDialContext(ctx, network, address, opt)
|
return actualDualStackDialContext(ctx, network, address, opt)
|
||||||
default:
|
default:
|
||||||
@ -89,11 +88,11 @@ func SetDial(concurrent bool) {
|
|||||||
dialMux.Lock()
|
dialMux.Lock()
|
||||||
tcpConcurrent = concurrent
|
tcpConcurrent = concurrent
|
||||||
if concurrent {
|
if concurrent {
|
||||||
actualSingleDialContext = concurrentSingleDialContext
|
actualSingleStackDialContext = concurrentSingleStackDialContext
|
||||||
actualDualStackDialContext = concurrentDualStackDialContext
|
actualDualStackDialContext = concurrentDualStackDialContext
|
||||||
} else {
|
} else {
|
||||||
actualSingleDialContext = singleDialContext
|
actualSingleStackDialContext = serialSingleStackDialContext
|
||||||
actualDualStackDialContext = dualStackDialContext
|
actualDualStackDialContext = serialDualStackDialContext
|
||||||
}
|
}
|
||||||
|
|
||||||
dialMux.Unlock()
|
dialMux.Unlock()
|
||||||
@ -114,10 +113,6 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
|||||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
if DisableIPv6 && destination.Is6() {
|
|
||||||
return nil, ErrorDisableIPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
address := net.JoinHostPort(destination.String(), port)
|
address := net.JoinHostPort(destination.String(), port)
|
||||||
if opt.tfo {
|
if opt.tfo {
|
||||||
return dialTFO(ctx, *dialer, network, address)
|
return dialTFO(ctx, *dialer, network, address)
|
||||||
@ -125,289 +120,57 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
|||||||
return dialer.DialContext(ctx, network, address)
|
return dialer.DialContext(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func singleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
|
func serialSingleStackDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
|
||||||
host, port, err := net.SplitHostPort(address)
|
ips, port, err := parseAddr(ctx, network, address, opt.resolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return serialDialContext(ctx, network, ips, port, opt)
|
||||||
var ip netip.Addr
|
|
||||||
switch network {
|
|
||||||
case "tcp4", "udp4":
|
|
||||||
if opt.resolver == nil {
|
|
||||||
ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, opt.resolver)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if opt.resolver == nil {
|
|
||||||
ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, opt.resolver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("dns resolve failed:%w", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dialContext(ctx, network, ip, port, opt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
func serialDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||||
host, port, err := net.SplitHostPort(address)
|
ips, port, err := parseAddr(ctx, network, address, opt.resolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return dualStackDialContext(
|
||||||
returned := make(chan struct{})
|
ctx,
|
||||||
defer close(returned)
|
func(ctx context.Context) (net.Conn, error) { return serialDialContext(ctx, network, ips, port, opt) },
|
||||||
|
func(ctx context.Context) (net.Conn, error) { return serialDialContext(ctx, network, ips, port, opt) },
|
||||||
type dialResult struct {
|
opt.prefer == 4)
|
||||||
net.Conn
|
|
||||||
error
|
|
||||||
resolved bool
|
|
||||||
ipv6 bool
|
|
||||||
done bool
|
|
||||||
}
|
|
||||||
results := make(chan dialResult)
|
|
||||||
var primary, fallback dialResult
|
|
||||||
|
|
||||||
startRacer := func(ctx context.Context, network, host string, r resolver.Resolver, ipv6 bool) {
|
|
||||||
result := dialResult{ipv6: ipv6, done: true}
|
|
||||||
defer func() {
|
|
||||||
select {
|
|
||||||
case results <- result:
|
|
||||||
case <-returned:
|
|
||||||
if result.Conn != nil {
|
|
||||||
_ = result.Conn.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var ip netip.Addr
|
|
||||||
if ipv6 {
|
|
||||||
if r == nil {
|
|
||||||
ip, result.error = resolver.ResolveIPv6ProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ip, result.error = resolver.ResolveIPv6WithResolver(ctx, host, r)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if r == nil {
|
|
||||||
ip, result.error = resolver.ResolveIPv4ProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ip, result.error = resolver.ResolveIPv4WithResolver(ctx, host, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if result.error != nil {
|
|
||||||
result.error = fmt.Errorf("dns resolve failed:%w", result.error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result.resolved = true
|
|
||||||
|
|
||||||
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
go startRacer(ctx, network+"4", host, opt.resolver, false)
|
|
||||||
go startRacer(ctx, network+"6", host, opt.resolver, true)
|
|
||||||
|
|
||||||
count := 2
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
select {
|
|
||||||
case res := <-results:
|
|
||||||
if res.error == nil {
|
|
||||||
return res.Conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !res.ipv6 {
|
|
||||||
primary = res
|
|
||||||
} else {
|
|
||||||
fallback = res
|
|
||||||
}
|
|
||||||
|
|
||||||
if primary.done && fallback.done {
|
|
||||||
if primary.resolved {
|
|
||||||
return nil, primary.error
|
|
||||||
} else if fallback.resolved {
|
|
||||||
return nil, fallback.error
|
|
||||||
} else {
|
|
||||||
return nil, primary.error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
err = ctx.Err()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
err = fmt.Errorf("dual stack dial failed")
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("dual stack dial failed:%w", err)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func concurrentSingleStackDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
|
||||||
returned := make(chan struct{})
|
ips, port, err := parseAddr(ctx, network, address, opt.resolver)
|
||||||
defer close(returned)
|
|
||||||
|
|
||||||
type dialResult struct {
|
|
||||||
ip netip.Addr
|
|
||||||
net.Conn
|
|
||||||
error
|
|
||||||
isPrimary bool
|
|
||||||
done bool
|
|
||||||
}
|
|
||||||
|
|
||||||
preferCount := atomic.NewInt32(0)
|
|
||||||
results := make(chan dialResult)
|
|
||||||
tcpRacer := func(ctx context.Context, ip netip.Addr) {
|
|
||||||
result := dialResult{ip: ip, done: true}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
select {
|
|
||||||
case results <- result:
|
|
||||||
case <-returned:
|
|
||||||
if result.Conn != nil {
|
|
||||||
_ = result.Conn.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if strings.Contains(network, "tcp") {
|
|
||||||
network = "tcp"
|
|
||||||
} else {
|
|
||||||
network = "udp"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip.Is6() {
|
|
||||||
network += "6"
|
|
||||||
if opt.prefer != 4 {
|
|
||||||
result.isPrimary = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip.Is4() {
|
|
||||||
network += "4"
|
|
||||||
if opt.prefer != 6 {
|
|
||||||
result.isPrimary = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.isPrimary {
|
|
||||||
preferCount.Add(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ip := range ips {
|
|
||||||
go tcpRacer(ctx, ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
connCount := len(ips)
|
|
||||||
var fallback dialResult
|
|
||||||
var primaryError error
|
|
||||||
var finalError error
|
|
||||||
for i := 0; i < connCount; i++ {
|
|
||||||
select {
|
|
||||||
case res := <-results:
|
|
||||||
if res.error == nil {
|
|
||||||
if res.isPrimary {
|
|
||||||
return res.Conn, nil
|
|
||||||
} else {
|
|
||||||
if !fallback.done || fallback.error != nil {
|
|
||||||
fallback = res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if res.isPrimary {
|
|
||||||
primaryError = res.error
|
|
||||||
preferCount.Add(-1)
|
|
||||||
if preferCount.Load() == 0 && fallback.done && fallback.error == nil {
|
|
||||||
return fallback.Conn, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
if fallback.done && fallback.error == nil {
|
|
||||||
return fallback.Conn, nil
|
|
||||||
}
|
|
||||||
finalError = ctx.Err()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fallback.done && fallback.error == nil {
|
|
||||||
return fallback.Conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if primaryError != nil {
|
|
||||||
return nil, primaryError
|
|
||||||
}
|
|
||||||
|
|
||||||
if fallback.error != nil {
|
|
||||||
return nil, fallback.error
|
|
||||||
}
|
|
||||||
|
|
||||||
if finalError == nil {
|
|
||||||
finalError = fmt.Errorf("all ips %v tcp shake hands failed", ips)
|
|
||||||
} else {
|
|
||||||
finalError = fmt.Errorf("concurrent dial failed:%w", finalError)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, finalError
|
|
||||||
}
|
|
||||||
|
|
||||||
func concurrentSingleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
|
|
||||||
host, port, err := net.SplitHostPort(address)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ips []netip.Addr
|
if conn, err := parallelDialContext(ctx, network, ips, port, opt); err != nil {
|
||||||
switch network {
|
|
||||||
case "tcp4", "udp4":
|
|
||||||
if opt.resolver == nil {
|
|
||||||
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ips, err = resolver.LookupIPv4WithResolver(ctx, host, opt.resolver)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if opt.resolver == nil {
|
|
||||||
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ips, err = resolver.LookupIPv6WithResolver(ctx, host, opt.resolver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("dns resolve failed:%w", err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return concurrentDialContext(ctx, network, ips, port, opt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||||
host, port, err := net.SplitHostPort(address)
|
ips, port, err := parseAddr(ctx, network, address, opt.resolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if opt.prefer != 4 && opt.prefer != 6 {
|
||||||
var ips []netip.Addr
|
return parallelDialContext(ctx, network, ips, port, opt)
|
||||||
if opt.resolver != nil {
|
|
||||||
ips, err = resolver.LookupIPWithResolver(ctx, host, opt.resolver)
|
|
||||||
} else {
|
|
||||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
|
||||||
}
|
}
|
||||||
|
ipv4s, ipv6s := sortationAddr(ips)
|
||||||
if err != nil {
|
return dualStackDialContext(
|
||||||
err = fmt.Errorf("dns resolve failed:%w", err)
|
ctx,
|
||||||
return nil, err
|
func(ctx context.Context) (net.Conn, error) {
|
||||||
}
|
return parallelDialContext(ctx, network, ipv4s, port, opt)
|
||||||
|
},
|
||||||
return concurrentDialContext(ctx, network, ips, port, opt)
|
func(ctx context.Context) (net.Conn, error) {
|
||||||
|
return parallelDialContext(ctx, network, ipv6s, port, opt)
|
||||||
|
},
|
||||||
|
opt.prefer == 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Dialer struct {
|
type Dialer struct {
|
||||||
@ -426,3 +189,162 @@ func NewDialer(options ...Option) Dialer {
|
|||||||
opt := applyOptions(options...)
|
opt := applyOptions(options...)
|
||||||
return Dialer{Opt: *opt}
|
return Dialer{Opt: *opt}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dualStackDialContext(
|
||||||
|
ctx context.Context,
|
||||||
|
ipv4DialFn func(ctx context.Context) (net.Conn, error),
|
||||||
|
ipv6DialFn func(ctx context.Context) (net.Conn, error),
|
||||||
|
preferIPv4 bool) (net.Conn, error) {
|
||||||
|
fallbackTicker := time.NewTicker(fallbackTimeout)
|
||||||
|
defer fallbackTicker.Stop()
|
||||||
|
results := make(chan dialResult)
|
||||||
|
returned := make(chan struct{})
|
||||||
|
defer close(returned)
|
||||||
|
racer := func(dial func(ctx context.Context) (net.Conn, error), isPrimary bool) {
|
||||||
|
result := dialResult{isPrimary: isPrimary}
|
||||||
|
defer func() {
|
||||||
|
select {
|
||||||
|
case results <- result:
|
||||||
|
case <-returned:
|
||||||
|
if result.Conn != nil {
|
||||||
|
_ = result.Conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
result.Conn, result.error = dial(ctx)
|
||||||
|
}
|
||||||
|
go racer(ipv4DialFn, preferIPv4)
|
||||||
|
go racer(ipv6DialFn, !preferIPv4)
|
||||||
|
var fallback dialResult
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if fallback.error == nil && fallback.Conn != nil {
|
||||||
|
return fallback.Conn, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("dual stack connect failed: %w", err)
|
||||||
|
case <-fallbackTicker.C:
|
||||||
|
if fallback.error == nil && fallback.Conn != nil {
|
||||||
|
return fallback.Conn, nil
|
||||||
|
}
|
||||||
|
case res := <-results:
|
||||||
|
if res.error == nil {
|
||||||
|
if res.isPrimary {
|
||||||
|
return res.Conn, nil
|
||||||
|
}
|
||||||
|
fallback = res
|
||||||
|
}
|
||||||
|
err = res.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||||
|
results := make(chan dialResult)
|
||||||
|
returned := make(chan struct{})
|
||||||
|
defer close(returned)
|
||||||
|
tcpRacer := func(ctx context.Context, ip netip.Addr, port string) {
|
||||||
|
result := dialResult{isPrimary: true}
|
||||||
|
defer func() {
|
||||||
|
select {
|
||||||
|
case results <- result:
|
||||||
|
case <-returned:
|
||||||
|
if result.Conn != nil {
|
||||||
|
_ = result.Conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
result.ip = ip
|
||||||
|
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
go tcpRacer(ctx, ip, port)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
return nil, ErrorConnTimeout
|
||||||
|
}
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case res := <-results:
|
||||||
|
if res.error == nil {
|
||||||
|
return res.Conn, nil
|
||||||
|
}
|
||||||
|
err = res.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||||
|
var (
|
||||||
|
conn net.Conn
|
||||||
|
err error
|
||||||
|
errs []error
|
||||||
|
)
|
||||||
|
for _, ip := range ips {
|
||||||
|
if conn, err = dialContext(ctx, network, ip, port, opt); err == nil {
|
||||||
|
return conn, nil
|
||||||
|
} else {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type dialResult struct {
|
||||||
|
ip netip.Addr
|
||||||
|
net.Conn
|
||||||
|
error
|
||||||
|
isPrimary bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAddr(ctx context.Context, network, address string, preferResolver resolver.Resolver) ([]netip.Addr, string, error) {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "-1", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ips []netip.Addr
|
||||||
|
switch network {
|
||||||
|
case "tcp4", "udp4":
|
||||||
|
if preferResolver == nil {
|
||||||
|
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
|
||||||
|
} else {
|
||||||
|
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
|
||||||
|
}
|
||||||
|
case "tcp6", "udp6":
|
||||||
|
if preferResolver == nil {
|
||||||
|
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
|
||||||
|
} else {
|
||||||
|
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if preferResolver == nil {
|
||||||
|
ips, err = resolver.LookupIP(ctx, host)
|
||||||
|
} else {
|
||||||
|
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)
|
||||||
|
}
|
||||||
|
return ips, port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
|
||||||
|
for _, v := range ips {
|
||||||
|
if v.Is4() || v.Is4In6() {
|
||||||
|
ipv4s = append(ipv4s, v)
|
||||||
|
} else {
|
||||||
|
ipv6s = append(ipv6s, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -11,31 +11,30 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
CN "github.com/Dreamacro/clash/common/net"
|
|
||||||
|
|
||||||
xtls "github.com/xtls/go"
|
xtls "github.com/xtls/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tlsCertificates = make([]tls.Certificate, 0)
|
var trustCert,_ = x509.SystemCertPool()
|
||||||
|
|
||||||
var mutex sync.RWMutex
|
var mutex sync.RWMutex
|
||||||
var errNotMacth error = errors.New("certificate fingerprints do not match")
|
var errNotMacth error = errors.New("certificate fingerprints do not match")
|
||||||
|
|
||||||
func AddCertificate(privateKey, certificate string) error {
|
func AddCertificate(certificate string) error {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
if cert, err := CN.ParseCert(certificate, privateKey); err != nil {
|
if certificate == "" {
|
||||||
return err
|
return fmt.Errorf("certificate is empty")
|
||||||
} else {
|
}
|
||||||
tlsCertificates = append(tlsCertificates, cert)
|
if ok := trustCert.AppendCertsFromPEM([]byte(certificate)); !ok {
|
||||||
|
return fmt.Errorf("add certificate failed")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCertificates() []tls.Certificate {
|
func ResetCertificate(){
|
||||||
mutex.RLock()
|
mutex.Lock()
|
||||||
defer mutex.RUnlock()
|
defer mutex.Unlock()
|
||||||
return tlsCertificates
|
trustCert,_=x509.SystemCertPool()
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
@ -87,10 +86,10 @@ func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string)
|
|||||||
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
|
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
Certificates: tlsCertificates,
|
RootCAs: trustCert,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCertificates...)
|
tlsConfig.RootCAs = trustCert
|
||||||
return tlsConfig
|
return tlsConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,29 +106,12 @@ func GetSpecifiedFingerprintXTLSConfig(tlsConfig *xtls.Config, fingerprint strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config {
|
func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config {
|
||||||
xtlsCerts := make([]xtls.Certificate, len(tlsCertificates))
|
|
||||||
for _, cert := range tlsCertificates {
|
|
||||||
tlsSsaList := make([]xtls.SignatureScheme, len(cert.SupportedSignatureAlgorithms))
|
|
||||||
for _, ssa := range cert.SupportedSignatureAlgorithms {
|
|
||||||
tlsSsa := xtls.SignatureScheme(ssa)
|
|
||||||
tlsSsaList = append(tlsSsaList, tlsSsa)
|
|
||||||
}
|
|
||||||
xtlsCert := xtls.Certificate{
|
|
||||||
Certificate: cert.Certificate,
|
|
||||||
PrivateKey: cert.PrivateKey,
|
|
||||||
OCSPStaple: cert.OCSPStaple,
|
|
||||||
SignedCertificateTimestamps: cert.SignedCertificateTimestamps,
|
|
||||||
Leaf: cert.Leaf,
|
|
||||||
SupportedSignatureAlgorithms: tlsSsaList,
|
|
||||||
}
|
|
||||||
xtlsCerts = append(xtlsCerts, xtlsCert)
|
|
||||||
}
|
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
return &xtls.Config{
|
return &xtls.Config{
|
||||||
Certificates: xtlsCerts,
|
RootCAs: trustCert,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig.Certificates = xtlsCerts
|
tlsConfig.RootCAs = trustCert
|
||||||
return tlsConfig
|
return tlsConfig
|
||||||
}
|
}
|
||||||
|
@ -120,13 +120,9 @@ type Profile struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TLS struct {
|
type TLS struct {
|
||||||
RawCert `yaml:",inline"`
|
|
||||||
CustomTrustCert []RawCert `yaml:"custom-certifactes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RawCert struct {
|
|
||||||
Certificate string `yaml:"certificate"`
|
Certificate string `yaml:"certificate"`
|
||||||
PrivateKey string `yaml:"private-key"`
|
PrivateKey string `yaml:"private-key"`
|
||||||
|
CustomTrustCert []string `yaml:"custom-certifactes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPTables config
|
// IPTables config
|
||||||
@ -447,6 +443,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
}
|
}
|
||||||
config.General = general
|
config.General = general
|
||||||
|
|
||||||
|
dialer.DefaultInterface.Store(config.General.Interface)
|
||||||
proxies, providers, err := parseProxies(rawCfg)
|
proxies, providers, err := parseProxies(rawCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
280
docs/config.yaml
280
docs/config.yaml
@ -7,22 +7,17 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口
|
|||||||
# tproxy-port: 7893
|
# tproxy-port: 7893
|
||||||
|
|
||||||
allow-lan: true # 允许局域网连接
|
allow-lan: true # 允许局域网连接
|
||||||
bind-address: "*" # 绑定IP地址,仅作用于 allow-lan 为 true,'*'表示所有地址
|
bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true,'*'表示所有地址
|
||||||
|
|
||||||
# find-process-mode has 3 values: always, strict, off
|
# find-process-mode has 3 values:always, strict, off
|
||||||
# - always, 开启,强制匹配所有进程
|
# - always, 开启,强制匹配所有进程
|
||||||
# - strict, 默认,由clash判断是否开启
|
# - strict, 默认,由 clash 判断是否开启
|
||||||
# - off, 不匹配进程,推荐在路由器上使用此模式
|
# - off, 不匹配进程,推荐在路由器上使用此模式
|
||||||
find-process-mode: strict
|
find-process-mode: strict
|
||||||
|
|
||||||
# global-client-fingerprint:全局TLS指纹,优先低于proxy内的 client-fingerprint
|
|
||||||
# accepts "chrome","firefox","safari","ios","random","none" options.
|
|
||||||
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
|
|
||||||
global-client-fingerprint: chrome
|
|
||||||
|
|
||||||
mode: rule
|
mode: rule
|
||||||
|
|
||||||
#自定义 geox-url
|
#自定义 geodata url
|
||||||
geox-url:
|
geox-url:
|
||||||
geoip: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
|
geoip: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
|
||||||
geosite: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
|
geosite: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
|
||||||
@ -32,16 +27,30 @@ log-level: debug # 日志等级 silent/error/warning/info/debug
|
|||||||
|
|
||||||
ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录
|
ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录
|
||||||
|
|
||||||
|
tls:
|
||||||
|
certificate: string # 证书 PEM 格式,或者 证书的路径
|
||||||
|
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||||
|
custom-certifactes:
|
||||||
|
- |
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
format/pem...
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
external-controller: 0.0.0.0:9093 # RESTful API 监听地址
|
external-controller: 0.0.0.0:9093 # RESTful API 监听地址
|
||||||
external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件
|
external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件
|
||||||
# secret: "123456" # `Authorization: Bearer ${secret}`
|
# secret: "123456" # `Authorization:Bearer ${secret}`
|
||||||
|
|
||||||
# tcp-concurrent: true # TCP并发连接所有IP, 将使用最快握手的TCP
|
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
|
||||||
external-ui: /path/to/ui/folder # 配置WEB UI目录,使用http://{{external-controller}}/ui 访问
|
external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
|
||||||
|
|
||||||
# interface-name: en0 # 设置出口网卡
|
# interface-name: en0 # 设置出口网卡
|
||||||
|
|
||||||
# routing-mark: 6666 # 配置 fwmark 仅用于Linux
|
# 全局 TLS 指纹,优先低于 proxy 内的 client-fingerprint
|
||||||
|
# 可选: "chrome","firefox","safari","ios","random","none" options.
|
||||||
|
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
|
||||||
|
global-client-fingerprint: chrome
|
||||||
|
|
||||||
|
# routing-mark:6666 # 配置 fwmark 仅用于 Linux
|
||||||
experimental:
|
experimental:
|
||||||
|
|
||||||
# 类似于 /etc/hosts, 仅支持配置单个 IP
|
# 类似于 /etc/hosts, 仅支持配置单个 IP
|
||||||
@ -50,6 +59,13 @@ hosts:
|
|||||||
# '.dev': 127.0.0.1
|
# '.dev': 127.0.0.1
|
||||||
# 'alpha.clash.dev': '::1'
|
# 'alpha.clash.dev': '::1'
|
||||||
|
|
||||||
|
profile:
|
||||||
|
# 存储 select 选择记录
|
||||||
|
store-selected: false
|
||||||
|
|
||||||
|
# 持久化 fake-ip
|
||||||
|
store-fake-ip: true
|
||||||
|
|
||||||
# Tun 配置
|
# Tun 配置
|
||||||
tun:
|
tun:
|
||||||
enable: false
|
enable: false
|
||||||
@ -105,14 +121,12 @@ sniffer:
|
|||||||
# 是否使用嗅探结果作为实际访问,默认 true
|
# 是否使用嗅探结果作为实际访问,默认 true
|
||||||
# 全局配置,优先级低于 sniffer.sniff 实际配置
|
# 全局配置,优先级低于 sniffer.sniff 实际配置
|
||||||
override-destination: false
|
override-destination: false
|
||||||
sniff:
|
sniff: # TLS 默认如果不配置 ports 默认嗅探 443
|
||||||
# TLS 默认如果不配置 ports 默认嗅探 443
|
|
||||||
TLS:
|
TLS:
|
||||||
# ports: [443, 8443]
|
# ports: [443, 8443]
|
||||||
|
|
||||||
# 默认嗅探 80
|
# 默认嗅探 80
|
||||||
HTTP:
|
HTTP: # 需要嗅探的端口
|
||||||
# 需要嗅探的端口
|
|
||||||
|
|
||||||
ports: [80, 8080-8880]
|
ports: [80, 8080-8880]
|
||||||
# 可覆盖 sniffer.override-destination
|
# 可覆盖 sniffer.override-destination
|
||||||
@ -136,27 +150,8 @@ sniffer:
|
|||||||
- "443"
|
- "443"
|
||||||
# - 8000-9999
|
# - 8000-9999
|
||||||
|
|
||||||
# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
|
||||||
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
|
|
||||||
# vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345
|
|
||||||
|
|
||||||
# tuic服务器入口(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
tunnels: # one line config
|
||||||
#tuic-server:
|
|
||||||
# enable: true
|
|
||||||
# listen: 127.0.0.1:10443
|
|
||||||
# token:
|
|
||||||
# - TOKEN
|
|
||||||
# certificate: ./server.crt
|
|
||||||
# private-key: ./server.key
|
|
||||||
# congestion-controller: bbr
|
|
||||||
# max-idle-time: 15000
|
|
||||||
# authentication-timeout: 1000
|
|
||||||
# alpn:
|
|
||||||
# - h3
|
|
||||||
# max-udp-relay-packet-size: 1500
|
|
||||||
|
|
||||||
tunnels:
|
|
||||||
# one line config
|
|
||||||
- tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy
|
- tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy
|
||||||
- tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn
|
- tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn
|
||||||
# full yaml config
|
# full yaml config
|
||||||
@ -165,12 +160,6 @@ tunnels:
|
|||||||
target: target.com
|
target: target.com
|
||||||
proxy: proxy
|
proxy: proxy
|
||||||
|
|
||||||
profile:
|
|
||||||
# 存储select选择记录
|
|
||||||
store-selected: false
|
|
||||||
|
|
||||||
# 持久化fake-ip
|
|
||||||
store-fake-ip: true
|
|
||||||
|
|
||||||
# DNS配置
|
# DNS配置
|
||||||
dns:
|
dns:
|
||||||
@ -240,12 +229,51 @@ dns:
|
|||||||
# - '+.youtube.com'
|
# - '+.youtube.com'
|
||||||
|
|
||||||
# 配置查询域名使用的 DNS 服务器
|
# 配置查询域名使用的 DNS 服务器
|
||||||
nameserver-policy:
|
nameserver-policy: # 'www.baidu.com': '114.114.114.114'
|
||||||
# 'www.baidu.com': '114.114.114.114'
|
|
||||||
# '+.internal.crop.com': '10.0.0.1'
|
# '+.internal.crop.com': '10.0.0.1'
|
||||||
"geosite:cn": "https://doh.pub/dns-query"
|
"geosite:cn":
|
||||||
"www.baidu.com": [https://doh.pub/dns-query,https://dns.alidns.com/dns-query]
|
- https://doh.pub/dns-query
|
||||||
proxies:
|
- https://dns.alidns.com/dns-query
|
||||||
|
"www.baidu.com": [https://doh.pub/dns-query, https://dns.alidns.com/dns-query]
|
||||||
|
|
||||||
|
proxies: # socks5
|
||||||
|
- name: "socks"
|
||||||
|
type: socks5
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
# username: username
|
||||||
|
# password: password
|
||||||
|
# tls: true
|
||||||
|
# fingerprint: xxxx
|
||||||
|
# skip-cert-verify: true
|
||||||
|
# udp: true
|
||||||
|
# ip-version: ipv6
|
||||||
|
|
||||||
|
# http
|
||||||
|
- name: "http"
|
||||||
|
type: http
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
# username: username
|
||||||
|
# password: password
|
||||||
|
# tls: true # https
|
||||||
|
# skip-cert-verify: true
|
||||||
|
# sni: custom.com
|
||||||
|
# fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints
|
||||||
|
# ip-version: dual
|
||||||
|
|
||||||
|
# Snell
|
||||||
|
# Beware that there's currently no UDP support yet
|
||||||
|
- name: "snell"
|
||||||
|
type: snell
|
||||||
|
server: server
|
||||||
|
port: 44046
|
||||||
|
psk: yourpsk
|
||||||
|
# version: 2
|
||||||
|
# obfs-opts:
|
||||||
|
# mode: http # or tls
|
||||||
|
# host: bing.com
|
||||||
|
|
||||||
# Shadowsocks
|
# Shadowsocks
|
||||||
# cipher支持:
|
# cipher支持:
|
||||||
# aes-128-gcm aes-192-gcm aes-256-gcm
|
# aes-128-gcm aes-192-gcm aes-256-gcm
|
||||||
@ -268,6 +296,7 @@ proxies:
|
|||||||
# UDP 则为双栈解析,获取结果中的第一个 IPv4
|
# UDP 则为双栈解析,获取结果中的第一个 IPv4
|
||||||
# ipv6-prefer 同 ipv4-prefer
|
# ipv6-prefer 同 ipv4-prefer
|
||||||
# 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效
|
# 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效
|
||||||
|
|
||||||
- name: "ss2"
|
- name: "ss2"
|
||||||
type: ss
|
type: ss
|
||||||
server: server
|
server: server
|
||||||
@ -309,6 +338,7 @@ proxies:
|
|||||||
plugin-opts:
|
plugin-opts:
|
||||||
host: "cloud.tencent.com"
|
host: "cloud.tencent.com"
|
||||||
password: "shadow_tls_password"
|
password: "shadow_tls_password"
|
||||||
|
version: 2 # support 1/2/3
|
||||||
|
|
||||||
# vmess
|
# vmess
|
||||||
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none
|
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none
|
||||||
@ -359,13 +389,13 @@ proxies:
|
|||||||
# udp: true
|
# udp: true
|
||||||
# network: http
|
# network: http
|
||||||
# http-opts:
|
# http-opts:
|
||||||
# # method: "GET"
|
# method: "GET"
|
||||||
# # path:
|
# path:
|
||||||
# # - '/'
|
# - '/'
|
||||||
# # - '/video'
|
# - '/video'
|
||||||
# # headers:
|
# headers:
|
||||||
# # Connection:
|
# Connection:
|
||||||
# # - keep-alive
|
# - keep-alive
|
||||||
# ip-version: ipv4 # 设置使用 IP 类型偏好,可选:ipv4,ipv6,dual,默认值:dual
|
# ip-version: ipv4 # 设置使用 IP 类型偏好,可选:ipv4,ipv6,dual,默认值:dual
|
||||||
|
|
||||||
- name: vmess-grpc
|
- name: vmess-grpc
|
||||||
@ -384,43 +414,49 @@ proxies:
|
|||||||
grpc-service-name: "example"
|
grpc-service-name: "example"
|
||||||
# ip-version: ipv4
|
# ip-version: ipv4
|
||||||
|
|
||||||
# socks5
|
# vless
|
||||||
- name: "socks"
|
- name: "vless-tcp"
|
||||||
type: socks5
|
type: vless
|
||||||
server: server
|
server: server
|
||||||
port: 443
|
port: 443
|
||||||
# username: username
|
uuid: uuid
|
||||||
# password: password
|
network: tcp
|
||||||
# tls: true
|
servername: example.com # AKA SNI
|
||||||
|
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
|
||||||
|
# skip-cert-verify: true
|
||||||
|
# fingerprint: xxxx
|
||||||
|
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||||
|
|
||||||
|
- name: "vless-vision"
|
||||||
|
type: vless
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
uuid: uuid
|
||||||
|
network: tcp
|
||||||
|
tls: true
|
||||||
|
udp: true
|
||||||
|
xudp: true
|
||||||
|
flow: xtls-rprx-vision # xtls-rprx-origin # enable XTLS
|
||||||
|
client-fingerprint: chrome
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# udp: true
|
|
||||||
# ip-version: ipv6
|
|
||||||
|
|
||||||
# http
|
- name: "vless-ws"
|
||||||
- name: "http"
|
type: vless
|
||||||
type: http
|
|
||||||
server: server
|
server: server
|
||||||
port: 443
|
port: 443
|
||||||
# username: username
|
uuid: uuid
|
||||||
# password: password
|
udp: true
|
||||||
# tls: true # https
|
tls: true
|
||||||
|
network: ws
|
||||||
|
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||||
|
servername: example.com # priority over wss host
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# sni: custom.com
|
# fingerprint: xxxx
|
||||||
# fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints
|
ws-opts:
|
||||||
# ip-version: dual
|
path: "/"
|
||||||
|
headers:
|
||||||
# Snell
|
Host: example.com
|
||||||
# Beware that there's currently no UDP support yet
|
|
||||||
- name: "snell"
|
|
||||||
type: snell
|
|
||||||
server: server
|
|
||||||
port: 44046
|
|
||||||
psk: yourpsk
|
|
||||||
# version: 2
|
|
||||||
# obfs-opts:
|
|
||||||
# mode: http # or tls
|
|
||||||
# host: bing.com
|
|
||||||
|
|
||||||
# Trojan
|
# Trojan
|
||||||
- name: "trojan"
|
- name: "trojan"
|
||||||
@ -477,36 +513,6 @@ proxies:
|
|||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
|
|
||||||
# vless
|
|
||||||
- name: "vless-tcp"
|
|
||||||
type: vless
|
|
||||||
server: server
|
|
||||||
port: 443
|
|
||||||
uuid: uuid
|
|
||||||
network: tcp
|
|
||||||
servername: example.com # AKA SNI
|
|
||||||
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
|
|
||||||
# skip-cert-verify: true
|
|
||||||
# fingerprint: xxxx
|
|
||||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
|
||||||
|
|
||||||
- name: "vless-ws"
|
|
||||||
type: vless
|
|
||||||
server: server
|
|
||||||
port: 443
|
|
||||||
uuid: uuid
|
|
||||||
udp: true
|
|
||||||
tls: true
|
|
||||||
network: ws
|
|
||||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
|
||||||
servername: example.com # priority over wss host
|
|
||||||
# skip-cert-verify: true
|
|
||||||
# fingerprint: xxxx
|
|
||||||
ws-opts:
|
|
||||||
path: "/"
|
|
||||||
headers:
|
|
||||||
Host: example.com
|
|
||||||
|
|
||||||
#hysteria
|
#hysteria
|
||||||
- name: "hysteria"
|
- name: "hysteria"
|
||||||
type: hysteria
|
type: hysteria
|
||||||
@ -533,6 +539,7 @@ proxies:
|
|||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# fast-open: true # 支持 TCP 快速打开,默认为 false
|
# fast-open: true # 支持 TCP 快速打开,默认为 false
|
||||||
|
|
||||||
|
# wireguard
|
||||||
- name: "wg"
|
- name: "wg"
|
||||||
type: wireguard
|
type: wireguard
|
||||||
server: 162.159.192.1
|
server: 162.159.192.1
|
||||||
@ -542,7 +549,9 @@ proxies:
|
|||||||
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
|
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
|
||||||
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
|
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
|
||||||
udp: true
|
udp: true
|
||||||
# reserved: 'U4An'
|
reserved: "U4An"
|
||||||
|
|
||||||
|
# tuic
|
||||||
- name: tuic
|
- name: tuic
|
||||||
server: www.example.com
|
server: www.example.com
|
||||||
port: 10443
|
port: 10443
|
||||||
@ -551,9 +560,9 @@ proxies:
|
|||||||
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
||||||
# heartbeat-interval: 10000
|
# heartbeat-interval: 10000
|
||||||
# alpn: [h3]
|
# alpn: [h3]
|
||||||
# disable-sni: true
|
disable-sni: true
|
||||||
reduce-rtt: true
|
reduce-rtt: true
|
||||||
# request-timeout: 8000
|
request-timeout: 8000
|
||||||
udp-relay-mode: native # Available: "native", "quic". Default: "native"
|
udp-relay-mode: native # Available: "native", "quic". Default: "native"
|
||||||
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
|
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
|
||||||
# max-udp-relay-packet-size: 1500
|
# max-udp-relay-packet-size: 1500
|
||||||
@ -581,8 +590,7 @@ proxies:
|
|||||||
# protocol-param: "#"
|
# protocol-param: "#"
|
||||||
# udp: true
|
# udp: true
|
||||||
|
|
||||||
proxy-groups:
|
proxy-groups: # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
||||||
# 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
|
||||||
# Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
|
# Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
|
||||||
- name: "relay"
|
- name: "relay"
|
||||||
type: relay
|
type: relay
|
||||||
@ -689,7 +697,8 @@ rules:
|
|||||||
- DOMAIN-KEYWORD,google,ss1
|
- DOMAIN-KEYWORD,google,ss1
|
||||||
- IP-CIDR,1.1.1.1/32,ss1
|
- IP-CIDR,1.1.1.1/32,ss1
|
||||||
- IP-CIDR6,2409::/64,DIRECT
|
- IP-CIDR6,2409::/64,DIRECT
|
||||||
- SUB-RULE,(OR,((NETWORK,TCP),(NETWORK,UDP))),sub-rule-name1 # 当满足条件是 TCP 或 UDP 流量时,使用名为 sub-rule-name1 当规则集
|
# 当满足条件是 TCP 或 UDP 流量时,使用名为 sub-rule-name1 的规则集
|
||||||
|
- SUB-RULE,(OR,((NETWORK,TCP),(NETWORK,UDP))),sub-rule-name1
|
||||||
- SUB-RULE,(AND,((NETWORK,UDP))),sub-rule-name2
|
- SUB-RULE,(AND,((NETWORK,UDP))),sub-rule-name2
|
||||||
# 定义多个子规则集,规则将以分叉匹配,使用 SUB-RULE 使用
|
# 定义多个子规则集,规则将以分叉匹配,使用 SUB-RULE 使用
|
||||||
# google.com(not match)--> baidu.com(match)
|
# google.com(not match)--> baidu.com(match)
|
||||||
@ -716,15 +725,6 @@ sub-rules:
|
|||||||
- IP-CIDR,8.8.8.8/32,ss1
|
- IP-CIDR,8.8.8.8/32,ss1
|
||||||
- DOMAIN,dns.alidns.com,REJECT
|
- DOMAIN,dns.alidns.com,REJECT
|
||||||
|
|
||||||
tls:
|
|
||||||
certificate: string # 证书 PEM 格式,或者 证书的路径
|
|
||||||
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
|
||||||
# 自定义证书验证,将加入 Clash 证书验证中,绝大多数 TLS 相关支持,如:DNS
|
|
||||||
# 可用于自定义证书的验证
|
|
||||||
custom-certificates:
|
|
||||||
- certificate: string # 证书 PEM 格式,或者 证书的路径
|
|
||||||
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
|
||||||
|
|
||||||
# 流量入站
|
# 流量入站
|
||||||
listeners:
|
listeners:
|
||||||
- name: socks5-in-1
|
- name: socks5-in-1
|
||||||
@ -838,7 +838,7 @@ listeners:
|
|||||||
# include_uid_range: # 限制被路由的的用户范围
|
# include_uid_range: # 限制被路由的的用户范围
|
||||||
# - 1000-99999
|
# - 1000-99999
|
||||||
# exclude_uid: # 排除路由的的用户
|
# exclude_uid: # 排除路由的的用户
|
||||||
#- 1000
|
# - 1000
|
||||||
# exclude_uid_range: # 排除路由的的用户范围
|
# exclude_uid_range: # 排除路由的的用户范围
|
||||||
# - 1000-99999
|
# - 1000-99999
|
||||||
|
|
||||||
@ -852,3 +852,23 @@ listeners:
|
|||||||
# - com.android.chrome
|
# - com.android.chrome
|
||||||
# exclude_package: # 排除被路由的 Android 应用包名
|
# exclude_package: # 排除被路由的 Android 应用包名
|
||||||
# - com.android.captiveportallogin
|
# - com.android.captiveportallogin
|
||||||
|
|
||||||
|
# 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理
|
||||||
|
# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||||
|
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
|
||||||
|
# vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345
|
||||||
|
|
||||||
|
# tuic服务器入口(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||||
|
# tuic-server:
|
||||||
|
# enable: true
|
||||||
|
# listen: 127.0.0.1:10443
|
||||||
|
# token:
|
||||||
|
# - TOKEN
|
||||||
|
# certificate: ./server.crt
|
||||||
|
# private-key: ./server.key
|
||||||
|
# congestion-controller: bbr
|
||||||
|
# max-idle-time: 15000
|
||||||
|
# authentication-timeout: 1000
|
||||||
|
# alpn:
|
||||||
|
# - h3
|
||||||
|
# max-udp-relay-packet-size: 1500
|
@ -169,9 +169,11 @@ func updateExperimental(c *config.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func preUpdateExperimental(c *config.Config) {
|
func preUpdateExperimental(c *config.Config) {
|
||||||
CTLS.AddCertificate(c.TLS.PrivateKey, c.TLS.Certificate)
|
CTLS.ResetCertificate()
|
||||||
for _, c := range c.TLS.CustomTrustCert {
|
for _, c := range c.TLS.CustomTrustCert {
|
||||||
CTLS.AddCertificate(c.PrivateKey, c.Certificate)
|
if err := CTLS.AddCertificate(c); err != nil {
|
||||||
|
log.Warnln("%s\nadd error: %s", c, err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,11 +331,7 @@ func updateTunnels(tunnels []LC.Tunnel) {
|
|||||||
func updateGeneral(general *config.General) {
|
func updateGeneral(general *config.General) {
|
||||||
tunnel.SetMode(general.Mode)
|
tunnel.SetMode(general.Mode)
|
||||||
tunnel.SetFindProcessMode(general.FindProcessMode)
|
tunnel.SetFindProcessMode(general.FindProcessMode)
|
||||||
dialer.DisableIPv6 = !general.IPv6
|
resolver.DisableIPv6 =!general.IPv6
|
||||||
if !dialer.DisableIPv6 {
|
|
||||||
log.Infoln("Use IPv6")
|
|
||||||
}
|
|
||||||
resolver.DisableIPv6 = dialer.DisableIPv6
|
|
||||||
|
|
||||||
if general.TCPConcurrent {
|
if general.TCPConcurrent {
|
||||||
dialer.SetDial(general.TCPConcurrent)
|
dialer.SetDial(general.TCPConcurrent)
|
||||||
|
@ -1,23 +1,33 @@
|
|||||||
package vless
|
package vless
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/subtle"
|
||||||
|
gotls "crypto/tls"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/buf"
|
"github.com/Dreamacro/clash/common/buf"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
|
utls "github.com/sagernet/utls"
|
||||||
xtls "github.com/xtls/go"
|
xtls "github.com/xtls/go"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
N.ExtendedConn
|
N.ExtendedWriter
|
||||||
|
N.ExtendedReader
|
||||||
|
net.Conn
|
||||||
dst *DstAddr
|
dst *DstAddr
|
||||||
id *uuid.UUID
|
id *uuid.UUID
|
||||||
addons *Addons
|
addons *Addons
|
||||||
@ -26,30 +36,143 @@ type Conn struct {
|
|||||||
handshake chan struct{}
|
handshake chan struct{}
|
||||||
handshakeMutex sync.Mutex
|
handshakeMutex sync.Mutex
|
||||||
err error
|
err error
|
||||||
|
|
||||||
|
tlsConn net.Conn
|
||||||
|
input *bytes.Reader
|
||||||
|
rawInput *bytes.Buffer
|
||||||
|
|
||||||
|
packetsToFilter int
|
||||||
|
isTLS bool
|
||||||
|
isTLS12orAbove bool
|
||||||
|
enableXTLS bool
|
||||||
|
cipher uint16
|
||||||
|
remainingServerHello uint16
|
||||||
|
readRemainingContent int
|
||||||
|
readRemainingPadding int
|
||||||
|
readProcess bool
|
||||||
|
readFilterUUID bool
|
||||||
|
readLastCommand byte
|
||||||
|
writeFilterApplicationData bool
|
||||||
|
writeDirect bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) Read(b []byte) (int, error) {
|
func (vc *Conn) Read(b []byte) (int, error) {
|
||||||
if vc.received {
|
if vc.received {
|
||||||
return vc.ExtendedConn.Read(b)
|
if vc.readProcess {
|
||||||
|
buffer := buf.With(b)
|
||||||
|
err := vc.ReadBuffer(buffer)
|
||||||
|
return buffer.Len(), err
|
||||||
|
}
|
||||||
|
return vc.ExtendedReader.Read(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := vc.recvResponse(); err != nil {
|
if err := vc.recvResponse(); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
vc.received = true
|
vc.received = true
|
||||||
return vc.ExtendedConn.Read(b)
|
return vc.Read(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error {
|
func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error {
|
||||||
if vc.received {
|
if vc.received {
|
||||||
return vc.ExtendedConn.ReadBuffer(buffer)
|
toRead := buffer.FreeBytes()
|
||||||
|
if vc.readRemainingContent > 0 {
|
||||||
|
if vc.readRemainingContent < buffer.FreeLen() {
|
||||||
|
toRead = toRead[:vc.readRemainingContent]
|
||||||
|
}
|
||||||
|
n, err := vc.ExtendedReader.Read(toRead)
|
||||||
|
buffer.Truncate(n)
|
||||||
|
vc.readRemainingContent -= n
|
||||||
|
vc.FilterTLS(toRead)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if vc.readRemainingPadding > 0 {
|
||||||
|
_, err := io.CopyN(io.Discard, vc.ExtendedReader, int64(vc.readRemainingPadding))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vc.readRemainingPadding = 0
|
||||||
|
}
|
||||||
|
if vc.readProcess {
|
||||||
|
switch vc.readLastCommand {
|
||||||
|
case commandPaddingContinue:
|
||||||
|
//if vc.isTLS || vc.packetsToFilter > 0 {
|
||||||
|
headerUUIDLen := 0
|
||||||
|
if vc.readFilterUUID {
|
||||||
|
headerUUIDLen = uuid.Size
|
||||||
|
}
|
||||||
|
var header []byte
|
||||||
|
if need := headerUUIDLen + paddingHeaderLen; buffer.FreeLen() < need {
|
||||||
|
header = make([]byte, need)
|
||||||
|
} else {
|
||||||
|
header = buffer.FreeBytes()[:need]
|
||||||
|
}
|
||||||
|
_, err := io.ReadFull(vc.ExtendedReader, header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pos := 0
|
||||||
|
if vc.readFilterUUID {
|
||||||
|
vc.readFilterUUID = false
|
||||||
|
pos = uuid.Size
|
||||||
|
if subtle.ConstantTimeCompare(vc.id.Bytes(), header[:uuid.Size]) != 1 {
|
||||||
|
err = fmt.Errorf("XTLS Vision server responded unknown UUID: %s",
|
||||||
|
uuid.FromBytesOrNil(header[:uuid.Size]).String())
|
||||||
|
log.Errorln(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vc.readLastCommand = header[pos]
|
||||||
|
vc.readRemainingContent = int(binary.BigEndian.Uint16(header[pos+1:]))
|
||||||
|
vc.readRemainingPadding = int(binary.BigEndian.Uint16(header[pos+3:]))
|
||||||
|
log.Debugln("XTLS Vision read padding: command=%d, payloadLen=%d, paddingLen=%d",
|
||||||
|
vc.readLastCommand, vc.readRemainingContent, vc.readRemainingPadding)
|
||||||
|
return vc.ReadBuffer(buffer)
|
||||||
|
//}
|
||||||
|
case commandPaddingEnd:
|
||||||
|
vc.readProcess = false
|
||||||
|
return vc.ReadBuffer(buffer)
|
||||||
|
case commandPaddingDirect:
|
||||||
|
if vc.input != nil {
|
||||||
|
_, err := buffer.ReadFrom(vc.input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if vc.input.Len() == 0 {
|
||||||
|
vc.input = nil
|
||||||
|
}
|
||||||
|
if buffer.IsFull() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vc.rawInput != nil {
|
||||||
|
_, err := buffer.ReadFrom(vc.rawInput)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if vc.rawInput.Len() == 0 {
|
||||||
|
vc.rawInput = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vc.input == nil && vc.rawInput == nil {
|
||||||
|
vc.readProcess = false
|
||||||
|
vc.ExtendedReader = N.NewExtendedReader(vc.Conn)
|
||||||
|
log.Debugln("XTLS Vision direct read start")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("XTLS Vision read unknown command: %d", vc.readLastCommand)
|
||||||
|
log.Debugln(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vc.ExtendedReader.ReadBuffer(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := vc.recvResponse(); err != nil {
|
if err := vc.recvResponse(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
vc.received = true
|
vc.received = true
|
||||||
return vc.ExtendedConn.ReadBuffer(buffer)
|
return vc.ReadBuffer(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) Write(p []byte) (int, error) {
|
func (vc *Conn) Write(p []byte) (int, error) {
|
||||||
@ -66,7 +189,19 @@ func (vc *Conn) Write(p []byte) (int, error) {
|
|||||||
return 0, vc.err
|
return 0, vc.err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vc.ExtendedConn.Write(p)
|
if vc.writeFilterApplicationData {
|
||||||
|
_buffer := buf.StackNew()
|
||||||
|
defer buf.KeepAlive(_buffer)
|
||||||
|
buffer := buf.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
buffer.Write(p)
|
||||||
|
err := vc.WriteBuffer(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
return vc.ExtendedWriter.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
func (vc *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
@ -80,7 +215,64 @@ func (vc *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|||||||
return vc.err
|
return vc.err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vc.ExtendedConn.WriteBuffer(buffer)
|
if vc.writeFilterApplicationData {
|
||||||
|
buffer2 := ReshapeBuffer(buffer)
|
||||||
|
defer buffer2.Release()
|
||||||
|
vc.FilterTLS(buffer.Bytes())
|
||||||
|
command := commandPaddingContinue
|
||||||
|
if !vc.isTLS {
|
||||||
|
command = commandPaddingEnd
|
||||||
|
|
||||||
|
// disable XTLS
|
||||||
|
vc.readProcess = false
|
||||||
|
vc.writeFilterApplicationData = false
|
||||||
|
vc.packetsToFilter = 0
|
||||||
|
} else if buffer.Len() > 6 && bytes.Equal(buffer.To(3), tlsApplicationDataStart) || vc.packetsToFilter <= 0 {
|
||||||
|
command = commandPaddingEnd
|
||||||
|
if vc.enableXTLS {
|
||||||
|
command = commandPaddingDirect
|
||||||
|
vc.writeDirect = true
|
||||||
|
}
|
||||||
|
vc.writeFilterApplicationData = false
|
||||||
|
}
|
||||||
|
ApplyPadding(buffer, command, nil)
|
||||||
|
err := vc.ExtendedWriter.WriteBuffer(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if vc.writeDirect {
|
||||||
|
vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn)
|
||||||
|
log.Debugln("XTLS Vision direct write start")
|
||||||
|
//time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
if buffer2 != nil {
|
||||||
|
if vc.writeDirect || !vc.isTLS {
|
||||||
|
return vc.ExtendedWriter.WriteBuffer(buffer2)
|
||||||
|
}
|
||||||
|
vc.FilterTLS(buffer2.Bytes())
|
||||||
|
command = commandPaddingContinue
|
||||||
|
if buffer2.Len() > 6 && bytes.Equal(buffer2.To(3), tlsApplicationDataStart) || vc.packetsToFilter <= 0 {
|
||||||
|
command = commandPaddingEnd
|
||||||
|
if vc.enableXTLS {
|
||||||
|
command = commandPaddingDirect
|
||||||
|
vc.writeDirect = true
|
||||||
|
}
|
||||||
|
vc.writeFilterApplicationData = false
|
||||||
|
}
|
||||||
|
ApplyPadding(buffer2, command, nil)
|
||||||
|
err = vc.ExtendedWriter.WriteBuffer(buffer2)
|
||||||
|
if vc.writeDirect {
|
||||||
|
vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn)
|
||||||
|
log.Debugln("XTLS Vision direct write start")
|
||||||
|
//time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
/*if vc.writeDirect {
|
||||||
|
log.Debugln("XTLS Vision Direct write, payloadLen=%d", buffer.Len())
|
||||||
|
}*/
|
||||||
|
return vc.ExtendedWriter.WriteBuffer(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) sendRequest(p []byte) bool {
|
func (vc *Conn) sendRequest(p []byte) bool {
|
||||||
@ -96,9 +288,6 @@ func (vc *Conn) sendRequest(p []byte) bool {
|
|||||||
}
|
}
|
||||||
defer close(vc.handshake)
|
defer close(vc.handshake)
|
||||||
|
|
||||||
requestLen := 1 // protocol version
|
|
||||||
requestLen += 16 // UUID
|
|
||||||
requestLen += 1 // addons length
|
|
||||||
var addonsBytes []byte
|
var addonsBytes []byte
|
||||||
if vc.addons != nil {
|
if vc.addons != nil {
|
||||||
addonsBytes, vc.err = proto.Marshal(vc.addons)
|
addonsBytes, vc.err = proto.Marshal(vc.addons)
|
||||||
@ -106,6 +295,18 @@ func (vc *Conn) sendRequest(p []byte) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
isVision := vc.IsXTLSVisionEnabled()
|
||||||
|
|
||||||
|
var buffer *buf.Buffer
|
||||||
|
if isVision {
|
||||||
|
_buffer := buf.StackNew()
|
||||||
|
defer buf.KeepAlive(_buffer)
|
||||||
|
buffer = buf.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
} else {
|
||||||
|
requestLen := 1 // protocol version
|
||||||
|
requestLen += 16 // UUID
|
||||||
|
requestLen += 1 // addons length
|
||||||
requestLen += len(addonsBytes)
|
requestLen += len(addonsBytes)
|
||||||
requestLen += 1 // command
|
requestLen += 1 // command
|
||||||
if !vc.dst.Mux {
|
if !vc.dst.Mux {
|
||||||
@ -117,8 +318,9 @@ func (vc *Conn) sendRequest(p []byte) bool {
|
|||||||
|
|
||||||
_buffer := buf.StackNewSize(requestLen)
|
_buffer := buf.StackNewSize(requestLen)
|
||||||
defer buf.KeepAlive(_buffer)
|
defer buf.KeepAlive(_buffer)
|
||||||
buffer := buf.Dup(_buffer)
|
buffer = buf.Dup(_buffer)
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
|
}
|
||||||
|
|
||||||
buf.Must(
|
buf.Must(
|
||||||
buffer.WriteByte(Version), // protocol version
|
buffer.WriteByte(Version), // protocol version
|
||||||
@ -143,15 +345,51 @@ func (vc *Conn) sendRequest(p []byte) bool {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isVision && !vc.dst.UDP && !vc.dst.Mux {
|
||||||
|
if len(p) == 0 {
|
||||||
|
WriteWithPadding(buffer, nil, commandPaddingContinue, vc.id)
|
||||||
|
} else {
|
||||||
|
vc.FilterTLS(p)
|
||||||
|
if vc.isTLS {
|
||||||
|
WriteWithPadding(buffer, p, commandPaddingContinue, vc.id)
|
||||||
|
} else {
|
||||||
buf.Must(buf.Error(buffer.Write(p)))
|
buf.Must(buf.Error(buffer.Write(p)))
|
||||||
|
|
||||||
_, vc.err = vc.ExtendedConn.Write(buffer.Bytes())
|
// disable XTLS
|
||||||
|
vc.readProcess = false
|
||||||
|
vc.writeFilterApplicationData = false
|
||||||
|
vc.packetsToFilter = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.Must(buf.Error(buffer.Write(p)))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, vc.err = vc.ExtendedWriter.Write(buffer.Bytes())
|
||||||
|
if vc.err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if isVision {
|
||||||
|
switch underlying := vc.tlsConn.(type) {
|
||||||
|
case *gotls.Conn:
|
||||||
|
if underlying.ConnectionState().Version != gotls.VersionTLS13 {
|
||||||
|
vc.err = ErrNotTLS13
|
||||||
|
}
|
||||||
|
case *utls.UConn:
|
||||||
|
if underlying.ConnectionState().Version != utls.VersionTLS13 {
|
||||||
|
vc.err = ErrNotTLS13
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
vc.err = fmt.Errorf(`failed to use %s, maybe "security" is not "tls" or "utls"`, vc.addons.Flow)
|
||||||
|
}
|
||||||
|
vc.tlsConn = nil
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) recvResponse() error {
|
func (vc *Conn) recvResponse() error {
|
||||||
var buf [1]byte
|
var buf [1]byte
|
||||||
_, vc.err = io.ReadFull(vc.ExtendedConn, buf[:])
|
_, vc.err = io.ReadFull(vc.ExtendedReader, buf[:])
|
||||||
if vc.err != nil {
|
if vc.err != nil {
|
||||||
return vc.err
|
return vc.err
|
||||||
}
|
}
|
||||||
@ -160,27 +398,43 @@ func (vc *Conn) recvResponse() error {
|
|||||||
return errors.New("unexpected response version")
|
return errors.New("unexpected response version")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, vc.err = io.ReadFull(vc.ExtendedConn, buf[:])
|
_, vc.err = io.ReadFull(vc.ExtendedReader, buf[:])
|
||||||
if vc.err != nil {
|
if vc.err != nil {
|
||||||
return vc.err
|
return vc.err
|
||||||
}
|
}
|
||||||
|
|
||||||
length := int64(buf[0])
|
length := int64(buf[0])
|
||||||
if length != 0 { // addon data length > 0
|
if length != 0 { // addon data length > 0
|
||||||
io.CopyN(io.Discard, vc.ExtendedConn, length) // just discard
|
io.CopyN(io.Discard, vc.ExtendedReader, length) // just discard
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (vc *Conn) FrontHeadroom() int {
|
||||||
|
if vc.IsXTLSVisionEnabled() {
|
||||||
|
return paddingHeaderLen
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (vc *Conn) Upstream() any {
|
func (vc *Conn) Upstream() any {
|
||||||
return vc.ExtendedConn
|
if vc.tlsConn == nil {
|
||||||
|
return vc.Conn
|
||||||
|
}
|
||||||
|
return vc.tlsConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *Conn) IsXTLSVisionEnabled() bool {
|
||||||
|
return vc.addons != nil && vc.addons.Flow == XRV
|
||||||
}
|
}
|
||||||
|
|
||||||
// newConn return a Conn instance
|
// newConn return a Conn instance
|
||||||
func newConn(conn net.Conn, client *Client, dst *DstAddr) (*Conn, error) {
|
func newConn(conn net.Conn, client *Client, dst *DstAddr) (*Conn, error) {
|
||||||
c := &Conn{
|
c := &Conn{
|
||||||
ExtendedConn: N.NewExtendedConn(conn),
|
ExtendedReader: N.NewExtendedReader(conn),
|
||||||
|
ExtendedWriter: N.NewExtendedWriter(conn),
|
||||||
|
Conn: conn,
|
||||||
id: client.uuid,
|
id: client.uuid,
|
||||||
dst: dst,
|
dst: dst,
|
||||||
handshake: make(chan struct{}),
|
handshake: make(chan struct{}),
|
||||||
@ -204,15 +458,42 @@ func newConn(conn net.Conn, client *Client, dst *DstAddr) (*Conn, error) {
|
|||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("failed to use %s, maybe \"security\" is not \"xtls\"", client.Addons.Flow)
|
return nil, fmt.Errorf("failed to use %s, maybe \"security\" is not \"xtls\"", client.Addons.Flow)
|
||||||
}
|
}
|
||||||
|
case XRV:
|
||||||
|
c.packetsToFilter = 6
|
||||||
|
c.readProcess = true
|
||||||
|
c.readFilterUUID = true
|
||||||
|
c.writeFilterApplicationData = true
|
||||||
|
c.addons = client.Addons
|
||||||
|
var t reflect.Type
|
||||||
|
var p uintptr
|
||||||
|
switch underlying := conn.(type) {
|
||||||
|
case *gotls.Conn:
|
||||||
|
c.Conn = underlying.NetConn()
|
||||||
|
c.tlsConn = underlying
|
||||||
|
t = reflect.TypeOf(underlying).Elem()
|
||||||
|
p = uintptr(unsafe.Pointer(underlying))
|
||||||
|
case *utls.UConn:
|
||||||
|
c.Conn = underlying.NetConn()
|
||||||
|
c.tlsConn = underlying
|
||||||
|
t = reflect.TypeOf(underlying.Conn).Elem()
|
||||||
|
p = uintptr(unsafe.Pointer(underlying.Conn))
|
||||||
|
case *tlsC.UConn:
|
||||||
|
c.Conn = underlying.NetConn()
|
||||||
|
c.tlsConn = underlying.UConn
|
||||||
|
t = reflect.TypeOf(underlying.Conn).Elem()
|
||||||
|
p = uintptr(unsafe.Pointer(underlying.Conn))
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf(`failed to use %s, maybe "security" is not "tls" or "utls"`, client.Addons.Flow)
|
||||||
|
}
|
||||||
|
i, _ := t.FieldByName("input")
|
||||||
|
r, _ := t.FieldByName("rawInput")
|
||||||
|
c.input = (*bytes.Reader)(unsafe.Pointer(p + i.Offset))
|
||||||
|
c.rawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset))
|
||||||
|
if _, ok := c.Conn.(*net.TCPConn); !ok {
|
||||||
|
log.Debugln("XTLS underlying conn is not *net.TCPConn, got %s", reflect.TypeOf(conn).Name())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go func() {
|
|
||||||
// select {
|
|
||||||
// case <-c.handshake:
|
|
||||||
// case <-time.After(200 * time.Millisecond):
|
|
||||||
// c.sendRequest(nil)
|
|
||||||
// }
|
|
||||||
//}()
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
79
transport/vless/filter.go
Normal file
79
transport/vless/filter.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package vless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tls13SupportedVersions = []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04}
|
||||||
|
tlsClientHandshakeStart = []byte{0x16, 0x03}
|
||||||
|
tlsServerHandshakeStart = []byte{0x16, 0x03, 0x03}
|
||||||
|
tlsApplicationDataStart = []byte{0x17, 0x03, 0x03}
|
||||||
|
|
||||||
|
tls13CipherSuiteMap = map[uint16]string{
|
||||||
|
0x1301: "TLS_AES_128_GCM_SHA256",
|
||||||
|
0x1302: "TLS_AES_256_GCM_SHA384",
|
||||||
|
0x1303: "TLS_CHACHA20_POLY1305_SHA256",
|
||||||
|
0x1304: "TLS_AES_128_CCM_SHA256",
|
||||||
|
0x1305: "TLS_AES_128_CCM_8_SHA256",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tlsHandshakeTypeClientHello byte = 0x01
|
||||||
|
tlsHandshakeTypeServerHello byte = 0x02
|
||||||
|
)
|
||||||
|
|
||||||
|
func (vc *Conn) FilterTLS(p []byte) (index int) {
|
||||||
|
if vc.packetsToFilter <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
lenP := len(p)
|
||||||
|
vc.packetsToFilter -= 1
|
||||||
|
if index = bytes.Index(p, tlsServerHandshakeStart); index != -1 {
|
||||||
|
if lenP >= index+5 && p[index+5] == tlsHandshakeTypeServerHello {
|
||||||
|
vc.remainingServerHello = binary.BigEndian.Uint16(p[index+3:]) + 5
|
||||||
|
vc.isTLS = true
|
||||||
|
vc.isTLS12orAbove = true
|
||||||
|
if lenP-index >= 79 && vc.remainingServerHello >= 79 {
|
||||||
|
sessionIDLen := int(p[index+43])
|
||||||
|
vc.cipher = binary.BigEndian.Uint16(p[index+43+sessionIDLen+1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if index = bytes.Index(p, tlsClientHandshakeStart); index != -1 {
|
||||||
|
if lenP >= index+5 && p[index+5] == tlsHandshakeTypeClientHello {
|
||||||
|
vc.isTLS = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vc.remainingServerHello > 0 {
|
||||||
|
end := vc.remainingServerHello
|
||||||
|
vc.remainingServerHello -= end
|
||||||
|
if end > uint16(lenP) {
|
||||||
|
end = uint16(lenP)
|
||||||
|
}
|
||||||
|
if bytes.Contains(p[index:end], tls13SupportedVersions) {
|
||||||
|
// TLS 1.3 Client Hello
|
||||||
|
cs, ok := tls13CipherSuiteMap[vc.cipher]
|
||||||
|
if ok && cs != "TLS_AES_128_CCM_8_SHA256" {
|
||||||
|
vc.enableXTLS = true
|
||||||
|
}
|
||||||
|
log.Debugln("XTLS Vision found TLS 1.3, packetLength=", lenP, ", CipherSuite=", cs)
|
||||||
|
vc.packetsToFilter = 0
|
||||||
|
return
|
||||||
|
} else if vc.remainingServerHello < 0 {
|
||||||
|
log.Debugln("XTLS Vision found TLS 1.2, packetLength=", lenP)
|
||||||
|
vc.packetsToFilter = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugln("XTLS Vision found inconclusive server hello, packetLength=", lenP,
|
||||||
|
", remainingServerHelloBytes=", vc.remainingServerHello)
|
||||||
|
}
|
||||||
|
if vc.packetsToFilter <= 0 {
|
||||||
|
log.Debugln("XTLS Vision stop filtering")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
69
transport/vless/vision.go
Normal file
69
transport/vless/vision.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package vless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/buf"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
paddingHeaderLen = 1 + 2 + 2 // =5
|
||||||
|
|
||||||
|
commandPaddingContinue byte = 0x00
|
||||||
|
commandPaddingEnd byte = 0x01
|
||||||
|
commandPaddingDirect byte = 0x02
|
||||||
|
)
|
||||||
|
|
||||||
|
func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid.UUID) {
|
||||||
|
contentLen := int32(len(p))
|
||||||
|
var paddingLen int32
|
||||||
|
if contentLen < 900 {
|
||||||
|
paddingLen = rand.Int31n(500) + 900 - contentLen
|
||||||
|
}
|
||||||
|
|
||||||
|
if userUUID != nil { // unnecessary, but keep the same with Xray
|
||||||
|
buffer.Write(userUUID.Bytes())
|
||||||
|
}
|
||||||
|
buffer.WriteByte(command)
|
||||||
|
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(contentLen))
|
||||||
|
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(paddingLen))
|
||||||
|
buffer.Write(p)
|
||||||
|
buffer.Extend(int(paddingLen))
|
||||||
|
log.Debugln("XTLS Vision write padding1: command=%v, payloadLen=%v, paddingLen=%v", command, contentLen, paddingLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApplyPadding(buffer *buf.Buffer, command byte, userUUID *uuid.UUID) {
|
||||||
|
contentLen := int32(buffer.Len())
|
||||||
|
var paddingLen int32
|
||||||
|
if contentLen < 900 {
|
||||||
|
paddingLen = rand.Int31n(500) + 900 - contentLen
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(paddingLen))
|
||||||
|
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(contentLen))
|
||||||
|
buffer.ExtendHeader(1)[0] = command
|
||||||
|
if userUUID != nil { // unnecessary, but keep the same with Xray
|
||||||
|
copy(buffer.ExtendHeader(uuid.Size), userUUID.Bytes())
|
||||||
|
}
|
||||||
|
buffer.Extend(int(paddingLen))
|
||||||
|
log.Debugln("XTLS Vision write padding2: command=%d, payloadLen=%d, paddingLen=%d", command, contentLen, paddingLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReshapeBuffer(buffer *buf.Buffer) *buf.Buffer {
|
||||||
|
if buffer.Len() <= buf.BufferSize-paddingHeaderLen {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cutAt := bytes.LastIndex(buffer.Bytes(), tlsApplicationDataStart)
|
||||||
|
if cutAt == -1 {
|
||||||
|
cutAt = buf.BufferSize / 2
|
||||||
|
}
|
||||||
|
buffer2 := buf.New()
|
||||||
|
buffer2.Write(buffer.From(cutAt))
|
||||||
|
buffer.Truncate(cutAt)
|
||||||
|
return buffer2
|
||||||
|
}
|
@ -12,6 +12,7 @@ const (
|
|||||||
XRO = "xtls-rprx-origin"
|
XRO = "xtls-rprx-origin"
|
||||||
XRD = "xtls-rprx-direct"
|
XRD = "xtls-rprx-direct"
|
||||||
XRS = "xtls-rprx-splice"
|
XRS = "xtls-rprx-splice"
|
||||||
|
XRV = "xtls-rprx-vision"
|
||||||
|
|
||||||
Version byte = 0 // protocol version. preview version is 0
|
Version byte = 0 // protocol version. preview version is 0
|
||||||
)
|
)
|
||||||
|
@ -2,6 +2,7 @@ package vless
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
@ -9,6 +10,10 @@ import (
|
|||||||
xtls "github.com/xtls/go"
|
xtls "github.com/xtls/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotTLS13 = errors.New("XTLS Vision based on TLS 1.3 outer connection")
|
||||||
|
)
|
||||||
|
|
||||||
type XTLSConfig struct {
|
type XTLSConfig struct {
|
||||||
Host string
|
Host string
|
||||||
SkipCertVerify bool
|
SkipCertVerify bool
|
||||||
|
@ -81,7 +81,7 @@ func (tt *tcpTracker) Upstream() any {
|
|||||||
return tt.Conn
|
return tt.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker {
|
func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64) *tcpTracker {
|
||||||
uuid, _ := uuid.NewV4()
|
uuid, _ := uuid.NewV4()
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
if tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
if tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||||
@ -100,8 +100,8 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
|
|||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Chain: conn.Chains(),
|
Chain: conn.Chains(),
|
||||||
Rule: "",
|
Rule: "",
|
||||||
UploadTotal: atomic.NewInt64(0),
|
UploadTotal: atomic.NewInt64(uploadTotal),
|
||||||
DownloadTotal: atomic.NewInt64(0),
|
DownloadTotal: atomic.NewInt64(downloadTotal),
|
||||||
},
|
},
|
||||||
extendedReader: N.NewExtendedReader(conn),
|
extendedReader: N.NewExtendedReader(conn),
|
||||||
extendedWriter: N.NewExtendedWriter(conn),
|
extendedWriter: N.NewExtendedWriter(conn),
|
||||||
@ -147,7 +147,7 @@ func (ut *udpTracker) Close() error {
|
|||||||
return ut.PacketConn.Close()
|
return ut.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker {
|
func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64) *udpTracker {
|
||||||
uuid, _ := uuid.NewV4()
|
uuid, _ := uuid.NewV4()
|
||||||
metadata.RemoteDst = conn.RemoteDestination()
|
metadata.RemoteDst = conn.RemoteDestination()
|
||||||
|
|
||||||
@ -160,8 +160,8 @@ func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru
|
|||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Chain: conn.Chains(),
|
Chain: conn.Chains(),
|
||||||
Rule: "",
|
Rule: "",
|
||||||
UploadTotal: atomic.NewInt64(0),
|
UploadTotal: atomic.NewInt64(uploadTotal),
|
||||||
DownloadTotal: atomic.NewInt64(0),
|
DownloadTotal: atomic.NewInt64(downloadTotal),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,7 +322,7 @@ func handleUDPConn(packet C.PacketAdapter) {
|
|||||||
}
|
}
|
||||||
pCtx.InjectPacketConn(rawPc)
|
pCtx.InjectPacketConn(rawPc)
|
||||||
|
|
||||||
pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule)
|
pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule, 0, 0)
|
||||||
|
|
||||||
switch true {
|
switch true {
|
||||||
case metadata.SpecialProxy != "":
|
case metadata.SpecialProxy != "":
|
||||||
@ -367,6 +367,7 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn := connCtx.Conn()
|
conn := connCtx.Conn()
|
||||||
|
conn.ResetPeeked()
|
||||||
if sniffer.Dispatcher.Enable() && sniffingEnable {
|
if sniffer.Dispatcher.Enable() && sniffingEnable {
|
||||||
sniffer.Dispatcher.TCPSniff(conn, metadata)
|
sniffer.Dispatcher.TCPSniff(conn, metadata)
|
||||||
}
|
}
|
||||||
@ -400,6 +401,7 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var peekBytes []byte
|
var peekBytes []byte
|
||||||
|
var peekLen int
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -415,7 +417,7 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if peekLen := len(peekBytes); peekLen > 0 {
|
if peekLen = len(peekBytes); peekLen > 0 {
|
||||||
_, _ = conn.Discard(peekLen)
|
_, _ = conn.Discard(peekLen)
|
||||||
}
|
}
|
||||||
return remoteConn, err
|
return remoteConn, err
|
||||||
@ -436,7 +438,7 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule)
|
remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule, 0, int64(peekLen))
|
||||||
defer func(remoteConn C.Conn) {
|
defer func(remoteConn C.Conn) {
|
||||||
_ = remoteConn.Close()
|
_ = remoteConn.Close()
|
||||||
}(remoteConn)
|
}(remoteConn)
|
||||||
|
Reference in New Issue
Block a user