diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 68f7f583..86352a15 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -8,13 +8,11 @@ on:
- ".github/ISSUE_TEMPLATE/**"
branches:
- Alpha
- - Meta
tags:
- "v*"
pull_request_target:
branches:
- Alpha
- - Meta
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
diff --git a/adapter/adapter.go b/adapter/adapter.go
index ffb5ced0..538ba271 100644
--- a/adapter/adapter.go
+++ b/adapter/adapter.go
@@ -4,16 +4,16 @@ import (
"context"
"encoding/json"
"fmt"
- "github.com/Dreamacro/clash/common/queue"
- "github.com/Dreamacro/clash/component/dialer"
- C "github.com/Dreamacro/clash/constant"
"net"
"net/http"
"net/netip"
"net/url"
"time"
- "go.uber.org/atomic"
+ "github.com/Dreamacro/clash/common/atomic"
+ "github.com/Dreamacro/clash/common/queue"
+ "github.com/Dreamacro/clash/component/dialer"
+ C "github.com/Dreamacro/clash/constant"
)
var UnifiedDelay = atomic.NewBool(false)
diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go
index 156bc114..367638b8 100644
--- a/adapter/outbound/base.go
+++ b/adapter/outbound/base.go
@@ -3,9 +3,9 @@ package outbound
import (
"context"
"encoding/json"
- "errors"
"net"
"strings"
+ "syscall"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
@@ -47,31 +47,31 @@ func (b *Base) Type() C.AdapterType {
// StreamConn implements C.ProxyAdapter
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
- return c, errors.New("no support")
+ return c, C.ErrNotSupport
}
func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
- return nil, errors.New("no support")
+ return nil, C.ErrNotSupport
}
// DialContextWithDialer implements C.ProxyAdapter
func (b *Base) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
- return nil, errors.New("no support")
+ return nil, C.ErrNotSupport
}
// ListenPacketContext implements C.ProxyAdapter
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
- return nil, errors.New("no support")
+ return nil, C.ErrNotSupport
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (b *Base) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
- return nil, errors.New("no support")
+ return nil, C.ErrNotSupport
}
// SupportWithDialer implements C.ProxyAdapter
-func (b *Base) SupportWithDialer() bool {
- return false
+func (b *Base) SupportWithDialer() C.NetWork {
+ return C.InvalidNet
}
// SupportUOT implements C.ProxyAdapter
@@ -94,6 +94,11 @@ func (b *Base) SupportTFO() bool {
return b.tfo
}
+// IsL3Protocol implements C.ProxyAdapter
+func (b *Base) IsL3Protocol(metadata *C.Metadata) bool {
+ return false
+}
+
// MarshalJSON implements C.ProxyAdapter
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
@@ -146,6 +151,7 @@ type BasicOption struct {
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
+ DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy
}
type BaseOption struct {
@@ -198,7 +204,18 @@ func (c *conn) Upstream() any {
return c.ExtendedConn
}
+func (c *conn) WriterReplaceable() bool {
+ return true
+}
+
+func (c *conn) ReaderReplaceable() bool {
+ return true
+}
+
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
+ if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn
+ c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly
+ }
return &conn{N.NewExtendedConn(c), []string{a.Name()}, parseRemoteDestination(a.Addr())}
}
@@ -230,6 +247,9 @@ func (c *packetConn) LocalAddr() net.Addr {
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
+ if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn
+ pc = N.NewDeadlinePacketConn(pc) // most conn from outbound can't handle readDeadline correctly
+ }
return &packetConn{pc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), parseRemoteDestination(a.Addr())}
}
diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go
index cf1b2648..eae37d7a 100644
--- a/adapter/outbound/direct.go
+++ b/adapter/outbound/direct.go
@@ -2,6 +2,7 @@ package outbound
import (
"context"
+ "errors"
"net"
"github.com/Dreamacro/clash/component/dialer"
@@ -26,7 +27,14 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
// ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
- opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
+ // net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
+ if !metadata.Resolved() {
+ ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver)
+ if err != nil {
+ return nil, errors.New("can't resolve ip")
+ }
+ metadata.DstIP = ip
+ }
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", metadata.DstIP), "", d.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
diff --git a/adapter/outbound/http.go b/adapter/outbound/http.go
index 6a668ebb..2d5fd899 100644
--- a/adapter/outbound/http.go
+++ b/adapter/outbound/http.go
@@ -14,6 +14,7 @@ import (
"strconv"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
)
@@ -66,6 +67,12 @@ func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...di
// DialContextWithDialer implements C.ProxyAdapter
func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+ if len(h.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(h.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
c, err := dialer.DialContext(ctx, "tcp", h.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
@@ -85,8 +92,8 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
}
// SupportWithDialer implements C.ProxyAdapter
-func (h *Http) SupportWithDialer() bool {
- return true
+func (h *Http) SupportWithDialer() C.NetWork {
+ return C.TCP
}
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go
index bd75cc3c..6024ea10 100644
--- a/adapter/outbound/hysteria.go
+++ b/adapter/outbound/hysteria.go
@@ -20,6 +20,7 @@ import (
M "github.com/sagernet/sing/common/metadata"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
@@ -28,6 +29,7 @@ import (
"github.com/Dreamacro/clash/transport/hysteria/obfs"
"github.com/Dreamacro/clash/transport/hysteria/pmtud_fix"
"github.com/Dreamacro/clash/transport/hysteria/transport"
+ "github.com/Dreamacro/clash/transport/hysteria/utils"
)
const (
@@ -46,21 +48,12 @@ var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
type Hysteria struct {
*Base
+ option *HysteriaOption
client *core.Client
}
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
- hdc := hyDialerWithContext{
- ctx: context.Background(),
- hyDialer: func(network string) (net.PacketConn, error) {
- return dialer.ListenPacket(ctx, network, "", h.Base.DialOptions(opts...)...)
- },
- remoteAddr: func(addr string) (net.Addr, error) {
- return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
- },
- }
-
- tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), &hdc)
+ tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), h.genHdc(ctx, opts...))
if err != nil {
return nil, err
}
@@ -69,20 +62,32 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts .
}
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
- hdc := hyDialerWithContext{
+ udpConn, err := h.client.DialUDP(h.genHdc(ctx, opts...))
+ if err != nil {
+ return nil, err
+ }
+ return newPacketConn(&hyPacketConn{udpConn}, h), nil
+}
+
+func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
+ return &hyDialerWithContext{
ctx: context.Background(),
hyDialer: func(network string) (net.PacketConn, error) {
- return dialer.ListenPacket(ctx, network, "", h.Base.DialOptions(opts...)...)
+ var err error
+ var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
+ if len(h.option.DialerProxy) > 0 {
+ cDialer, err = proxydialer.NewByName(h.option.DialerProxy, cDialer)
+ if err != nil {
+ return nil, err
+ }
+ }
+ rAddrPort, _ := netip.ParseAddrPort(h.Addr())
+ return cDialer.ListenPacket(ctx, network, "", rAddrPort)
},
remoteAddr: func(addr string) (net.Addr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
},
}
- udpConn, err := h.client.DialUDP(&hdc)
- if err != nil {
- return nil, err
- }
- return newPacketConn(&hyPacketConn{udpConn}, h), nil
}
type HysteriaOption struct {
@@ -258,6 +263,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
+ option: &option,
client: client,
}, nil
}
diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go
index 1c64b3ca..e2ed24c2 100644
--- a/adapter/outbound/shadowsocks.go
+++ b/adapter/outbound/shadowsocks.go
@@ -11,6 +11,8 @@ import (
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
+ "github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/restls"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
@@ -145,6 +147,12 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, op
// DialContextWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+ if len(ss.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
@@ -166,17 +174,18 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta
// ListenPacketWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
+ if len(ss.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
if ss.option.UDPOverTCP {
tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata)
if err != nil {
return nil, err
}
- destination := M.ParseSocksaddr(metadata.RemoteAddress())
- if ss.option.UDPOverTCPVersion == 1 {
- return newPacketConn(uot.NewConn(tcpConn, uot.Request{Destination: destination}), ss), nil
- } else {
- return newPacketConn(uot.NewLazyConn(tcpConn, uot.Request{Destination: destination}), ss), nil
- }
+ return ss.ListenPacketOnStreamConn(ctx, tcpConn, metadata)
}
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
if err != nil {
@@ -187,26 +196,35 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
if err != nil {
return nil, err
}
- pc = ss.method.DialPacketConn(&bufio.BindPacketConn{PacketConn: pc, Addr: addr})
+ pc = ss.method.DialPacketConn(bufio.NewBindPacketConn(pc, addr))
return newPacketConn(pc, ss), nil
}
// SupportWithDialer implements C.ProxyAdapter
-func (ss *ShadowSocks) SupportWithDialer() bool {
- return true
+func (ss *ShadowSocks) SupportWithDialer() C.NetWork {
+ return C.ALLNet
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
-func (ss *ShadowSocks) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
+func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
if ss.option.UDPOverTCP {
- destination := M.ParseSocksaddr(metadata.RemoteAddress())
+ // ss uot use stream-oriented udp with a special address, so we need a net.UDPAddr
+ if !metadata.Resolved() {
+ ip, err := resolver.ResolveIP(ctx, metadata.Host)
+ if err != nil {
+ return nil, errors.New("can't resolve ip")
+ }
+ metadata.DstIP = ip
+ }
+
+ destination := M.SocksaddrFromNet(metadata.UDPAddr())
if ss.option.UDPOverTCPVersion == uot.LegacyVersion {
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), ss), nil
} else {
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), ss), nil
}
}
- return nil, errors.New("no support")
+ return nil, C.ErrNotSupport
}
// SupportUOT implements C.ProxyAdapter
diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go
index 135e7132..2b94ab0c 100644
--- a/adapter/outbound/shadowsocksr.go
+++ b/adapter/outbound/shadowsocksr.go
@@ -7,6 +7,7 @@ import (
"strconv"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/shadowsocks/core"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
@@ -17,6 +18,7 @@ import (
type ShadowSocksR struct {
*Base
+ option *ShadowSocksROption
cipher core.Cipher
obfs obfs.Obfs
protocol protocol.Protocol
@@ -65,6 +67,12 @@ func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata,
// DialContextWithDialer implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+ if len(ssr.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(ssr.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
@@ -86,6 +94,12 @@ func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Me
// ListenPacketWithDialer implements C.ProxyAdapter
func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
+ if len(ssr.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(ssr.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
if err != nil {
return nil, err
@@ -102,8 +116,8 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
}
// SupportWithDialer implements C.ProxyAdapter
-func (ssr *ShadowSocksR) SupportWithDialer() bool {
- return true
+func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork {
+ return C.ALLNet
}
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
@@ -168,6 +182,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
+ option: &option,
cipher: coreCiph,
obfs: obfs,
protocol: protocol,
diff --git a/adapter/outbound/singmux.go b/adapter/outbound/singmux.go
new file mode 100644
index 00000000..acfdfe99
--- /dev/null
+++ b/adapter/outbound/singmux.go
@@ -0,0 +1,138 @@
+package outbound
+
+import (
+ "context"
+ "errors"
+ "net"
+ "runtime"
+
+ CN "github.com/Dreamacro/clash/common/net"
+ "github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
+ "github.com/Dreamacro/clash/component/resolver"
+ C "github.com/Dreamacro/clash/constant"
+
+ mux "github.com/sagernet/sing-mux"
+ E "github.com/sagernet/sing/common/exceptions"
+ M "github.com/sagernet/sing/common/metadata"
+ N "github.com/sagernet/sing/common/network"
+)
+
+type SingMux struct {
+ C.ProxyAdapter
+ base ProxyBase
+ client *mux.Client
+ dialer *muxSingDialer
+ onlyTcp bool
+}
+
+type SingMuxOption struct {
+ Enabled bool `proxy:"enabled,omitempty"`
+ Protocol string `proxy:"protocol,omitempty"`
+ MaxConnections int `proxy:"max-connections,omitempty"`
+ MinStreams int `proxy:"min-streams,omitempty"`
+ MaxStreams int `proxy:"max-streams,omitempty"`
+ Padding bool `proxy:"padding,omitempty"`
+ Statistic bool `proxy:"statistic,omitempty"`
+ OnlyTcp bool `proxy:"only-tcp,omitempty"`
+}
+
+type ProxyBase interface {
+ DialOptions(opts ...dialer.Option) []dialer.Option
+}
+
+type muxSingDialer struct {
+ dialer dialer.Dialer
+ proxy C.ProxyAdapter
+ statistic bool
+}
+
+var _ N.Dialer = (*muxSingDialer)(nil)
+
+func (d *muxSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
+ var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
+ return cDialer.DialContext(ctx, network, destination.String())
+}
+
+func (d *muxSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
+ var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
+ return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
+}
+
+func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
+ options := s.base.DialOptions(opts...)
+ s.dialer.dialer = dialer.NewDialer(options...)
+ c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()))
+ if err != nil {
+ return nil, err
+ }
+ return NewConn(CN.NewRefConn(c, s), s.ProxyAdapter), err
+}
+
+func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
+ if s.onlyTcp {
+ return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
+ }
+ options := s.base.DialOptions(opts...)
+ s.dialer.dialer = dialer.NewDialer(options...)
+
+ // sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
+ if !metadata.Resolved() {
+ ip, err := resolver.ResolveIP(ctx, metadata.Host)
+ if err != nil {
+ return nil, errors.New("can't resolve ip")
+ }
+ metadata.DstIP = ip
+ }
+
+ pc, err := s.client.ListenPacket(ctx, M.SocksaddrFromNet(metadata.UDPAddr()))
+ if err != nil {
+ return nil, err
+ }
+ if pc == nil {
+ return nil, E.New("packetConn is nil")
+ }
+ return newPacketConn(CN.NewRefPacketConn(pc, s), s.ProxyAdapter), nil
+}
+
+func (s *SingMux) SupportUDP() bool {
+ if s.onlyTcp {
+ return s.ProxyAdapter.SupportUOT()
+ }
+ return true
+}
+
+func (s *SingMux) SupportUOT() bool {
+ if s.onlyTcp {
+ return s.ProxyAdapter.SupportUOT()
+ }
+ return true
+}
+
+func closeSingMux(s *SingMux) {
+ _ = s.client.Close()
+}
+
+func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) {
+ singDialer := &muxSingDialer{dialer: dialer.NewDialer(), proxy: proxy, statistic: option.Statistic}
+ client, err := mux.NewClient(mux.Options{
+ Dialer: singDialer,
+ Protocol: option.Protocol,
+ MaxConnections: option.MaxConnections,
+ MinStreams: option.MinStreams,
+ MaxStreams: option.MaxStreams,
+ Padding: option.Padding,
+ })
+ if err != nil {
+ return nil, err
+ }
+ outbound := &SingMux{
+ ProxyAdapter: proxy,
+ base: base,
+ client: client,
+ dialer: singDialer,
+ onlyTcp: option.OnlyTcp,
+ }
+ runtime.SetFinalizer(outbound, closeSingMux)
+ return outbound, nil
+}
diff --git a/adapter/outbound/snell.go b/adapter/outbound/snell.go
index d6f1efee..1ec0a430 100644
--- a/adapter/outbound/snell.go
+++ b/adapter/outbound/snell.go
@@ -8,6 +8,7 @@ import (
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/snell"
@@ -15,6 +16,7 @@ import (
type Snell struct {
*Base
+ option *SnellOption
psk []byte
pool *snell.Pool
obfsOption *simpleObfsOption
@@ -83,6 +85,12 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// DialContextWithDialer implements C.ProxyAdapter
func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+ if len(s.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
@@ -104,6 +112,13 @@ func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
// ListenPacketWithDialer implements C.ProxyAdapter
func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
+ var err error
+ if len(s.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil {
return nil, err
@@ -121,8 +136,8 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
}
// SupportWithDialer implements C.ProxyAdapter
-func (s *Snell) SupportWithDialer() bool {
- return true
+func (s *Snell) SupportWithDialer() C.NetWork {
+ return C.ALLNet
}
// SupportUOT implements C.ProxyAdapter
@@ -172,6 +187,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
+ option: &option,
psk: psk,
obfsOption: obfsOption,
version: option.Version,
@@ -179,7 +195,15 @@ func NewSnell(option SnellOption) (*Snell, error) {
if option.Version == snell.Version2 {
s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
- c, err := dialer.DialContext(ctx, "tcp", addr, s.Base.DialOptions()...)
+ var err error
+ var cDialer C.Dialer = dialer.NewDialer(s.Base.DialOptions()...)
+ if len(s.option.DialerProxy) > 0 {
+ cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer)
+ if err != nil {
+ return nil, err
+ }
+ }
+ c, err := cDialer.DialContext(ctx, "tcp", addr)
if err != nil {
return nil, err
}
diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go
index cdb89cc2..26f5733b 100644
--- a/adapter/outbound/socks5.go
+++ b/adapter/outbound/socks5.go
@@ -10,6 +10,7 @@ import (
"strconv"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
@@ -17,6 +18,7 @@ import (
type Socks5 struct {
*Base
+ option *Socks5Option
user string
pass string
tls bool
@@ -70,6 +72,12 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
// DialContextWithDialer implements C.ProxyAdapter
func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+ if len(ss.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
@@ -89,13 +97,20 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
}
// SupportWithDialer implements C.ProxyAdapter
-func (ss *Socks5) SupportWithDialer() bool {
- return true
+func (ss *Socks5) SupportWithDialer() C.NetWork {
+ return C.TCP
}
// ListenPacketContext implements C.ProxyAdapter
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
- c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
+ var cDialer C.Dialer = dialer.NewDialer(ss.Base.DialOptions(opts...)...)
+ if len(ss.option.DialerProxy) > 0 {
+ cDialer, err = proxydialer.NewByName(ss.option.DialerProxy, cDialer)
+ if err != nil {
+ return nil, err
+ }
+ }
+ c, err := cDialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
return
@@ -187,6 +202,7 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
+ option: &option,
user: option.UserName,
pass: option.Password,
tls: option.TLS,
diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go
index 030e74a9..b9bbc33f 100644
--- a/adapter/outbound/trojan.go
+++ b/adapter/outbound/trojan.go
@@ -8,8 +8,8 @@ import (
"net/http"
"strconv"
- N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun"
@@ -105,7 +105,7 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return c, err
}
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
- return N.NewExtendedConn(c), err
+ return c, err
}
// DialContext implements C.ProxyAdapter
@@ -135,6 +135,12 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
// DialContextWithDialer implements C.ProxyAdapter
func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+ if len(t.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@@ -179,6 +185,12 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
// ListenPacketWithDialer implements C.ProxyAdapter
func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
+ if len(t.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@@ -202,8 +214,8 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
}
// SupportWithDialer implements C.ProxyAdapter
-func (t *Trojan) SupportWithDialer() bool {
- return true
+func (t *Trojan) SupportWithDialer() C.NetWork {
+ return C.ALLNet
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
@@ -271,7 +283,15 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
if option.Network == "grpc" {
dialFn := func(network, addr string) (net.Conn, error) {
- c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
+ var err error
+ var cDialer C.Dialer = dialer.NewDialer(t.Base.DialOptions()...)
+ if len(t.option.DialerProxy) > 0 {
+ cDialer, err = proxydialer.NewByName(t.option.DialerProxy, cDialer)
+ if err != nil {
+ return nil, err
+ }
+ }
+ c, err := cDialer.DialContext(context.Background(), "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
}
diff --git a/adapter/outbound/tuic.go b/adapter/outbound/tuic.go
index d2f2b5e9..f2452ae2 100644
--- a/adapter/outbound/tuic.go
+++ b/adapter/outbound/tuic.go
@@ -16,6 +16,7 @@ import (
"github.com/metacubex/quic-go"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/tuic"
@@ -23,6 +24,7 @@ import (
type Tuic struct {
*Base
+ option *TuicOption
client *tuic.PoolClient
}
@@ -84,8 +86,8 @@ func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, meta
}
// SupportWithDialer implements C.ProxyAdapter
-func (t *Tuic) SupportWithDialer() bool {
- return true
+func (t *Tuic) SupportWithDialer() C.NetWork {
+ return C.ALLNet
}
func (t *Tuic) dial(ctx context.Context, opts ...dialer.Option) (pc net.PacketConn, addr net.Addr, err error) {
@@ -93,6 +95,12 @@ func (t *Tuic) dial(ctx context.Context, opts ...dialer.Option) (pc net.PacketCo
}
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.PacketConn, addr net.Addr, err error) {
+ if len(t.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, nil, err
+ }
+ }
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer)
if err != nil {
return nil, nil, err
@@ -230,6 +238,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
+ option: &option,
}
clientMaxOpenStreams := int64(option.MaxOpenStreams)
diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go
index 757661cc..d7db27d5 100644
--- a/adapter/outbound/vless.go
+++ b/adapter/outbound/vless.go
@@ -14,6 +14,7 @@ import (
"github.com/Dreamacro/clash/common/convert"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
@@ -168,7 +169,35 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return nil, err
}
- return v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
+ return v.streamConn(c, metadata)
+}
+
+func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
+ if metadata.NetWork == C.UDP {
+ if v.option.PacketAddr {
+ metadata = &C.Metadata{
+ NetWork: C.UDP,
+ Host: packetaddr.SeqPacketMagicAddress,
+ DstPort: "443",
+ }
+ } else {
+ metadata = &C.Metadata{ // a clear metadata only contains ip
+ NetWork: C.UDP,
+ DstIP: metadata.DstIP,
+ DstPort: metadata.DstPort,
+ }
+ }
+ conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
+ if v.option.PacketAddr {
+ conn = packetaddr.NewBindConn(conn)
+ }
+ } else {
+ conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, false))
+ }
+ if err != nil {
+ conn = nil
+ }
+ return
}
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
@@ -238,6 +267,12 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// DialContextWithDialer implements C.ProxyAdapter
func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+ if len(v.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
@@ -264,7 +299,6 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
}
metadata.DstIP = ip
}
-
var c net.Conn
// gun transport
if v.transport != nil && len(opts) == 0 {
@@ -276,27 +310,25 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
safeConnClose(c, err)
}(c)
- 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))
- }
-
+ c, err = v.streamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("new vless client error: %v", err)
}
- return v.ListenPacketOnStreamConn(c, metadata)
+ return v.ListenPacketOnStreamConn(ctx, c, metadata)
}
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
+ if len(v.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
+
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
@@ -305,6 +337,7 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
}
metadata.DstIP = ip
}
+
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
@@ -314,39 +347,39 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
safeConnClose(c, err)
}(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 {
return nil, fmt.Errorf("new vless client error: %v", err)
}
- return v.ListenPacketOnStreamConn(c, metadata)
+ return v.ListenPacketOnStreamConn(ctx, c, metadata)
}
// SupportWithDialer implements C.ProxyAdapter
-func (v *Vless) SupportWithDialer() bool {
- return true
+func (v *Vless) SupportWithDialer() C.NetWork {
+ return C.ALLNet
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
-func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
+func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
+ // vless use stream-oriented udp with a special address, so we need a net.UDPAddr
+ if !metadata.Resolved() {
+ ip, err := resolver.ResolveIP(ctx, metadata.Host)
+ if err != nil {
+ return nil, errors.New("can't resolve ip")
+ }
+ metadata.DstIP = ip
+ }
+
if v.option.XUDP {
return newPacketConn(&threadSafePacketConn{
- PacketConn: vmessSing.NewXUDPConn(c, M.ParseSocksaddr(metadata.RemoteAddress())),
+ PacketConn: vmessSing.NewXUDPConn(c, M.SocksaddrFromNet(metadata.UDPAddr())),
}, v), nil
} else if v.option.PacketAddr {
return newPacketConn(&threadSafePacketConn{
PacketConn: packetaddr.NewConn(&vlessPacketConn{
Conn: c, rAddr: metadata.UDPAddr(),
- }, M.ParseSocksaddr(metadata.RemoteAddress())),
+ }, M.SocksaddrFromNet(metadata.UDPAddr())),
}, v), nil
}
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
@@ -504,6 +537,9 @@ func NewVless(option VlessOption) (*Vless, error) {
option.XUDP = true
}
}
+ if option.XUDP {
+ option.PacketAddr = false
+ }
client, err := vless.NewClient(option.UUID, addons, option.FlowShow)
if err != nil {
@@ -538,7 +574,15 @@ func NewVless(option VlessOption) (*Vless, error) {
}
case "grpc":
dialFn := func(network, addr string) (net.Conn, error) {
- c, err := dialer.DialContext(context.Background(), "tcp", v.addr, v.Base.DialOptions()...)
+ var err error
+ var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
+ if len(v.option.DialerProxy) > 0 {
+ cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer)
+ if err != nil {
+ return nil, err
+ }
+ }
+ c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go
index 5bb46dad..8901f3d5 100644
--- a/adapter/outbound/vmess.go
+++ b/adapter/outbound/vmess.go
@@ -13,6 +13,7 @@ import (
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
@@ -216,27 +217,42 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if err != nil {
return nil, err
}
+ return v.streamConn(c, metadata)
+}
+
+func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
if metadata.NetWork == C.UDP {
if v.option.XUDP {
if N.NeedHandshake(c) {
- return v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
+ conn = v.client.DialEarlyXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
} else {
- return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
+ conn, err = v.client.DialXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
}
+ } else if v.option.PacketAddr {
+ if N.NeedHandshake(c) {
+ conn = v.client.DialEarlyPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
+ } else {
+ conn, err = v.client.DialPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
+ }
+ conn = packetaddr.NewBindConn(conn)
} else {
if N.NeedHandshake(c) {
- return v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
+ conn = v.client.DialEarlyPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
} else {
- return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
+ conn, err = v.client.DialPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
}
}
} else {
if N.NeedHandshake(c) {
- return v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
+ conn = v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} else {
- return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
+ conn, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
}
}
+ if err != nil {
+ conn = nil
+ }
+ return
}
// DialContext implements C.ProxyAdapter
@@ -263,6 +279,12 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// DialContextWithDialer implements C.ProxyAdapter
func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
+ if len(v.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
@@ -286,14 +308,6 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
}
metadata.DstIP = ip
}
-
- if v.option.PacketAddr {
- _metadata := *metadata // make a copy
- metadata = &_metadata
- metadata.Host = packetaddr.SeqPacketMagicAddress
- metadata.DstPort = "443"
- }
-
var c net.Conn
// gun transport
if v.transport != nil && len(opts) == 0 {
@@ -305,30 +319,24 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
safeConnClose(c, err)
}(c)
- if v.option.XUDP {
- if N.NeedHandshake(c) {
- c = v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
- } else {
- c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
- }
- } else {
- if N.NeedHandshake(c) {
- c = v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
- } else {
- c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
- }
- }
-
+ c, err = v.streamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
- return v.ListenPacketOnStreamConn(c, metadata)
+ return v.ListenPacketOnStreamConn(ctx, c, metadata)
}
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
+ if len(v.option.DialerProxy) > 0 {
+ dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
+ if err != nil {
+ return nil, err
+ }
+ }
+
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
@@ -351,19 +359,26 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
- return v.ListenPacketOnStreamConn(c, metadata)
+ return v.ListenPacketOnStreamConn(ctx, c, metadata)
}
// SupportWithDialer implements C.ProxyAdapter
-func (v *Vmess) SupportWithDialer() bool {
- return true
+func (v *Vmess) SupportWithDialer() C.NetWork {
+ return C.ALLNet
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
-func (v *Vmess) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
- if v.option.PacketAddr {
- return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindConn(c)}, v), nil
- } else if pc, ok := c.(net.PacketConn); ok {
+func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
+ // vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
+ if !metadata.Resolved() {
+ ip, err := resolver.ResolveIP(ctx, metadata.Host)
+ if err != nil {
+ return nil, errors.New("can't resolve ip")
+ }
+ metadata.DstIP = ip
+ }
+
+ if pc, ok := c.(net.PacketConn); ok {
return newPacketConn(&threadSafePacketConn{PacketConn: pc}, v), nil
}
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
@@ -428,7 +443,15 @@ func NewVmess(option VmessOption) (*Vmess, error) {
}
case "grpc":
dialFn := func(network, addr string) (net.Conn, error) {
- c, err := dialer.DialContext(context.Background(), "tcp", v.addr, v.Base.DialOptions()...)
+ var err error
+ var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
+ if len(v.option.DialerProxy) > 0 {
+ cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer)
+ if err != nil {
+ return nil, err
+ }
+ }
+ c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
@@ -486,9 +509,9 @@ type vmessPacketConn struct {
// WriteTo implments C.PacketConn.WriteTo
// Since VMess doesn't support full cone NAT by design, we verify if addr matches uc.rAddr, and drop the packet if not.
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
- allowedAddr := uc.rAddr.(*net.UDPAddr)
- destAddr := addr.(*net.UDPAddr)
- if !(allowedAddr.IP.Equal(destAddr.IP) && allowedAddr.Port == destAddr.Port) {
+ allowedAddr := uc.rAddr
+ destAddr := addr
+ if allowedAddr.String() != destAddr.String() {
return 0, ErrUDPRemoteAddrMismatch
}
uc.access.Lock()
diff --git a/adapter/outbound/wireguard.go b/adapter/outbound/wireguard.go
index 7eae30fc..67cd9092 100644
--- a/adapter/outbound/wireguard.go
+++ b/adapter/outbound/wireguard.go
@@ -15,8 +15,10 @@ import (
CN "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
+ "github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
wireguard "github.com/metacubex/sing-wireguard"
@@ -37,75 +39,97 @@ type WireGuard struct {
dialer *wgSingDialer
startOnce sync.Once
startErr error
+ resolver *dns.Resolver
+ refP *refProxyAdapter
}
type WireGuardOption struct {
BasicOption
- Name string `proxy:"name"`
- Server string `proxy:"server"`
- Port int `proxy:"port"`
- Ip string `proxy:"ip,omitempty"`
- Ipv6 string `proxy:"ipv6,omitempty"`
- PrivateKey string `proxy:"private-key"`
- PublicKey string `proxy:"public-key"`
- PreSharedKey string `proxy:"pre-shared-key,omitempty"`
- Reserved []uint8 `proxy:"reserved,omitempty"`
- Workers int `proxy:"workers,omitempty"`
- MTU int `proxy:"mtu,omitempty"`
- UDP bool `proxy:"udp,omitempty"`
- PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
+ WireGuardPeerOption
+ Name string `proxy:"name"`
+ PrivateKey string `proxy:"private-key"`
+ Workers int `proxy:"workers,omitempty"`
+ MTU int `proxy:"mtu,omitempty"`
+ UDP bool `proxy:"udp,omitempty"`
+ PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
+
+ Peers []WireGuardPeerOption `proxy:"peers,omitempty"`
+
+ RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
+ Dns []string `proxy:"dns,omitempty"`
+}
+
+type WireGuardPeerOption struct {
+ Server string `proxy:"server"`
+ Port int `proxy:"port"`
+ Ip string `proxy:"ip,omitempty"`
+ Ipv6 string `proxy:"ipv6,omitempty"`
+ PublicKey string `proxy:"public-key,omitempty"`
+ PreSharedKey string `proxy:"pre-shared-key,omitempty"`
+ Reserved []uint8 `proxy:"reserved,omitempty"`
+ AllowedIPs []string `proxy:"allowed_ips,omitempty"`
}
type wgSingDialer struct {
- dialer dialer.Dialer
+ dialer dialer.Dialer
+ proxyName string
}
-var _ N.Dialer = &wgSingDialer{}
+var _ N.Dialer = (*wgSingDialer)(nil)
func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
- return d.dialer.DialContext(ctx, network, destination.String())
+ var cDialer C.Dialer = d.dialer
+ if len(d.proxyName) > 0 {
+ pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
+ if err != nil {
+ return nil, err
+ }
+ cDialer = pd
+ }
+ return cDialer.DialContext(ctx, network, destination.String())
}
func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
- return d.dialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
+ var cDialer C.Dialer = d.dialer
+ if len(d.proxyName) > 0 {
+ pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
+ if err != nil {
+ return nil, err
+ }
+ cDialer = pd
+ }
+ return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
+}
+
+type wgSingErrorHandler struct {
+ name string
+}
+
+var _ E.Handler = (*wgSingErrorHandler)(nil)
+
+func (w wgSingErrorHandler) NewError(ctx context.Context, err error) {
+ if E.IsClosedOrCanceled(err) {
+ log.SingLogger.Debug(fmt.Sprintf("[WG](%s) connection closed: %s", w.name, err))
+ return
+ }
+ log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", w.name, err))
}
type wgNetDialer struct {
tunDevice wireguard.Device
}
-var _ dialer.NetDialer = &wgNetDialer{}
+var _ dialer.NetDialer = (*wgNetDialer)(nil)
func (d wgNetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return d.tunDevice.DialContext(ctx, network, M.ParseSocksaddr(address).Unwrap())
}
-func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
- outbound := &WireGuard{
- Base: &Base{
- name: option.Name,
- addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
- tp: C.WireGuard,
- udp: option.UDP,
- iface: option.Interface,
- rmark: option.RoutingMark,
- prefer: C.NewDNSPrefer(option.IPVersion),
- },
- dialer: &wgSingDialer{dialer: dialer.NewDialer()},
- }
- runtime.SetFinalizer(outbound, closeWireGuard)
+func (option WireGuardPeerOption) Addr() M.Socksaddr {
+ return M.ParseSocksaddrHostPort(option.Server, uint16(option.Port))
+}
- var reserved [3]uint8
- if len(option.Reserved) > 0 {
- if len(option.Reserved) != 3 {
- return nil, E.New("invalid reserved value, required 3 bytes, got ", len(option.Reserved))
- }
- reserved[0] = uint8(option.Reserved[0])
- reserved[1] = uint8(option.Reserved[1])
- reserved[2] = uint8(option.Reserved[2])
- }
- peerAddr := M.ParseSocksaddrHostPort(option.Server, uint16(option.Port))
- outbound.bind = wireguard.NewClientBind(context.Background(), outbound.dialer, peerAddr, reserved)
+func (option WireGuardPeerOption) Prefixes() ([]netip.Prefix, error) {
localPrefixes := make([]netip.Prefix, 0, 2)
if len(option.Ip) > 0 {
if !strings.Contains(option.Ip, "/") {
@@ -130,7 +154,46 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
if len(localPrefixes) == 0 {
return nil, E.New("missing local address")
}
- var privateKey, peerPublicKey, preSharedKey string
+ return localPrefixes, nil
+}
+
+func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
+ outbound := &WireGuard{
+ Base: &Base{
+ name: option.Name,
+ addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
+ tp: C.WireGuard,
+ udp: option.UDP,
+ iface: option.Interface,
+ rmark: option.RoutingMark,
+ prefer: C.NewDNSPrefer(option.IPVersion),
+ },
+ dialer: &wgSingDialer{dialer: dialer.NewDialer(), proxyName: option.DialerProxy},
+ }
+ runtime.SetFinalizer(outbound, closeWireGuard)
+
+ var reserved [3]uint8
+ if len(option.Reserved) > 0 {
+ if len(option.Reserved) != 3 {
+ return nil, E.New("invalid reserved value, required 3 bytes, got ", len(option.Reserved))
+ }
+ copy(reserved[:], option.Reserved)
+ }
+ var isConnect bool
+ var connectAddr M.Socksaddr
+ if len(option.Peers) < 2 {
+ isConnect = true
+ if len(option.Peers) == 1 {
+ connectAddr = option.Peers[0].Addr()
+ } else {
+ connectAddr = option.Addr()
+ }
+ }
+ outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr, reserved)
+
+ var localPrefixes []netip.Prefix
+
+ var privateKey string
{
bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey)
if err != nil {
@@ -138,40 +201,92 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
}
privateKey = hex.EncodeToString(bytes)
}
- {
- bytes, err := base64.StdEncoding.DecodeString(option.PublicKey)
- if err != nil {
- return nil, E.Cause(err, "decode peer public key")
- }
- peerPublicKey = hex.EncodeToString(bytes)
- }
- if option.PreSharedKey != "" {
- bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey)
- if err != nil {
- return nil, E.Cause(err, "decode pre shared key")
- }
- preSharedKey = hex.EncodeToString(bytes)
- }
ipcConf := "private_key=" + privateKey
- ipcConf += "\npublic_key=" + peerPublicKey
- ipcConf += "\nendpoint=" + peerAddr.String()
- if preSharedKey != "" {
- ipcConf += "\npreshared_key=" + preSharedKey
- }
- var has4, has6 bool
- for _, address := range localPrefixes {
- if address.Addr().Is4() {
- has4 = true
- } else {
- has6 = true
+ if peersLen := len(option.Peers); peersLen > 0 {
+ localPrefixes = make([]netip.Prefix, 0, peersLen*2)
+ for i, peer := range option.Peers {
+ var peerPublicKey, preSharedKey string
+ {
+ bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
+ if err != nil {
+ return nil, E.Cause(err, "decode public key for peer ", i)
+ }
+ peerPublicKey = hex.EncodeToString(bytes)
+ }
+ if peer.PreSharedKey != "" {
+ bytes, err := base64.StdEncoding.DecodeString(peer.PreSharedKey)
+ if err != nil {
+ return nil, E.Cause(err, "decode pre shared key for peer ", i)
+ }
+ preSharedKey = hex.EncodeToString(bytes)
+ }
+ destination := peer.Addr()
+ ipcConf += "\npublic_key=" + peerPublicKey
+ ipcConf += "\nendpoint=" + destination.String()
+ if preSharedKey != "" {
+ ipcConf += "\npreshared_key=" + preSharedKey
+ }
+ if len(peer.AllowedIPs) == 0 {
+ return nil, E.New("missing allowed_ips for peer ", i)
+ }
+ for _, allowedIP := range peer.AllowedIPs {
+ ipcConf += "\nallowed_ip=" + allowedIP
+ }
+ if len(peer.Reserved) > 0 {
+ if len(peer.Reserved) != 3 {
+ return nil, E.New("invalid reserved value for peer ", i, ", required 3 bytes, got ", len(peer.Reserved))
+ }
+ copy(reserved[:], option.Reserved)
+ outbound.bind.SetReservedForEndpoint(destination, reserved)
+ }
+ prefixes, err := peer.Prefixes()
+ if err != nil {
+ return nil, err
+ }
+ localPrefixes = append(localPrefixes, prefixes...)
+ }
+ } else {
+ var peerPublicKey, preSharedKey string
+ {
+ bytes, err := base64.StdEncoding.DecodeString(option.PublicKey)
+ if err != nil {
+ return nil, E.Cause(err, "decode peer public key")
+ }
+ peerPublicKey = hex.EncodeToString(bytes)
+ }
+ if option.PreSharedKey != "" {
+ bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey)
+ if err != nil {
+ return nil, E.Cause(err, "decode pre shared key")
+ }
+ preSharedKey = hex.EncodeToString(bytes)
+ }
+ ipcConf += "\npublic_key=" + peerPublicKey
+ ipcConf += "\nendpoint=" + connectAddr.String()
+ if preSharedKey != "" {
+ ipcConf += "\npreshared_key=" + preSharedKey
+ }
+ var err error
+ localPrefixes, err = option.Prefixes()
+ if err != nil {
+ return nil, err
+ }
+ var has4, has6 bool
+ for _, address := range localPrefixes {
+ if address.Addr().Is4() {
+ has4 = true
+ } else {
+ has6 = true
+ }
+ }
+ if has4 {
+ ipcConf += "\nallowed_ip=0.0.0.0/0"
+ }
+ if has6 {
+ ipcConf += "\nallowed_ip=::/0"
}
}
- if has4 {
- ipcConf += "\nallowed_ip=0.0.0.0/0"
- }
- if has6 {
- ipcConf += "\nallowed_ip=::/0"
- }
+
if option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive)
}
@@ -179,6 +294,9 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
if mtu == 0 {
mtu = 1408
}
+ if len(localPrefixes) == 0 {
+ return nil, E.New("missing local address")
+ }
var err error
outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu))
if err != nil {
@@ -186,20 +304,45 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
}
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
Verbosef: func(format string, args ...interface{}) {
- log.SingLogger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
+ log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
},
Errorf: func(format string, args ...interface{}) {
- log.SingLogger.Error(fmt.Sprintf(strings.ToLower(format), args...))
+ log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
},
}, option.Workers)
if debug.Enabled {
- log.SingLogger.Trace("created wireguard ipc conf: \n", ipcConf)
+ log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", option.Name, ipcConf))
}
err = outbound.device.IpcSet(ipcConf)
if err != nil {
return nil, E.Cause(err, "setup wireguard")
}
//err = outbound.tunDevice.Start()
+
+ var has6 bool
+ for _, address := range localPrefixes {
+ if !address.Addr().Unmap().Is4() {
+ has6 = true
+ break
+ }
+ }
+
+ refP := &refProxyAdapter{}
+ outbound.refP = refP
+ if option.RemoteDnsResolve && len(option.Dns) > 0 {
+ nss, err := dns.ParseNameServer(option.Dns)
+ if err != nil {
+ return nil, err
+ }
+ for i := range nss {
+ nss[i].ProxyAdapter = refP
+ }
+ outbound.resolver = dns.NewResolver(dns.Config{
+ Main: nss,
+ IPv6: has6,
+ })
+ }
+
return outbound, nil
}
@@ -220,8 +363,14 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
if w.startErr != nil {
return nil, w.startErr
}
- if !metadata.Resolved() {
- options = append(options, dialer.WithResolver(resolver.DefaultResolver))
+ if !metadata.Resolved() || w.resolver != nil {
+ r := resolver.DefaultResolver
+ if w.resolver != nil {
+ w.refP.SetProxyAdapter(w)
+ defer w.refP.ClearProxyAdapter()
+ r = w.resolver
+ }
+ options = append(options, dialer.WithResolver(r))
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
} else {
@@ -250,8 +399,14 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
if err != nil {
return nil, err
}
- if !metadata.Resolved() {
- ip, err := resolver.ResolveIP(ctx, metadata.Host)
+ if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
+ r := resolver.DefaultResolver
+ if w.resolver != nil {
+ w.refP.SetProxyAdapter(w)
+ defer w.refP.ClearProxyAdapter()
+ r = w.resolver
+ }
+ ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, errors.New("can't resolve ip")
}
@@ -267,3 +422,144 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
}
return newPacketConn(CN.NewRefPacketConn(pc, w), w), nil
}
+
+// IsL3Protocol implements C.ProxyAdapter
+func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
+ return true
+}
+
+type refProxyAdapter struct {
+ proxyAdapter C.ProxyAdapter
+ count int
+ mutex sync.Mutex
+}
+
+func (r *refProxyAdapter) SetProxyAdapter(proxyAdapter C.ProxyAdapter) {
+ r.mutex.Lock()
+ defer r.mutex.Unlock()
+ r.proxyAdapter = proxyAdapter
+ r.count++
+}
+
+func (r *refProxyAdapter) ClearProxyAdapter() {
+ r.mutex.Lock()
+ defer r.mutex.Unlock()
+ r.count--
+ if r.count == 0 {
+ r.proxyAdapter = nil
+ }
+}
+
+func (r *refProxyAdapter) Name() string {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.Name()
+ }
+ return ""
+}
+
+func (r *refProxyAdapter) Type() C.AdapterType {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.Type()
+ }
+ return C.AdapterType(0)
+}
+
+func (r *refProxyAdapter) Addr() string {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.Addr()
+ }
+ return ""
+}
+
+func (r *refProxyAdapter) SupportUDP() bool {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.SupportUDP()
+ }
+ return false
+}
+
+func (r *refProxyAdapter) SupportXUDP() bool {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.SupportXUDP()
+ }
+ return false
+}
+
+func (r *refProxyAdapter) SupportTFO() bool {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.SupportTFO()
+ }
+ return false
+}
+
+func (r *refProxyAdapter) MarshalJSON() ([]byte, error) {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.MarshalJSON()
+ }
+ return nil, C.ErrNotSupport
+}
+
+func (r *refProxyAdapter) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.StreamConn(c, metadata)
+ }
+ return nil, C.ErrNotSupport
+}
+
+func (r *refProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.DialContext(ctx, metadata, opts...)
+ }
+ return nil, C.ErrNotSupport
+}
+
+func (r *refProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
+ }
+ return nil, C.ErrNotSupport
+}
+
+func (r *refProxyAdapter) SupportUOT() bool {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.SupportUOT()
+ }
+ return false
+}
+
+func (r *refProxyAdapter) SupportWithDialer() C.NetWork {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.SupportWithDialer()
+ }
+ return C.InvalidNet
+}
+
+func (r *refProxyAdapter) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.DialContextWithDialer(ctx, dialer, metadata)
+ }
+ return nil, C.ErrNotSupport
+}
+
+func (r *refProxyAdapter) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.ListenPacketWithDialer(ctx, dialer, metadata)
+ }
+ return nil, C.ErrNotSupport
+}
+
+func (r *refProxyAdapter) IsL3Protocol(metadata *C.Metadata) bool {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.IsL3Protocol(metadata)
+ }
+ return false
+}
+
+func (r *refProxyAdapter) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
+ if r.proxyAdapter != nil {
+ return r.proxyAdapter.Unwrap(metadata, touch)
+ }
+ return nil
+}
+
+var _ C.ProxyAdapter = (*refProxyAdapter)(nil)
diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go
index d1d5e6b3..1f4e1580 100644
--- a/adapter/outboundgroup/fallback.go
+++ b/adapter/outboundgroup/fallback.go
@@ -37,16 +37,13 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
}
if N.NeedHandshake(c) {
- c = &callback.FirstWriteCallBackConn{
- Conn: c,
- Callback: func(err error) {
- if err == nil {
- f.onDialSuccess()
- } else {
- f.onDialFailed(proxy.Type(), err)
- }
- },
- }
+ c = callback.NewFirstWriteCallBackConn(c, func(err error) {
+ if err == nil {
+ f.onDialSuccess()
+ } else {
+ f.onDialFailed(proxy.Type(), err)
+ }
+ })
}
return c, err
@@ -73,6 +70,11 @@ func (f *Fallback) SupportUDP() bool {
return proxy.SupportUDP()
}
+// IsL3Protocol implements C.ProxyAdapter
+func (f *Fallback) IsL3Protocol(metadata *C.Metadata) bool {
+ return f.findAliveProxy(false).IsL3Protocol(metadata)
+}
+
// MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) {
all := []string{}
@@ -136,6 +138,10 @@ func (f *Fallback) Set(name string) error {
return nil
}
+func (f *Fallback) ForceSet(name string) {
+ f.selected = name
+}
+
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
return &Fallback{
GroupBase: NewGroupBase(GroupBaseOption{
diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go
index 0a421793..8e253e63 100644
--- a/adapter/outboundgroup/groupbase.go
+++ b/adapter/outboundgroup/groupbase.go
@@ -8,6 +8,7 @@ import (
"time"
"github.com/Dreamacro/clash/adapter/outbound"
+ "github.com/Dreamacro/clash/common/atomic"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
types "github.com/Dreamacro/clash/constant/provider"
@@ -15,7 +16,6 @@ import (
"github.com/Dreamacro/clash/tunnel"
"github.com/dlclark/regexp2"
- "go.uber.org/atomic"
)
type GroupBase struct {
diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go
index 1ed80496..607d4f4f 100644
--- a/adapter/outboundgroup/loadbalance.go
+++ b/adapter/outboundgroup/loadbalance.go
@@ -95,16 +95,13 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, op
}
if N.NeedHandshake(c) {
- c = &callback.FirstWriteCallBackConn{
- Conn: c,
- Callback: func(err error) {
- if err == nil {
- lb.onDialSuccess()
- } else {
- lb.onDialFailed(proxy.Type(), err)
- }
- },
- }
+ c = callback.NewFirstWriteCallBackConn(c, func(err error) {
+ if err == nil {
+ lb.onDialSuccess()
+ } else {
+ lb.onDialFailed(proxy.Type(), err)
+ }
+ })
}
return
@@ -127,6 +124,11 @@ func (lb *LoadBalance) SupportUDP() bool {
return !lb.disableUDP
}
+// IsL3Protocol implements C.ProxyAdapter
+func (lb *LoadBalance) IsL3Protocol(metadata *C.Metadata) bool {
+ return lb.Unwrap(metadata, false).IsL3Protocol(metadata)
+}
+
func strategyRoundRobin() strategyFn {
idx := 0
idxMutex := sync.Mutex{}
diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go
index e17ae132..ba733616 100644
--- a/adapter/outboundgroup/relay.go
+++ b/adapter/outboundgroup/relay.go
@@ -3,13 +3,9 @@ package outboundgroup
import (
"context"
"encoding/json"
- "net"
- "net/netip"
- "strings"
-
"github.com/Dreamacro/clash/adapter/outbound"
- N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
@@ -18,36 +14,6 @@ type Relay struct {
*GroupBase
}
-type proxyDialer struct {
- proxy C.Proxy
- dialer C.Dialer
-}
-
-func (p proxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
- currentMeta, err := addrToMetadata(address)
- if err != nil {
- return nil, err
- }
- if strings.Contains(network, "udp") { // should not support this operation
- currentMeta.NetWork = C.UDP
- pc, err := p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta)
- if err != nil {
- return nil, err
- }
- return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil
- }
- return p.proxy.DialContextWithDialer(ctx, p.dialer, currentMeta)
-}
-
-func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
- currentMeta, err := addrToMetadata(rAddrPort.String())
- if err != nil {
- return nil, err
- }
- currentMeta.NetWork = C.UDP
- return p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta)
-}
-
// DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
proxies, chainProxies := r.proxies(metadata, true)
@@ -61,10 +27,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
var d C.Dialer
d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
for _, proxy := range proxies[:len(proxies)-1] {
- d = proxyDialer{
- proxy: proxy,
- dialer: d,
- }
+ d = proxydialer.New(proxy, d, false)
}
last := proxies[len(proxies)-1]
conn, err := last.DialContextWithDialer(ctx, d, metadata)
@@ -95,10 +58,7 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
var d C.Dialer
d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
for _, proxy := range proxies[:len(proxies)-1] {
- d = proxyDialer{
- proxy: proxy,
- dialer: d,
- }
+ d = proxydialer.New(proxy, d, false)
}
last := proxies[len(proxies)-1]
pc, err := last.ListenPacketWithDialer(ctx, d, metadata)
@@ -129,7 +89,10 @@ func (r *Relay) SupportUDP() bool {
if proxy.SupportUOT() {
return true
}
- if !proxy.SupportWithDialer() {
+ switch proxy.SupportWithDialer() {
+ case C.ALLNet:
+ case C.UDP:
+ default: // C.TCP and C.InvalidNet
return false
}
}
diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go
index 6356d10e..96934f0c 100644
--- a/adapter/outboundgroup/selector.go
+++ b/adapter/outboundgroup/selector.go
@@ -44,6 +44,11 @@ func (s *Selector) SupportUDP() bool {
return s.selectedProxy(false).SupportUDP()
}
+// IsL3Protocol implements C.ProxyAdapter
+func (s *Selector) IsL3Protocol(metadata *C.Metadata) bool {
+ return s.selectedProxy(false).IsL3Protocol(metadata)
+}
+
// MarshalJSON implements C.ProxyAdapter
func (s *Selector) MarshalJSON() ([]byte, error) {
all := []string{}
@@ -73,6 +78,10 @@ func (s *Selector) Set(name string) error {
return errors.New("proxy not exist")
}
+func (s *Selector) ForceSet(name string) {
+ s.selected = name
+}
+
// Unwrap implements C.ProxyAdapter
func (s *Selector) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
return s.selectedProxy(touch)
diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go
index d340539c..5b0d2a17 100644
--- a/adapter/outboundgroup/urltest.go
+++ b/adapter/outboundgroup/urltest.go
@@ -3,6 +3,7 @@ package outboundgroup
import (
"context"
"encoding/json"
+ "errors"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
@@ -24,6 +25,8 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption {
type URLTest struct {
*GroupBase
+ selected string
+ testUrl string
tolerance uint16
disableUDP bool
fastNode C.Proxy
@@ -34,6 +37,26 @@ func (u *URLTest) Now() string {
return u.fast(false).Name()
}
+func (u *URLTest) Set(name string) error {
+ var p C.Proxy
+ for _, proxy := range u.GetProxies(false) {
+ if proxy.Name() == name {
+ p = proxy
+ break
+ }
+ }
+ if p == nil {
+ return errors.New("proxy not exist")
+ }
+ u.selected = name
+ u.fast(false)
+ return nil
+}
+
+func (u *URLTest) ForceSet(name string) {
+ u.selected = name
+}
+
// DialContext implements C.ProxyAdapter
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
proxy := u.fast(true)
@@ -45,16 +68,13 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
}
if N.NeedHandshake(c) {
- c = &callback.FirstWriteCallBackConn{
- Conn: c,
- Callback: func(err error) {
- if err == nil {
- u.onDialSuccess()
- } else {
- u.onDialFailed(proxy.Type(), err)
- }
- },
- }
+ c = callback.NewFirstWriteCallBackConn(c, func(err error) {
+ if err == nil {
+ u.onDialSuccess()
+ } else {
+ u.onDialFailed(proxy.Type(), err)
+ }
+ })
}
return c, err
@@ -77,16 +97,24 @@ func (u *URLTest) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
func (u *URLTest) fast(touch bool) C.Proxy {
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
+ var s C.Proxy
proxies := u.GetProxies(touch)
fast := proxies[0]
+ if fast.Name() == u.selected {
+ s = fast
+ }
min := fast.LastDelay()
fastNotExist := true
for _, proxy := range proxies[1:] {
+
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
fastNotExist = false
}
+ if proxy.Name() == u.selected {
+ s = proxy
+ }
if !proxy.Alive() {
continue
}
@@ -97,12 +125,15 @@ func (u *URLTest) fast(touch bool) C.Proxy {
min = delay
}
}
-
// tolerance
if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
u.fastNode = fast
}
-
+ if s != nil {
+ if s.Alive() && s.LastDelay() < fast.LastDelay()+u.tolerance {
+ u.fastNode = s
+ }
+ }
return u.fastNode, nil
})
if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again
@@ -117,10 +148,14 @@ func (u *URLTest) SupportUDP() bool {
if u.disableUDP {
return false
}
-
return u.fast(false).SupportUDP()
}
+// IsL3Protocol implements C.ProxyAdapter
+func (u *URLTest) IsL3Protocol(metadata *C.Metadata) bool {
+ return u.fast(false).IsL3Protocol(metadata)
+}
+
// MarshalJSON implements C.ProxyAdapter
func (u *URLTest) MarshalJSON() ([]byte, error) {
all := []string{}
@@ -164,6 +199,7 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o
}),
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
disableUDP: option.DisableUDP,
+ testUrl: option.URL,
}
for _, option := range options {
diff --git a/adapter/outboundgroup/util.go b/adapter/outboundgroup/util.go
index 578011f8..85373a1f 100644
--- a/adapter/outboundgroup/util.go
+++ b/adapter/outboundgroup/util.go
@@ -1,37 +1,10 @@
package outboundgroup
import (
- "fmt"
"net"
- "net/netip"
"time"
-
- C "github.com/Dreamacro/clash/constant"
)
-func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
- host, port, err := net.SplitHostPort(rawAddress)
- if err != nil {
- err = fmt.Errorf("addrToMetadata failed: %w", err)
- return
- }
-
- if ip, err := netip.ParseAddr(host); err != nil {
- addr = &C.Metadata{
- Host: host,
- DstPort: port,
- }
- } else {
- addr = &C.Metadata{
- Host: "",
- DstIP: ip.Unmap(),
- DstPort: port,
- }
- }
-
- return
-}
-
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
@@ -41,4 +14,5 @@ func tcpKeepAlive(c net.Conn) {
type SelectAble interface {
Set(string) error
+ ForceSet(name string)
}
diff --git a/adapter/parser.go b/adapter/parser.go
index 1f626f33..a561a1ed 100644
--- a/adapter/parser.go
+++ b/adapter/parser.go
@@ -114,5 +114,19 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
return nil, err
}
+ if muxMapping, muxExist := mapping["smux"].(map[string]any); muxExist {
+ muxOption := &outbound.SingMuxOption{}
+ err = decoder.Decode(muxMapping, muxOption)
+ if err != nil {
+ return nil, err
+ }
+ if muxOption.Enabled {
+ proxy, err = outbound.NewSingMux(*muxOption, proxy, proxy.(outbound.ProxyBase))
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
return NewProxy(proxy), nil
}
diff --git a/adapter/provider/healthcheck.go b/adapter/provider/healthcheck.go
index 9deb7b82..fa13e32e 100644
--- a/adapter/provider/healthcheck.go
+++ b/adapter/provider/healthcheck.go
@@ -4,13 +4,12 @@ import (
"context"
"time"
+ "github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/batch"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
-
- "go.uber.org/atomic"
)
const (
diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go
index fc5ed936..1df7f320 100644
--- a/adapter/provider/parser.go
+++ b/adapter/provider/parser.go
@@ -28,6 +28,7 @@ type proxyProviderSchema struct {
Filter string `provider:"filter,omitempty"`
ExcludeFilter string `provider:"exclude-filter,omitempty"`
ExcludeType string `provider:"exclude-type,omitempty"`
+ DialerProxy string `provider:"dialer-proxy,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
}
@@ -65,6 +66,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
filter := schema.Filter
excludeFilter := schema.ExcludeFilter
excludeType := schema.ExcludeType
+ dialerProxy := schema.DialerProxy
- return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, vehicle, hc)
+ return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, dialerProxy, vehicle, hc)
}
diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go
index 4a2cf7b8..4138c0de 100644
--- a/adapter/provider/provider.go
+++ b/adapter/provider/provider.go
@@ -17,6 +17,7 @@ import (
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log"
+ "github.com/Dreamacro/clash/tunnel/statistic"
"github.com/dlclark/regexp2"
"gopkg.in/yaml.v3"
@@ -81,6 +82,7 @@ func (pp *proxySetProvider) Initial() error {
}
pp.OnUpdate(elm)
pp.getSubscriptionInfo()
+ pp.closeAllConnections()
return nil
}
@@ -138,12 +140,24 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
}()
}
+func (pp *proxySetProvider) closeAllConnections() {
+ snapshot := statistic.DefaultManager.Snapshot()
+ for _, c := range snapshot.Connections {
+ for _, chain := range c.Chains() {
+ if chain == pp.Name() {
+ _ = c.Close()
+ break
+ }
+ }
+ }
+}
+
func stopProxyProvider(pd *ProxySetProvider) {
pd.healthCheck.close()
_ = pd.Fetcher.Destroy()
}
-func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
+func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
excludeFilterReg, err := regexp2.Compile(excludeFilter, 0)
if err != nil {
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
@@ -171,7 +185,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
healthCheck: hc,
}
- fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg), proxiesOnUpdate(pd))
+ fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy), proxiesOnUpdate(pd))
pd.Fetcher = fetcher
wrapper := &ProxySetProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyProvider)
@@ -267,7 +281,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
}
}
-func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] {
+func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string) resource.Parser[[]C.Proxy] {
return func(buf []byte) ([]C.Proxy, error) {
schema := &ProxySchema{}
@@ -330,6 +344,9 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
if _, ok := proxiesSet[name]; ok {
continue
}
+ if len(dialerProxy) > 0 {
+ mapping["dialer-proxy"] = dialerProxy
+ }
proxy, err := adapter.ParseProxy(mapping)
if err != nil {
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
diff --git a/common/atomic/type.go b/common/atomic/type.go
new file mode 100644
index 00000000..f1549235
--- /dev/null
+++ b/common/atomic/type.go
@@ -0,0 +1,205 @@
+package atomic
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "sync/atomic"
+)
+
+type Bool struct {
+ atomic.Bool
+}
+
+func NewBool(val bool) *Bool {
+ i := &Bool{}
+ i.Store(val)
+ return i
+}
+
+func (i *Bool) MarshalJSON() ([]byte, error) {
+ return json.Marshal(i.Load())
+}
+
+func (i *Bool) UnmarshalJSON(b []byte) error {
+ var v bool
+ if err := json.Unmarshal(b, &v); err != nil {
+ return err
+ }
+ i.Store(v)
+ return nil
+}
+
+func (i *Bool) String() string {
+ v := i.Load()
+ return strconv.FormatBool(v)
+}
+
+type Pointer[T any] struct {
+ atomic.Pointer[T]
+}
+
+func NewPointer[T any](v *T) *Pointer[T] {
+ var p Pointer[T]
+ if v != nil {
+ p.Store(v)
+ }
+ return &p
+}
+
+func (p *Pointer[T]) MarshalJSON() ([]byte, error) {
+ return json.Marshal(p.Load())
+}
+
+func (p *Pointer[T]) UnmarshalJSON(b []byte) error {
+ var v *T
+ if err := json.Unmarshal(b, &v); err != nil {
+ return err
+ }
+ p.Store(v)
+ return nil
+}
+
+func (p *Pointer[T]) String() string {
+ return fmt.Sprint(p.Load())
+}
+
+type Int32 struct {
+ atomic.Int32
+}
+
+func NewInt32(val int32) *Int32 {
+ i := &Int32{}
+ i.Store(val)
+ return i
+}
+
+func (i *Int32) MarshalJSON() ([]byte, error) {
+ return json.Marshal(i.Load())
+}
+
+func (i *Int32) UnmarshalJSON(b []byte) error {
+ var v int32
+ if err := json.Unmarshal(b, &v); err != nil {
+ return err
+ }
+ i.Store(v)
+ return nil
+}
+
+func (i *Int32) String() string {
+ v := i.Load()
+ return strconv.FormatInt(int64(v), 10)
+}
+
+type Int64 struct {
+ atomic.Int64
+}
+
+func NewInt64(val int64) *Int64 {
+ i := &Int64{}
+ i.Store(val)
+ return i
+}
+
+func (i *Int64) MarshalJSON() ([]byte, error) {
+ return json.Marshal(i.Load())
+}
+
+func (i *Int64) UnmarshalJSON(b []byte) error {
+ var v int64
+ if err := json.Unmarshal(b, &v); err != nil {
+ return err
+ }
+ i.Store(v)
+ return nil
+}
+
+func (i *Int64) String() string {
+ v := i.Load()
+ return strconv.FormatInt(int64(v), 10)
+}
+
+type Uint32 struct {
+ atomic.Uint32
+}
+
+func NewUint32(val uint32) *Uint32 {
+ i := &Uint32{}
+ i.Store(val)
+ return i
+}
+
+func (i *Uint32) MarshalJSON() ([]byte, error) {
+ return json.Marshal(i.Load())
+}
+
+func (i *Uint32) UnmarshalJSON(b []byte) error {
+ var v uint32
+ if err := json.Unmarshal(b, &v); err != nil {
+ return err
+ }
+ i.Store(v)
+ return nil
+}
+
+func (i *Uint32) String() string {
+ v := i.Load()
+ return strconv.FormatUint(uint64(v), 10)
+}
+
+type Uint64 struct {
+ atomic.Uint64
+}
+
+func NewUint64(val uint64) *Uint64 {
+ i := &Uint64{}
+ i.Store(val)
+ return i
+}
+
+func (i *Uint64) MarshalJSON() ([]byte, error) {
+ return json.Marshal(i.Load())
+}
+
+func (i *Uint64) UnmarshalJSON(b []byte) error {
+ var v uint64
+ if err := json.Unmarshal(b, &v); err != nil {
+ return err
+ }
+ i.Store(v)
+ return nil
+}
+
+func (i *Uint64) String() string {
+ v := i.Load()
+ return strconv.FormatUint(uint64(v), 10)
+}
+
+type Uintptr struct {
+ atomic.Uintptr
+}
+
+func NewUintptr(val uintptr) *Uintptr {
+ i := &Uintptr{}
+ i.Store(val)
+ return i
+}
+
+func (i *Uintptr) MarshalJSON() ([]byte, error) {
+ return json.Marshal(i.Load())
+}
+
+func (i *Uintptr) UnmarshalJSON(b []byte) error {
+ var v uintptr
+ if err := json.Unmarshal(b, &v); err != nil {
+ return err
+ }
+ i.Store(v)
+ return nil
+}
+
+func (i *Uintptr) String() string {
+ v := i.Load()
+ return strconv.FormatUint(uint64(v), 10)
+}
diff --git a/common/atomic/value.go b/common/atomic/value.go
new file mode 100644
index 00000000..ca0eb631
--- /dev/null
+++ b/common/atomic/value.go
@@ -0,0 +1,58 @@
+package atomic
+
+import (
+ "encoding/json"
+ "sync/atomic"
+)
+
+func DefaultValue[T any]() T {
+ var defaultValue T
+ return defaultValue
+}
+
+type TypedValue[T any] struct {
+ value atomic.Value
+}
+
+func (t *TypedValue[T]) Load() T {
+ value := t.value.Load()
+ if value == nil {
+ return DefaultValue[T]()
+ }
+ return value.(T)
+}
+
+func (t *TypedValue[T]) Store(value T) {
+ t.value.Store(value)
+}
+
+func (t *TypedValue[T]) Swap(new T) T {
+ old := t.value.Swap(new)
+ if old == nil {
+ return DefaultValue[T]()
+ }
+ return old.(T)
+}
+
+func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
+ return t.value.CompareAndSwap(old, new)
+}
+
+func (t *TypedValue[T]) MarshalJSON() ([]byte, error) {
+ return json.Marshal(t.Load())
+}
+
+func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
+ var v T
+ if err := json.Unmarshal(b, &v); err != nil {
+ return err
+ }
+ t.Store(v)
+ return nil
+}
+
+func NewTypedValue[T any](t T) *TypedValue[T] {
+ v := &TypedValue[T]{}
+ v.Store(t)
+ return v
+}
diff --git a/common/buf/sing.go b/common/buf/sing.go
index f86b5755..93140887 100644
--- a/common/buf/sing.go
+++ b/common/buf/sing.go
@@ -10,9 +10,11 @@ const BufferSize = buf.BufferSize
type Buffer = buf.Buffer
var New = buf.New
+var NewSize = buf.NewSize
var StackNew = buf.StackNew
var StackNewSize = buf.StackNewSize
var With = buf.With
+var As = buf.As
var KeepAlive = common.KeepAlive
@@ -21,5 +23,7 @@ func Dup[T any](obj T) T {
return common.Dup(obj)
}
-var Must = common.Must
-var Error = common.Error
+var (
+ Must = common.Must
+ Error = common.Error
+)
diff --git a/common/callback/callback.go b/common/callback/callback.go
index a0f1e717..fe76dc67 100644
--- a/common/callback/callback.go
+++ b/common/callback/callback.go
@@ -1,25 +1,55 @@
package callback
import (
+ "github.com/Dreamacro/clash/common/buf"
+ N "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant"
)
-type FirstWriteCallBackConn struct {
+type firstWriteCallBackConn struct {
C.Conn
- Callback func(error)
+ callback func(error)
written bool
}
-func (c *FirstWriteCallBackConn) Write(b []byte) (n int, err error) {
+func (c *firstWriteCallBackConn) Write(b []byte) (n int, err error) {
defer func() {
if !c.written {
c.written = true
- c.Callback(err)
+ c.callback(err)
}
}()
return c.Conn.Write(b)
}
-func (c *FirstWriteCallBackConn) Upstream() any {
+func (c *firstWriteCallBackConn) WriteBuffer(buffer *buf.Buffer) (err error) {
+ defer func() {
+ if !c.written {
+ c.written = true
+ c.callback(err)
+ }
+ }()
+ return c.Conn.WriteBuffer(buffer)
+}
+
+func (c *firstWriteCallBackConn) Upstream() any {
return c.Conn
}
+
+func (c *firstWriteCallBackConn) WriterReplaceable() bool {
+ return c.written
+}
+
+func (c *firstWriteCallBackConn) ReaderReplaceable() bool {
+ return true
+}
+
+var _ N.ExtendedConn = (*firstWriteCallBackConn)(nil)
+
+func NewFirstWriteCallBackConn(c C.Conn, callback func(error)) C.Conn {
+ return &firstWriteCallBackConn{
+ Conn: c,
+ callback: callback,
+ written: false,
+ }
+}
diff --git a/common/convert/converter.go b/common/convert/converter.go
index abd07a94..b67918db 100644
--- a/common/convert/converter.go
+++ b/common/convert/converter.go
@@ -5,10 +5,11 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
- "github.com/Dreamacro/clash/log"
"net/url"
"strconv"
"strings"
+
+ "github.com/Dreamacro/clash/log"
)
// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config
@@ -201,7 +202,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
vmess["servername"] = sni
}
- network := strings.ToLower(values["net"].(string))
+ network, _ := values["net"].(string)
+ network = strings.ToLower(network)
if values["type"] == "http" {
network = "http"
} else if network == "http" {
@@ -209,9 +211,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
}
vmess["network"] = network
- tls := strings.ToLower(values["tls"].(string))
- if strings.HasSuffix(tls, "tls") {
- vmess["tls"] = true
+ tls, ok := values["tls"].(string)
+ if ok {
+ tls = strings.ToLower(tls)
+ if strings.HasSuffix(tls, "tls") {
+ vmess["tls"] = true
+ }
}
switch network {
diff --git a/common/net/bufconn.go b/common/net/bufconn.go
index 8087608c..2ff73c82 100644
--- a/common/net/bufconn.go
+++ b/common/net/bufconn.go
@@ -69,6 +69,16 @@ func (c *BufferedConn) ReadBuffer(buffer *buf.Buffer) (err error) {
return c.ExtendedConn.ReadBuffer(buffer)
}
+func (c *BufferedConn) ReadCached() *buf.Buffer { // call in sing/common/bufio.Copy
+ if c.r.Buffered() > 0 {
+ length := c.r.Buffered()
+ b, _ := c.r.Peek(length)
+ _, _ = c.r.Discard(length)
+ return buf.As(b)
+ }
+ return nil
+}
+
func (c *BufferedConn) Upstream() any {
return c.ExtendedConn
}
@@ -79,3 +89,7 @@ func (c *BufferedConn) ReaderReplaceable() bool {
}
return true
}
+
+func (c *BufferedConn) WriterReplaceable() bool {
+ return true
+}
diff --git a/common/net/refconn.go b/common/net/refconn.go
index 324e6474..537cb839 100644
--- a/common/net/refconn.go
+++ b/common/net/refconn.go
@@ -4,10 +4,12 @@ import (
"net"
"runtime"
"time"
+
+ "github.com/Dreamacro/clash/common/buf"
)
type refConn struct {
- conn net.Conn
+ conn ExtendedConn
ref any
}
@@ -55,8 +57,28 @@ func (c *refConn) Upstream() any {
return c.conn
}
+func (c *refConn) ReadBuffer(buffer *buf.Buffer) error {
+ defer runtime.KeepAlive(c.ref)
+ return c.conn.ReadBuffer(buffer)
+}
+
+func (c *refConn) WriteBuffer(buffer *buf.Buffer) error {
+ defer runtime.KeepAlive(c.ref)
+ return c.conn.WriteBuffer(buffer)
+}
+
+func (c *refConn) ReaderReplaceable() bool { // Relay() will handle reference
+ return true
+}
+
+func (c *refConn) WriterReplaceable() bool { // Relay() will handle reference
+ return true
+}
+
+var _ ExtendedConn = (*refConn)(nil)
+
func NewRefConn(conn net.Conn, ref any) net.Conn {
- return &refConn{conn: conn, ref: ref}
+ return &refConn{conn: NewExtendedConn(conn), ref: ref}
}
type refPacketConn struct {
diff --git a/common/net/sing.go b/common/net/sing.go
index 5c980738..7eb92f03 100644
--- a/common/net/sing.go
+++ b/common/net/sing.go
@@ -3,9 +3,11 @@ package net
import (
"context"
"net"
+ "runtime"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
+ "github.com/sagernet/sing/common/bufio/deadline"
"github.com/sagernet/sing/common/network"
)
@@ -17,6 +19,14 @@ type ExtendedConn = network.ExtendedConn
type ExtendedWriter = network.ExtendedWriter
type ExtendedReader = network.ExtendedReader
+func NewDeadlineConn(conn net.Conn) ExtendedConn {
+ return deadline.NewFallbackConn(conn)
+}
+
+func NewDeadlinePacketConn(pc net.PacketConn) net.PacketConn {
+ return deadline.NewFallbackPacketConn(bufio.NewPacketConn(pc))
+}
+
func NeedHandshake(conn any) bool {
if earlyConn, isEarlyConn := common.Cast[network.EarlyConn](conn); isEarlyConn && earlyConn.NeedHandshake() {
return true
@@ -24,7 +34,11 @@ func NeedHandshake(conn any) bool {
return false
}
+type CountFunc = network.CountFunc
+
// Relay copies between left and right bidirectionally.
func Relay(leftConn, rightConn net.Conn) {
+ defer runtime.KeepAlive(leftConn)
+ defer runtime.KeepAlive(rightConn)
_ = bufio.CopyConn(context.TODO(), leftConn, rightConn)
}
diff --git a/common/observable/observable_test.go b/common/observable/observable_test.go
index 5459e0e2..5a02273d 100644
--- a/common/observable/observable_test.go
+++ b/common/observable/observable_test.go
@@ -5,8 +5,9 @@ import (
"testing"
"time"
+ "github.com/Dreamacro/clash/common/atomic"
+
"github.com/stretchr/testify/assert"
- "go.uber.org/atomic"
)
func iterator[T any](item []T) chan T {
@@ -44,7 +45,7 @@ func TestObservable_MultiSubscribe(t *testing.T) {
wg.Add(2)
waitCh := func(ch <-chan int) {
for range ch {
- count.Inc()
+ count.Add(1)
}
wg.Done()
}
diff --git a/common/pool/buffer_low_memory.go b/common/pool/buffer_low_memory.go
new file mode 100644
index 00000000..24e18a75
--- /dev/null
+++ b/common/pool/buffer_low_memory.go
@@ -0,0 +1,15 @@
+//go:build with_low_memory
+
+package pool
+
+const (
+ // io.Copy default buffer size is 32 KiB
+ // but the maximum packet size of vmess/shadowsocks is about 16 KiB
+ // so define a buffer of 20 KiB to reduce the memory of each TCP relay
+ RelayBufferSize = 16 * 1024
+
+ // RelayBufferSize uses 20KiB, but due to the allocator it will actually
+ // request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
+ // set to 9000, so the UDP Buffer size set to 16Kib
+ UDPBufferSize = 8 * 1024
+)
diff --git a/common/pool/buffer_standard.go b/common/pool/buffer_standard.go
new file mode 100644
index 00000000..ff758700
--- /dev/null
+++ b/common/pool/buffer_standard.go
@@ -0,0 +1,15 @@
+//go:build !with_low_memory
+
+package pool
+
+const (
+ // io.Copy default buffer size is 32 KiB
+ // but the maximum packet size of vmess/shadowsocks is about 16 KiB
+ // so define a buffer of 20 KiB to reduce the memory of each TCP relay
+ RelayBufferSize = 20 * 1024
+
+ // RelayBufferSize uses 20KiB, but due to the allocator it will actually
+ // request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
+ // set to 9000, so the UDP Buffer size set to 16Kib
+ UDPBufferSize = 16 * 1024
+)
diff --git a/common/pool/pool.go b/common/pool/pool.go
index bee4887f..288ea467 100644
--- a/common/pool/pool.go
+++ b/common/pool/pool.go
@@ -1,17 +1,5 @@
package pool
-const (
- // io.Copy default buffer size is 32 KiB
- // but the maximum packet size of vmess/shadowsocks is about 16 KiB
- // so define a buffer of 20 KiB to reduce the memory of each TCP relay
- RelayBufferSize = 20 * 1024
-
- // RelayBufferSize uses 20KiB, but due to the allocator it will actually
- // request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
- // set to 9000, so the UDP Buffer size set to 16Kib
- UDPBufferSize = 16 * 1024
-)
-
func Get(size int) []byte {
return defaultAllocator.Get(size)
}
diff --git a/common/singledo/singledo_test.go b/common/singledo/singledo_test.go
index 26e3d37d..9e114fb7 100644
--- a/common/singledo/singledo_test.go
+++ b/common/singledo/singledo_test.go
@@ -5,8 +5,9 @@ import (
"testing"
"time"
+ "github.com/Dreamacro/clash/common/atomic"
+
"github.com/stretchr/testify/assert"
- "go.uber.org/atomic"
)
func TestBasic(t *testing.T) {
@@ -26,7 +27,7 @@ func TestBasic(t *testing.T) {
go func() {
_, _, shard := single.Do(call)
if shard {
- shardCount.Inc()
+ shardCount.Add(1)
}
wg.Done()
}()
diff --git a/common/utils/strings.go b/common/utils/strings.go
new file mode 100644
index 00000000..5d5ae596
--- /dev/null
+++ b/common/utils/strings.go
@@ -0,0 +1,9 @@
+package utils
+
+func Reverse(s string) string {
+ a := []rune(s)
+ for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
+ a[i], a[j] = a[j], a[i]
+ }
+ return string(a)
+}
diff --git a/common/utils/uuid.go b/common/utils/uuid.go
index 930fda7d..f559b471 100644
--- a/common/utils/uuid.go
+++ b/common/utils/uuid.go
@@ -1,7 +1,7 @@
package utils
import (
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
"github.com/zhangyunhao116/fastrand"
)
diff --git a/common/utils/uuid_test.go b/common/utils/uuid_test.go
index ba00ea08..3e0507d8 100644
--- a/common/utils/uuid_test.go
+++ b/common/utils/uuid_test.go
@@ -1,7 +1,7 @@
package utils
import (
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
"reflect"
"testing"
)
diff --git a/component/dialer/bind_windows.go b/component/dialer/bind_windows.go
index 4a099169..0d38d1c5 100644
--- a/component/dialer/bind_windows.go
+++ b/component/dialer/bind_windows.go
@@ -3,6 +3,7 @@ package dialer
import (
"context"
"encoding/binary"
+ "fmt"
"net"
"net/netip"
"syscall"
@@ -20,11 +21,19 @@ func bind4(handle syscall.Handle, ifaceIdx int) error {
var bytes [4]byte
binary.BigEndian.PutUint32(bytes[:], uint32(ifaceIdx))
idx := *(*uint32)(unsafe.Pointer(&bytes[0]))
- return syscall.SetsockoptInt(handle, syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx))
+ err := syscall.SetsockoptInt(handle, syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx))
+ if err != nil {
+ err = fmt.Errorf("bind4: %w", err)
+ }
+ return err
}
func bind6(handle syscall.Handle, ifaceIdx int) error {
- return syscall.SetsockoptInt(handle, syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, ifaceIdx)
+ err := syscall.SetsockoptInt(handle, syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, ifaceIdx)
+ if err != nil {
+ err = fmt.Errorf("bind6: %w", err)
+ }
+ return err
}
func bindControl(ifaceIdx int) controlFn {
@@ -49,9 +58,9 @@ func bindControl(ifaceIdx int) controlFn {
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil {
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
if bind4err != nil {
- innerErr = bind6err
+ innerErr = fmt.Errorf("%w (%s)", bind6err, bind4err)
} else {
- innerErr = bind4err
+ innerErr = nil
}
} else {
innerErr = bind6err
diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go
index d70e9173..5a55cd22 100644
--- a/component/dialer/dialer.go
+++ b/component/dialer/dialer.go
@@ -157,7 +157,7 @@ func concurrentDualStackDialContext(ctx context.Context, network string, ips []n
}
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
- ipv4s, ipv6s := sortationAddr(ips)
+ ipv4s, ipv6s := resolver.SortationAddr(ips)
preferIPVersion := opt.prefer
fallbackTicker := time.NewTicker(fallbackTimeout)
@@ -309,27 +309,16 @@ func parseAddr(ctx context.Context, network, address string, preferResolver reso
return ips, port, nil
}
-func sortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
- for _, v := range ips {
- if v.Is4() { // 4in6 parse was in parseAddr
- ipv4s = append(ipv4s, v)
- } else {
- ipv6s = append(ipv6s, v)
- }
- }
- return
-}
-
type Dialer struct {
- opt option
+ Opt option
}
func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
- return DialContext(ctx, network, address, WithOption(d.opt))
+ return DialContext(ctx, network, address, WithOption(d.Opt))
}
func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
- opt := WithOption(d.opt)
+ opt := WithOption(d.Opt)
if rAddrPort.Addr().Unmap().IsLoopback() {
// avoid "The requested address is not valid in its context."
opt = WithInterface("")
@@ -339,5 +328,5 @@ func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddr
func NewDialer(options ...Option) Dialer {
opt := applyOptions(options...)
- return Dialer{opt: *opt}
+ return Dialer{Opt: *opt}
}
diff --git a/component/dialer/options.go b/component/dialer/options.go
index 372a2e63..096c7a5c 100644
--- a/component/dialer/options.go
+++ b/component/dialer/options.go
@@ -4,14 +4,13 @@ import (
"context"
"net"
+ "github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/resolver"
-
- "go.uber.org/atomic"
)
var (
DefaultOptions []Option
- DefaultInterface = atomic.NewString("")
+ DefaultInterface = atomic.NewTypedValue[string]("")
DefaultRoutingMark = atomic.NewInt32(0)
)
diff --git a/component/dialer/tfo.go b/component/dialer/tfo.go
index 0041a976..09d5cced 100644
--- a/component/dialer/tfo.go
+++ b/component/dialer/tfo.go
@@ -105,10 +105,22 @@ func (c *tfoConn) Upstream() any {
return c.Conn
}
+func (c *tfoConn) NeedAdditionalReadDeadline() bool {
+ return c.Conn == nil
+}
+
func (c *tfoConn) NeedHandshake() bool {
return c.Conn == nil
}
+func (c *tfoConn) ReaderReplaceable() bool {
+ return c.Conn != nil
+}
+
+func (c *tfoConn) WriterReplaceable() bool {
+ return c.Conn != nil
+}
+
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
ctx, cancel := context.WithCancel(ctx)
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
diff --git a/component/geodata/init.go b/component/geodata/init.go
index f7dd7a9e..acae1a34 100644
--- a/component/geodata/init.go
+++ b/component/geodata/init.go
@@ -1,13 +1,17 @@
package geodata
import (
+ "context"
"fmt"
- "github.com/Dreamacro/clash/component/mmdb"
- C "github.com/Dreamacro/clash/constant"
- "github.com/Dreamacro/clash/log"
"io"
"net/http"
"os"
+ "time"
+
+ clashHttp "github.com/Dreamacro/clash/component/http"
+ "github.com/Dreamacro/clash/component/mmdb"
+ C "github.com/Dreamacro/clash/constant"
+ "github.com/Dreamacro/clash/log"
)
var initGeoSite bool
@@ -38,7 +42,9 @@ func InitGeoSite() error {
}
func downloadGeoSite(path string) (err error) {
- resp, err := http.Get(C.GeoSiteUrl)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
+ defer cancel()
+ resp, err := clashHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil {
return
}
@@ -55,7 +61,9 @@ func downloadGeoSite(path string) (err error) {
}
func downloadGeoIP(path string) (err error) {
- resp, err := http.Get(C.GeoIpUrl)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
+ defer cancel()
+ resp, err := clashHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil {
return
}
diff --git a/component/mmdb/mmdb.go b/component/mmdb/mmdb.go
index 8f28d486..14f6f997 100644
--- a/component/mmdb/mmdb.go
+++ b/component/mmdb/mmdb.go
@@ -1,14 +1,18 @@
package mmdb
import (
- "github.com/oschwald/geoip2-golang"
+ "context"
"io"
"net/http"
"os"
"sync"
+ "time"
+ clashHttp "github.com/Dreamacro/clash/component/http"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
+
+ "github.com/oschwald/geoip2-golang"
)
var (
@@ -47,7 +51,9 @@ func Instance() *geoip2.Reader {
}
func DownloadMMDB(path string) (err error) {
- resp, err := http.Get(C.MmdbUrl)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
+ defer cancel()
+ resp, err := clashHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil {
return
}
diff --git a/component/profile/profile.go b/component/profile/profile.go
index e3d9e78c..aa6df2f7 100644
--- a/component/profile/profile.go
+++ b/component/profile/profile.go
@@ -1,7 +1,7 @@
package profile
import (
- "go.uber.org/atomic"
+ "github.com/Dreamacro/clash/common/atomic"
)
// StoreSelected is a global switch for storing selected proxy to cache
diff --git a/component/proxydialer/proxydialer.go b/component/proxydialer/proxydialer.go
new file mode 100644
index 00000000..fc5cb294
--- /dev/null
+++ b/component/proxydialer/proxydialer.go
@@ -0,0 +1,96 @@
+package proxydialer
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net"
+ "net/netip"
+ "strings"
+
+ N "github.com/Dreamacro/clash/common/net"
+ "github.com/Dreamacro/clash/component/dialer"
+ "github.com/Dreamacro/clash/component/resolver"
+ C "github.com/Dreamacro/clash/constant"
+ "github.com/Dreamacro/clash/tunnel"
+ "github.com/Dreamacro/clash/tunnel/statistic"
+)
+
+type proxyDialer struct {
+ proxy C.ProxyAdapter
+ dialer C.Dialer
+ statistic bool
+}
+
+func New(proxy C.ProxyAdapter, dialer C.Dialer, statistic bool) C.Dialer {
+ return proxyDialer{proxy: proxy, dialer: dialer, statistic: statistic}
+}
+
+func NewByName(proxyName string, dialer C.Dialer) (C.Dialer, error) {
+ proxies := tunnel.Proxies()
+ if proxy, ok := proxies[proxyName]; ok {
+ return New(proxy, dialer, true), nil
+ }
+ return nil, fmt.Errorf("proxyName[%s] not found", proxyName)
+}
+
+func (p proxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
+ currentMeta := &C.Metadata{Type: C.INNER}
+ if err := currentMeta.SetRemoteAddress(address); err != nil {
+ return nil, err
+ }
+ if strings.Contains(network, "udp") { // using in wireguard outbound
+ if !currentMeta.Resolved() {
+ ip, err := resolver.ResolveIP(ctx, currentMeta.Host)
+ if err != nil {
+ return nil, errors.New("can't resolve ip")
+ }
+ currentMeta.DstIP = ip
+ }
+ pc, err := p.listenPacket(ctx, currentMeta)
+ if err != nil {
+ return nil, err
+ }
+ return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil
+ }
+ var conn C.Conn
+ var err error
+ if d, ok := p.dialer.(dialer.Dialer); ok { // first using old function to let mux work
+ conn, err = p.proxy.DialContext(ctx, currentMeta, dialer.WithOption(d.Opt))
+ } else {
+ conn, err = p.proxy.DialContextWithDialer(ctx, p.dialer, currentMeta)
+ }
+ if err != nil {
+ return nil, err
+ }
+ if p.statistic {
+ conn = statistic.NewTCPTracker(conn, statistic.DefaultManager, currentMeta, nil, 0, 0, false)
+ }
+ return conn, err
+}
+
+func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
+ currentMeta := &C.Metadata{Type: C.INNER}
+ if err := currentMeta.SetRemoteAddress(address); err != nil {
+ return nil, err
+ }
+ return p.listenPacket(ctx, currentMeta)
+}
+
+func (p proxyDialer) listenPacket(ctx context.Context, currentMeta *C.Metadata) (C.PacketConn, error) {
+ var pc C.PacketConn
+ var err error
+ currentMeta.NetWork = C.UDP
+ if d, ok := p.dialer.(dialer.Dialer); ok { // first using old function to let mux work
+ pc, err = p.proxy.ListenPacketContext(ctx, currentMeta, dialer.WithOption(d.Opt))
+ } else {
+ pc, err = p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta)
+ }
+ if err != nil {
+ return nil, err
+ }
+ if p.statistic {
+ pc = statistic.NewUDPTracker(pc, statistic.DefaultManager, currentMeta, nil, 0, 0, false)
+ }
+ return pc, nil
+}
diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go
index f5872ad7..6be6a95f 100644
--- a/component/resolver/resolver.go
+++ b/component/resolver/resolver.go
@@ -44,10 +44,8 @@ type Resolver interface {
LookupIP(ctx context.Context, host string) (ips []netip.Addr, err error)
LookupIPv4(ctx context.Context, host string) (ips []netip.Addr, err error)
LookupIPv6(ctx context.Context, host string) (ips []netip.Addr, err error)
- ResolveIP(ctx context.Context, host string) (ip netip.Addr, err error)
- ResolveIPv4(ctx context.Context, host string) (ip netip.Addr, err error)
- ResolveIPv6(ctx context.Context, host string) (ip netip.Addr, err error)
ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error)
+ Invalid() bool
}
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver
@@ -68,7 +66,7 @@ func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]net
return []netip.Addr{}, ErrIPVersion
}
- if r != nil {
+ if r != nil && r.Invalid() {
return r.LookupIPv4(ctx, host)
}
@@ -124,7 +122,7 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
return nil, ErrIPVersion
}
- if r != nil {
+ if r != nil && r.Invalid() {
return r.LookupIPv6(ctx, host)
}
@@ -164,7 +162,7 @@ func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip
return node.IPs, nil
}
- if r != nil {
+ if r != nil && r.Invalid() {
if DisableIPv6 {
return r.LookupIPv4(ctx, host)
}
@@ -200,10 +198,14 @@ func ResolveIPWithResolver(ctx context.Context, host string, r Resolver) (netip.
} else if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
}
- return ips[fastrand.Intn(len(ips))], nil
+ ipv4s, ipv6s := SortationAddr(ips)
+ if len(ipv4s) > 0 {
+ return ipv4s[fastrand.Intn(len(ipv4s))], nil
+ }
+ return ipv6s[fastrand.Intn(len(ipv6s))], nil
}
-// ResolveIP with a host, return ip
+// ResolveIP with a host, return ip and priority return TypeA
func ResolveIP(ctx context.Context, host string) (netip.Addr, error) {
return ResolveIPWithResolver(ctx, host, DefaultResolver)
}
@@ -264,3 +266,14 @@ func LookupIPProxyServerHost(ctx context.Context, host string) ([]netip.Addr, er
}
return LookupIP(ctx, host)
}
+
+func SortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
+ for _, v := range ips {
+ if v.Unmap().Is4() {
+ ipv4s = append(ipv4s, v)
+ } else {
+ ipv6s = append(ipv6s, v)
+ }
+ }
+ return
+}
diff --git a/component/sniffer/dispatcher.go b/component/sniffer/dispatcher.go
index 97d448ce..fa1c6827 100644
--- a/component/sniffer/dispatcher.go
+++ b/component/sniffer/dispatcher.go
@@ -26,18 +26,18 @@ var (
var Dispatcher *SnifferDispatcher
type SnifferDispatcher struct {
- enable bool
- sniffers map[sniffer.Sniffer]SnifferConfig
- forceDomain *trie.DomainTrie[struct{}]
- skipSNI *trie.DomainTrie[struct{}]
- skipList *cache.LruCache[string, uint8]
- rwMux sync.RWMutex
- forceDnsMapping bool
- parsePureIp bool
+ enable bool
+ sniffers map[sniffer.Sniffer]SnifferConfig
+ forceDomain *trie.DomainSet
+ skipSNI *trie.DomainSet
+ skipList *cache.LruCache[string, uint8]
+ rwMux sync.RWMutex
+ forceDnsMapping bool
+ parsePureIp bool
}
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) {
- if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Search(metadata.Host) != nil || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
+ if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
port, err := strconv.ParseUint(metadata.DstPort, 10, 16)
if err != nil {
log.Debugln("[Sniffer] Dst port is error")
@@ -74,7 +74,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
return
} else {
- if sd.skipSNI.Search(host) != nil {
+ if sd.skipSNI.Has(host) {
log.Debugln("[Sniffer] Skip sni[%s]", host)
return
}
@@ -166,8 +166,8 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
return &dispatcher, nil
}
-func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig, forceDomain *trie.DomainTrie[struct{}],
- skipSNI *trie.DomainTrie[struct{}],
+func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig,
+ forceDomain *trie.DomainSet, skipSNI *trie.DomainSet,
forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{
enable: true,
diff --git a/component/tls/config.go b/component/tls/config.go
index 91b89f1d..b5b56591 100644
--- a/component/tls/config.go
+++ b/component/tls/config.go
@@ -15,7 +15,7 @@ import (
)
var trustCerts []*x509.Certificate
-
+var certPool *x509.CertPool
var mutex sync.RWMutex
var errNotMacth error = errors.New("certificate fingerprints do not match")
@@ -40,10 +40,20 @@ func ResetCertificate() {
}
func getCertPool() *x509.CertPool {
- certPool, err := x509.SystemCertPool()
- if err == nil {
- for _, cert := range trustCerts {
- certPool.AddCert(cert)
+ if len(trustCerts) == 0 {
+ return nil
+ }
+ if certPool == nil {
+ mutex.Lock()
+ defer mutex.Unlock()
+ if certPool != nil {
+ return certPool
+ }
+ certPool, err := x509.SystemCertPool()
+ if err == nil {
+ for _, cert := range trustCerts {
+ certPool.AddCert(cert)
+ }
}
}
return certPool
diff --git a/component/trie/domain.go b/component/trie/domain.go
index d9463c6e..3decbb02 100644
--- a/component/trie/domain.go
+++ b/component/trie/domain.go
@@ -25,7 +25,7 @@ func ValidAndSplitDomain(domain string) ([]string, bool) {
if domain != "" && domain[len(domain)-1] == '.' {
return nil, false
}
-
+ domain = strings.ToLower(domain)
parts := strings.Split(domain, domainStep)
if len(parts) == 1 {
if parts[0] == "" {
@@ -123,6 +123,33 @@ func (t *DomainTrie[T]) Optimize() {
t.root.optimize()
}
+func (t *DomainTrie[T]) Foreach(print func(domain string, data T)) {
+ for key, data := range t.root.getChildren() {
+ recursion([]string{key}, data, print)
+ if data != nil && data.inited {
+ print(joinDomain([]string{key}), data.data)
+ }
+ }
+}
+
+func recursion[T any](items []string, node *Node[T], fn func(domain string, data T)) {
+ for key, data := range node.getChildren() {
+ newItems := append([]string{key}, items...)
+ if data != nil && data.inited {
+ domain := joinDomain(newItems)
+ if domain[0] == domainStepByte {
+ domain = complexWildcard + domain
+ }
+ fn(domain, data.Data())
+ }
+ recursion(newItems, data, fn)
+ }
+}
+
+func joinDomain(items []string) string {
+ return strings.Join(items, domainStep)
+}
+
// New returns a new, empty Trie.
func New[T any]() *DomainTrie[T] {
return &DomainTrie[T]{root: newNode[T]()}
diff --git a/component/trie/domain_set.go b/component/trie/domain_set.go
new file mode 100644
index 00000000..be793ad3
--- /dev/null
+++ b/component/trie/domain_set.go
@@ -0,0 +1,174 @@
+package trie
+
+// Package succinct provides several succinct data types.
+// Modify from https://github.com/openacid/succinct/blob/d4684c35d123f7528b14e03c24327231723db704/sskv.go
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/Dreamacro/clash/common/utils"
+ "github.com/openacid/low/bitmap"
+)
+
+const (
+ complexWildcardByte = byte('+')
+ wildcardByte = byte('*')
+ domainStepByte = byte('.')
+)
+
+type DomainSet struct {
+ leaves, labelBitmap []uint64
+ labels []byte
+ ranks, selects []int32
+}
+
+// NewDomainSet creates a new *DomainSet struct, from a DomainTrie.
+func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
+ reserveDomains := make([]string, 0)
+ t.Foreach(func(domain string, data T) {
+ reserveDomains = append(reserveDomains, utils.Reverse(domain))
+ })
+ // ensure that the same prefix is continuous
+ // and according to the ascending sequence of length
+ sort.Strings(reserveDomains)
+ keys := reserveDomains
+ if len(keys) == 0 {
+ return nil
+ }
+ ss := &DomainSet{}
+ lIdx := 0
+
+ type qElt struct{ s, e, col int }
+ queue := []qElt{{0, len(keys), 0}}
+ for i := 0; i < len(queue); i++ {
+ elt := queue[i]
+ if elt.col == len(keys[elt.s]) {
+ elt.s++
+ // a leaf node
+ setBit(&ss.leaves, i, 1)
+ }
+
+ for j := elt.s; j < elt.e; {
+
+ frm := j
+
+ for ; j < elt.e && keys[j][elt.col] == keys[frm][elt.col]; j++ {
+ }
+ queue = append(queue, qElt{frm, j, elt.col + 1})
+ ss.labels = append(ss.labels, keys[frm][elt.col])
+ setBit(&ss.labelBitmap, lIdx, 0)
+ lIdx++
+ }
+ setBit(&ss.labelBitmap, lIdx, 1)
+ lIdx++
+ }
+
+ ss.init()
+ return ss
+}
+
+// Has query for a key and return whether it presents in the DomainSet.
+func (ss *DomainSet) Has(key string) bool {
+ if ss == nil {
+ return false
+ }
+ key = utils.Reverse(key)
+ key = strings.ToLower(key)
+ // no more labels in this node
+ // skip character matching
+ // go to next level
+ nodeId, bmIdx := 0, 0
+ type wildcardCursor struct {
+ bmIdx, index int
+ }
+ stack := make([]wildcardCursor, 0)
+ for i := 0; i < len(key); i++ {
+ RESTART:
+ c := key[i]
+ for ; ; bmIdx++ {
+ if getBit(ss.labelBitmap, bmIdx) != 0 {
+ if len(stack) > 0 {
+ cursor := stack[len(stack)-1]
+ stack = stack[0 : len(stack)-1]
+ // back wildcard and find next node
+ nextNodeId := countZeros(ss.labelBitmap, ss.ranks, cursor.bmIdx+1)
+ nextBmIdx := selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nextNodeId-1) + 1
+ j := cursor.index
+ for ; j < len(key) && key[j] != domainStepByte; j++ {
+ }
+ if j == len(key) {
+ if getBit(ss.leaves, nextNodeId) != 0 {
+ return true
+ } else {
+ goto RESTART
+ }
+ }
+ for ; ; nextBmIdx++ {
+ if nextBmIdx-nextNodeId < len(ss.labels) && ss.labels[nextBmIdx-nextNodeId] == domainStepByte {
+ bmIdx = nextBmIdx
+ nodeId = nextNodeId
+ i = j
+ goto RESTART
+ }
+ }
+ }
+ return false
+ }
+ // handle wildcard for domain
+ if ss.labels[bmIdx-nodeId] == complexWildcardByte {
+ return true
+ } else if ss.labels[bmIdx-nodeId] == wildcardByte {
+ cursor := wildcardCursor{}
+ cursor.bmIdx = bmIdx
+ cursor.index = i
+ stack = append(stack, cursor)
+ } else if ss.labels[bmIdx-nodeId] == c {
+ break
+ }
+ }
+ nodeId = countZeros(ss.labelBitmap, ss.ranks, bmIdx+1)
+ bmIdx = selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nodeId-1) + 1
+ }
+
+ return getBit(ss.leaves, nodeId) != 0
+
+}
+
+func setBit(bm *[]uint64, i int, v int) {
+ for i>>6 >= len(*bm) {
+ *bm = append(*bm, 0)
+ }
+ (*bm)[i>>6] |= uint64(v) << uint(i&63)
+}
+
+func getBit(bm []uint64, i int) uint64 {
+ return bm[i>>6] & (1 << uint(i&63))
+}
+
+// init builds pre-calculated cache to speed up rank() and select()
+func (ss *DomainSet) init() {
+ ss.selects, ss.ranks = bitmap.IndexSelect32R64(ss.labelBitmap)
+}
+
+// countZeros counts the number of "0" in a bitmap before the i-th bit(excluding
+// the i-th bit) on behalf of rank index.
+// E.g.:
+//
+// countZeros("010010", 4) == 3
+// // 012345
+func countZeros(bm []uint64, ranks []int32, i int) int {
+ a, _ := bitmap.Rank64(bm, ranks, int32(i))
+ return i - int(a)
+}
+
+// selectIthOne returns the index of the i-th "1" in a bitmap, on behalf of rank
+// and select indexes.
+// E.g.:
+//
+// selectIthOne("010010", 1) == 4
+// // 012345
+func selectIthOne(bm []uint64, ranks, selects []int32, i int) int {
+ a, _ := bitmap.Select32R64(bm, selects, ranks, int32(i))
+ return int(a)
+}
diff --git a/component/trie/domain_set_test.go b/component/trie/domain_set_test.go
new file mode 100644
index 00000000..9e0e0b70
--- /dev/null
+++ b/component/trie/domain_set_test.go
@@ -0,0 +1,85 @@
+package trie_test
+
+import (
+ "testing"
+
+ "github.com/Dreamacro/clash/component/trie"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDomainSet(t *testing.T) {
+ tree := trie.New[struct{}]()
+ domainSet := []string{
+ "baidu.com",
+ "google.com",
+ "www.google.com",
+ "test.a.net",
+ "test.a.oc",
+ "Mijia Cloud",
+ ".qq.com",
+ "+.cn",
+ }
+
+ for _, domain := range domainSet {
+ assert.NoError(t, tree.Insert(domain, struct{}{}))
+ }
+ set := tree.NewDomainSet()
+ assert.NotNil(t, set)
+ assert.True(t, set.Has("test.cn"))
+ assert.True(t, set.Has("cn"))
+ assert.True(t, set.Has("Mijia Cloud"))
+ assert.True(t, set.Has("test.a.net"))
+ assert.True(t, set.Has("www.qq.com"))
+ assert.True(t, set.Has("google.com"))
+ assert.False(t, set.Has("qq.com"))
+ assert.False(t, set.Has("www.baidu.com"))
+}
+
+func TestDomainSetComplexWildcard(t *testing.T) {
+ tree := trie.New[struct{}]()
+ domainSet := []string{
+ "+.baidu.com",
+ "+.a.baidu.com",
+ "www.baidu.com",
+ "+.bb.baidu.com",
+ "test.a.net",
+ "test.a.oc",
+ "www.qq.com",
+ }
+
+ for _, domain := range domainSet {
+ assert.NoError(t, tree.Insert(domain, struct{}{}))
+ }
+ set := tree.NewDomainSet()
+ assert.NotNil(t, set)
+ assert.False(t, set.Has("google.com"))
+ assert.True(t, set.Has("www.baidu.com"))
+ assert.True(t, set.Has("test.test.baidu.com"))
+}
+
+func TestDomainSetWildcard(t *testing.T) {
+ tree := trie.New[struct{}]()
+ domainSet := []string{
+ "*.*.*.baidu.com",
+ "www.baidu.*",
+ "stun.*.*",
+ "*.*.qq.com",
+ "test.*.baidu.com",
+ "*.apple.com",
+ }
+
+ for _, domain := range domainSet {
+ assert.NoError(t, tree.Insert(domain, struct{}{}))
+ }
+ set := tree.NewDomainSet()
+ assert.NotNil(t, set)
+ assert.True(t, set.Has("www.baidu.com"))
+ assert.True(t, set.Has("test.test.baidu.com"))
+ assert.True(t, set.Has("test.test.qq.com"))
+ assert.True(t, set.Has("stun.ab.cd"))
+ assert.False(t, set.Has("test.baidu.com"))
+ assert.False(t, set.Has("www.google.com"))
+ assert.False(t, set.Has("a.www.google.com"))
+ assert.False(t, set.Has("test.qq.com"))
+ assert.False(t, set.Has("test.test.test.qq.com"))
+}
diff --git a/component/trie/domain_test.go b/component/trie/domain_test.go
index c54b3d3b..976055a9 100644
--- a/component/trie/domain_test.go
+++ b/component/trie/domain_test.go
@@ -1,16 +1,17 @@
-package trie
+package trie_test
import (
"net/netip"
"testing"
+ "github.com/Dreamacro/clash/component/trie"
"github.com/stretchr/testify/assert"
)
var localIP = netip.AddrFrom4([4]byte{127, 0, 0, 1})
func TestTrie_Basic(t *testing.T) {
- tree := New[netip.Addr]()
+ tree := trie.New[netip.Addr]()
domains := []string{
"example.com",
"google.com",
@@ -18,7 +19,7 @@ func TestTrie_Basic(t *testing.T) {
}
for _, domain := range domains {
- tree.Insert(domain, localIP)
+ assert.NoError(t, tree.Insert(domain, localIP))
}
node := tree.Search("example.com")
@@ -31,7 +32,7 @@ func TestTrie_Basic(t *testing.T) {
}
func TestTrie_Wildcard(t *testing.T) {
- tree := New[netip.Addr]()
+ tree := trie.New[netip.Addr]()
domains := []string{
"*.example.com",
"sub.*.example.com",
@@ -47,7 +48,7 @@ func TestTrie_Wildcard(t *testing.T) {
}
for _, domain := range domains {
- tree.Insert(domain, localIP)
+ assert.NoError(t, tree.Insert(domain, localIP))
}
assert.NotNil(t, tree.Search("sub.example.com"))
@@ -64,7 +65,7 @@ func TestTrie_Wildcard(t *testing.T) {
}
func TestTrie_Priority(t *testing.T) {
- tree := New[int]()
+ tree := trie.New[int]()
domains := []string{
".dev",
"example.dev",
@@ -79,7 +80,7 @@ func TestTrie_Priority(t *testing.T) {
}
for idx, domain := range domains {
- tree.Insert(domain, idx+1)
+ assert.NoError(t, tree.Insert(domain, idx+1))
}
assertFn("test.dev", 1)
@@ -90,8 +91,8 @@ func TestTrie_Priority(t *testing.T) {
}
func TestTrie_Boundary(t *testing.T) {
- tree := New[netip.Addr]()
- tree.Insert("*.dev", localIP)
+ tree := trie.New[netip.Addr]()
+ assert.NoError(t, tree.Insert("*.dev", localIP))
assert.NotNil(t, tree.Insert(".", localIP))
assert.NotNil(t, tree.Insert("..dev", localIP))
@@ -99,9 +100,29 @@ func TestTrie_Boundary(t *testing.T) {
}
func TestTrie_WildcardBoundary(t *testing.T) {
- tree := New[netip.Addr]()
- tree.Insert("+.*", localIP)
- tree.Insert("stun.*.*.*", localIP)
+ tree := trie.New[netip.Addr]()
+ assert.NoError(t, tree.Insert("+.*", localIP))
+ assert.NoError(t, tree.Insert("stun.*.*.*", localIP))
assert.NotNil(t, tree.Search("example.com"))
}
+
+func TestTrie_Foreach(t *testing.T) {
+ tree := trie.New[netip.Addr]()
+ domainList := []string{
+ "google.com",
+ "stun.*.*.*",
+ "test.*.google.com",
+ "+.baidu.com",
+ "*.baidu.com",
+ "*.*.baidu.com",
+ }
+ for _, domain := range domainList {
+ assert.NoError(t, tree.Insert(domain, localIP))
+ }
+ count := 0
+ tree.Foreach(func(domain string, data netip.Addr) {
+ count++
+ })
+ assert.Equal(t, 7, count)
+}
diff --git a/component/trie/node.go b/component/trie/node.go
index e19b40ac..3aa2bc7d 100644
--- a/component/trie/node.go
+++ b/component/trie/node.go
@@ -116,6 +116,18 @@ func (n *Node[T]) setData(data T) {
n.inited = true
}
+func (n *Node[T]) getChildren() map[string]*Node[T] {
+ if n.childMap == nil {
+ if n.childNode != nil {
+ m := make(map[string]*Node[T])
+ m[n.childStr] = n.childNode
+ return m
+ }
+ } else {
+ return n.childMap
+ }
+ return nil
+}
func (n *Node[T]) Data() T {
return n.data
}
diff --git a/config/config.go b/config/config.go
index c407aad5..24594ee2 100644
--- a/config/config.go
+++ b/config/config.go
@@ -9,7 +9,6 @@ import (
"net/url"
"os"
"regexp"
- "runtime"
"strconv"
"strings"
"time"
@@ -136,9 +135,8 @@ type IPTables struct {
type Sniffer struct {
Enable bool
Sniffers map[snifferTypes.Type]SNIFF.SnifferConfig
- Reverses *trie.DomainTrie[struct{}]
- ForceDomain *trie.DomainTrie[struct{}]
- SkipDomain *trie.DomainTrie[struct{}]
+ ForceDomain *trie.DomainSet
+ SkipDomain *trie.DomainSet
ForceDnsMapping bool
ParsePureIp bool
}
@@ -490,7 +488,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
config.Hosts = hosts
- dnsCfg, err := parseDNS(rawCfg, hosts, rules)
+ dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders)
if err != nil {
return nil, err
}
@@ -822,8 +820,6 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[s
rules = append(rules, parsed)
}
- runtime.GC()
-
return rules, nil
}
@@ -900,7 +896,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
}
- proxyAdapter := u.Fragment
+ proxyName := u.Fragment
var addr, dnsNetType string
params := map[string]string{}
@@ -917,7 +913,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
case "https":
addr, err = hostWithDefaultPort(u.Host, "443")
if err == nil {
- proxyAdapter = ""
+ proxyName = ""
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path}
addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS
@@ -927,7 +923,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
if len(arr) == 0 {
continue
} else if len(arr) == 1 {
- proxyAdapter = arr[0]
+ proxyName = arr[0]
} else if len(arr) == 2 {
params[arr[0]] = arr[1]
} else {
@@ -953,18 +949,24 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
nameservers = append(
nameservers,
dns.NameServer{
- Net: dnsNetType,
- Addr: addr,
- ProxyAdapter: proxyAdapter,
- Interface: dialer.DefaultInterface,
- Params: params,
- PreferH3: preferH3,
+ Net: dnsNetType,
+ Addr: addr,
+ ProxyName: proxyName,
+ Interface: dialer.DefaultInterface,
+ Params: params,
+ PreferH3: preferH3,
},
)
}
return nameservers, nil
}
+func init() {
+ dns.ParseNameServer = func(servers []string) ([]dns.NameServer, error) { // using by wireguard
+ return parseNameServer(servers, false)
+ }
+}
+
func parsePureDNSServer(server string) string {
addPre := func(server string) string {
return "udp://" + server
@@ -983,7 +985,7 @@ func parsePureDNSServer(server string) string {
}
}
}
-func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][]dns.NameServer, error) {
+func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]providerTypes.RuleProvider, preferH3 bool) (map[string][]dns.NameServer, error) {
policy := map[string][]dns.NameServer{}
updatedPolicy := make(map[string]interface{})
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
@@ -998,6 +1000,14 @@ func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][
newKey := "geosite:" + subkey
updatedPolicy[newKey] = v
}
+ } else if strings.Contains(k, "rule-set:") {
+ subkeys := strings.Split(k, ":")
+ subkeys = subkeys[1:]
+ subkeys = strings.Split(subkeys[0], ",")
+ for _, subkey := range subkeys {
+ newKey := "rule-set:" + subkey
+ updatedPolicy[newKey] = v
+ }
} else if re.MatchString(k) {
subkeys := strings.Split(k, ",")
for _, subkey := range subkeys {
@@ -1021,6 +1031,19 @@ func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
}
+ if strings.HasPrefix(domain, "rule-set:") {
+ domainSetName := domain[9:]
+ if provider, ok := ruleProviders[domainSetName]; !ok {
+ return nil, fmt.Errorf("not found rule-set: %s", domainSetName)
+ } else {
+ switch provider.Behavior() {
+ case providerTypes.IPCIDR:
+ return nil, fmt.Errorf("rule provider type error, except domain,actual %s", provider.Behavior())
+ case providerTypes.Classical:
+ log.Warnln("%s provider is %s, only matching it contain domain rule", provider.Name(), provider.Behavior())
+ }
+ }
+ }
policy[domain] = nameservers
}
@@ -1073,11 +1096,10 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
log.Infoln("Start initial GeoSite dns fallback filter `%s`, records: %d", country, recordsCount)
}
}
- runtime.GC()
return sites, nil
}
-func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule) (*DNS, error) {
+func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
cfg := rawCfg.DNS
if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
@@ -1104,7 +1126,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
return nil, err
}
- if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, cfg.PreferH3); err != nil {
+ if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.PreferH3); err != nil {
return nil, err
}
@@ -1324,23 +1346,24 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
}
sniffer.Sniffers = loadSniffer
- sniffer.ForceDomain = trie.New[struct{}]()
- for _, domain := range snifferRaw.ForceDomain {
- err := sniffer.ForceDomain.Insert(domain, struct{}{})
- if err != nil {
- return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
- }
- }
- sniffer.ForceDomain.Optimize()
- sniffer.SkipDomain = trie.New[struct{}]()
- for _, domain := range snifferRaw.SkipDomain {
- err := sniffer.SkipDomain.Insert(domain, struct{}{})
+ forceDomainTrie := trie.New[struct{}]()
+ for _, domain := range snifferRaw.ForceDomain {
+ err := forceDomainTrie.Insert(domain, struct{}{})
if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
}
}
- sniffer.SkipDomain.Optimize()
+ sniffer.ForceDomain = forceDomainTrie.NewDomainSet()
+
+ skipDomainTrie := trie.New[struct{}]()
+ for _, domain := range snifferRaw.SkipDomain {
+ err := skipDomainTrie.Insert(domain, struct{}{})
+ if err != nil {
+ return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
+ }
+ }
+ sniffer.SkipDomain = skipDomainTrie.NewDomainSet()
return sniffer, nil
}
diff --git a/config/updateGeo.go b/config/updateGeo.go
index 698bd52d..e76301ba 100644
--- a/config/updateGeo.go
+++ b/config/updateGeo.go
@@ -1,15 +1,20 @@
package config
import (
+ "context"
"fmt"
- "github.com/Dreamacro/clash/component/geodata"
- _ "github.com/Dreamacro/clash/component/geodata/standard"
- C "github.com/Dreamacro/clash/constant"
- "github.com/oschwald/geoip2-golang"
"io"
"net/http"
"os"
"runtime"
+ "time"
+
+ "github.com/Dreamacro/clash/component/geodata"
+ _ "github.com/Dreamacro/clash/component/geodata/standard"
+ clashHttp "github.com/Dreamacro/clash/component/http"
+ C "github.com/Dreamacro/clash/constant"
+
+ "github.com/oschwald/geoip2-golang"
)
func UpdateGeoDatabases() error {
@@ -69,7 +74,9 @@ func UpdateGeoDatabases() error {
}
func downloadForBytes(url string) ([]byte, error) {
- resp, err := http.Get(url)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
+ defer cancel()
+ resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil {
return nil, err
}
diff --git a/config/utils.go b/config/utils.go
index 2c470618..799082c4 100644
--- a/config/utils.go
+++ b/config/utils.go
@@ -3,6 +3,7 @@ package config
import (
"fmt"
"net"
+ "net/netip"
"strings"
"github.com/Dreamacro/clash/adapter/outboundgroup"
@@ -149,20 +150,11 @@ func proxyGroupsDagSort(groupsConfig []map[string]any) error {
}
func verifyIP6() bool {
- addrs, err := net.InterfaceAddrs()
- if err != nil {
- return false
- }
- for _, addr := range addrs {
- ipNet, isIpNet := addr.(*net.IPNet)
- if isIpNet && !ipNet.IP.IsLoopback() {
- if ipNet.IP.To16() != nil {
- s := ipNet.IP.String()
- for i := 0; i < len(s); i++ {
- switch s[i] {
- case ':':
- return true
- }
+ if iAddrs, err := net.InterfaceAddrs(); err == nil {
+ for _, addr := range iAddrs {
+ if prefix, err := netip.ParsePrefix(addr.String()); err == nil {
+ if addr := prefix.Addr().Unmap(); addr.Is6() && addr.IsGlobalUnicast() {
+ return true
}
}
}
diff --git a/constant/adapters.go b/constant/adapters.go
index bf5f7fdb..2a2c68c1 100644
--- a/constant/adapters.go
+++ b/constant/adapters.go
@@ -2,12 +2,14 @@ package constant
import (
"context"
+ "errors"
"fmt"
"net"
"net/netip"
"sync"
"time"
+ N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
)
@@ -43,6 +45,8 @@ const (
DefaultTLSTimeout = DefaultTCPTimeout
)
+var ErrNotSupport = errors.New("no support")
+
type Connection interface {
Chains() Chain
AppendToChains(adapter ProxyAdapter)
@@ -72,7 +76,7 @@ func (c Chain) Last() string {
}
type Conn interface {
- net.Conn
+ N.ExtendedConn
Connection
}
@@ -116,10 +120,13 @@ type ProxyAdapter interface {
// SupportUOT return UDP over TCP support
SupportUOT() bool
- SupportWithDialer() bool
+ SupportWithDialer() NetWork
DialContextWithDialer(ctx context.Context, dialer Dialer, metadata *Metadata) (Conn, error)
ListenPacketWithDialer(ctx context.Context, dialer Dialer, metadata *Metadata) (PacketConn, error)
+ // IsL3Protocol return ProxyAdapter working in L3 (tell dns module not pass the domain to avoid loopback)
+ IsL3Protocol(metadata *Metadata) bool
+
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
Unwrap(metadata *Metadata, touch bool) Proxy
}
diff --git a/constant/context.go b/constant/context.go
index da1e4155..1c70124b 100644
--- a/constant/context.go
+++ b/constant/context.go
@@ -5,7 +5,7 @@ import (
N "github.com/Dreamacro/clash/common/net"
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
)
type PlainContext interface {
diff --git a/constant/features/low_memory.go b/constant/features/low_memory.go
new file mode 100644
index 00000000..0d252113
--- /dev/null
+++ b/constant/features/low_memory.go
@@ -0,0 +1,6 @@
+//go:build with_low_memory
+package features
+
+func init() {
+ TAGS = append(TAGS, "with_low_memory")
+}
diff --git a/constant/features/no_doq.go b/constant/features/no_doq.go
deleted file mode 100644
index c915272f..00000000
--- a/constant/features/no_doq.go
+++ /dev/null
@@ -1,7 +0,0 @@
-//go:build no_doq
-
-package features
-
-func init() {
- TAGS = append(TAGS, "no_doq")
-}
diff --git a/constant/features/no_fake_tcp.go b/constant/features/no_fake_tcp.go
new file mode 100644
index 00000000..f536a066
--- /dev/null
+++ b/constant/features/no_fake_tcp.go
@@ -0,0 +1,7 @@
+//go:build no_fake_tcp
+
+package features
+
+func init() {
+ TAGS = append(TAGS, "no_fake_tcp")
+}
diff --git a/constant/features/no_gvisor.go b/constant/features/no_gvisor.go
deleted file mode 100644
index d0d5391a..00000000
--- a/constant/features/no_gvisor.go
+++ /dev/null
@@ -1,7 +0,0 @@
-//go:build no_gvisor
-
-package features
-
-func init() {
- TAGS = append(TAGS, "no_gvisor")
-}
diff --git a/constant/features/with_gvisor.go b/constant/features/with_gvisor.go
new file mode 100644
index 00000000..1b3417b3
--- /dev/null
+++ b/constant/features/with_gvisor.go
@@ -0,0 +1,7 @@
+//go:build with_gvisor
+
+package features
+
+func init() {
+ TAGS = append(TAGS, "with_gvisor")
+}
diff --git a/constant/metadata.go b/constant/metadata.go
index 599a6055..1c344d5d 100644
--- a/constant/metadata.go
+++ b/constant/metadata.go
@@ -15,7 +15,10 @@ const (
TCP NetWork = iota
UDP
ALLNet
+ InvalidNet = 0xff
+)
+const (
HTTP Type = iota
HTTPS
SOCKS4
@@ -33,12 +36,16 @@ const (
type NetWork int
func (n NetWork) String() string {
- if n == TCP {
+ switch n {
+ case TCP:
return "tcp"
- } else if n == UDP {
+ case UDP:
return "udp"
+ case ALLNet:
+ return "all"
+ default:
+ return "invalid"
}
- return "all"
}
func (n NetWork) MarshalJSON() ([]byte, error) {
@@ -222,3 +229,21 @@ func (m *Metadata) String() string {
func (m *Metadata) Valid() bool {
return m.Host != "" || m.DstIP.IsValid()
}
+
+func (m *Metadata) SetRemoteAddress(rawAddress string) error {
+ host, port, err := net.SplitHostPort(rawAddress)
+ if err != nil {
+ return err
+ }
+
+ if ip, err := netip.ParseAddr(host); err != nil {
+ m.Host = host
+ m.DstIP = netip.Addr{}
+ } else {
+ m.Host = ""
+ m.DstIP = ip.Unmap()
+ }
+ m.DstPort = port
+
+ return nil
+}
diff --git a/constant/provider/interface.go b/constant/provider/interface.go
index b42bbe71..fb5efd87 100644
--- a/constant/provider/interface.go
+++ b/constant/provider/interface.go
@@ -73,17 +73,27 @@ type ProxyProvider interface {
Version() uint32
}
-// Rule Type
+// RuleProvider interface
+type RuleProvider interface {
+ Provider
+ Behavior() RuleBehavior
+ Match(*constant.Metadata) bool
+ ShouldResolveIP() bool
+ ShouldFindProcess() bool
+ AsRule(adaptor string) constant.Rule
+}
+
+// Rule Behavior
const (
- Domain RuleType = iota
+ Domain RuleBehavior = iota
IPCIDR
Classical
)
-// RuleType defined
-type RuleType int
+// RuleBehavior defined
+type RuleBehavior int
-func (rt RuleType) String() string {
+func (rt RuleBehavior) String() string {
switch rt {
case Domain:
return "Domain"
@@ -96,12 +106,20 @@ func (rt RuleType) String() string {
}
}
-// RuleProvider interface
-type RuleProvider interface {
- Provider
- Behavior() RuleType
- Match(*constant.Metadata) bool
- ShouldResolveIP() bool
- ShouldFindProcess() bool
- AsRule(adaptor string) constant.Rule
+const (
+ YamlRule RuleFormat = iota
+ TextRule
+)
+
+type RuleFormat int
+
+func (rf RuleFormat) String() string {
+ switch rf {
+ case YamlRule:
+ return "YamlRule"
+ case TextRule:
+ return "TextRule"
+ default:
+ return "Unknown"
+ }
}
diff --git a/context/conn.go b/context/conn.go
index c5477780..afeed852 100644
--- a/context/conn.go
+++ b/context/conn.go
@@ -7,7 +7,7 @@ import (
N "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant"
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
)
type ConnContext struct {
diff --git a/context/dns.go b/context/dns.go
index c41de724..ae29154f 100644
--- a/context/dns.go
+++ b/context/dns.go
@@ -4,7 +4,7 @@ import (
"context"
"github.com/Dreamacro/clash/common/utils"
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
"github.com/miekg/dns"
)
diff --git a/context/packetconn.go b/context/packetconn.go
index b9afef41..d695bae5 100644
--- a/context/packetconn.go
+++ b/context/packetconn.go
@@ -6,7 +6,7 @@ import (
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant"
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
)
type PacketConnContext struct {
diff --git a/dns/client.go b/dns/client.go
index 936a5882..ba83412b 100644
--- a/dns/client.go
+++ b/dns/client.go
@@ -8,11 +8,11 @@ import (
"net/netip"
"strings"
- tlsC "github.com/Dreamacro/clash/component/tls"
- "go.uber.org/atomic"
-
+ "github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
+ tlsC "github.com/Dreamacro/clash/component/tls"
+ C "github.com/Dreamacro/clash/constant"
D "github.com/miekg/dns"
"github.com/zhangyunhao116/fastrand"
@@ -23,8 +23,9 @@ type client struct {
r *Resolver
port string
host string
- iface *atomic.String
- proxyAdapter string
+ iface *atomic.TypedValue[string]
+ proxyAdapter C.ProxyAdapter
+ proxyName string
addr string
}
@@ -81,7 +82,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
options = append(options, dialer.WithInterface(c.iface.Load()))
}
- conn, err := getDialHandler(c.r, c.proxyAdapter, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port))
+ conn, err := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port))
if err != nil {
return nil, err
}
diff --git a/dns/dhcp.go b/dns/dhcp.go
index 151e4421..a6c1df76 100644
--- a/dns/dhcp.go
+++ b/dns/dhcp.go
@@ -8,8 +8,7 @@ import (
"sync"
"time"
- "go.uber.org/atomic"
-
+ "github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/dhcp"
"github.com/Dreamacro/clash/component/iface"
"github.com/Dreamacro/clash/component/resolver"
@@ -86,7 +85,7 @@ func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
for _, item := range dns {
nameserver = append(nameserver, NameServer{
Addr: net.JoinHostPort(item.String(), "53"),
- Interface: atomic.NewString(d.ifaceName),
+ Interface: atomic.NewTypedValue(d.ifaceName),
})
}
diff --git a/dns/doh.go b/dns/doh.go
index 1e6528d9..dd8ba435 100644
--- a/dns/doh.go
+++ b/dns/doh.go
@@ -21,6 +21,7 @@ import (
"github.com/metacubex/quic-go"
"github.com/metacubex/quic-go/http3"
D "github.com/miekg/dns"
+ "golang.org/x/exp/slices"
"golang.org/x/net/http2"
)
@@ -63,7 +64,8 @@ type dnsOverHTTPS struct {
url *url.URL
r *Resolver
httpVersions []C.HTTPVersion
- proxyAdapter string
+ proxyAdapter C.ProxyAdapter
+ proxyName string
addr string
}
@@ -71,7 +73,7 @@ type dnsOverHTTPS struct {
var _ dnsClient = (*dnsOverHTTPS)(nil)
// newDoH returns the DNS-over-HTTPS Upstream.
-func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[string]string, proxyAdapter string) dnsClient {
+func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) dnsClient {
u, _ := url.Parse(urlString)
httpVersions := DefaultHTTPVersions
if preferH3 {
@@ -87,6 +89,7 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
addr: u.String(),
r: r,
proxyAdapter: proxyAdapter,
+ proxyName: proxyName,
quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod,
TokenStore: newQUICTokenStore(),
@@ -390,14 +393,17 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
nextProtos = append(nextProtos, string(v))
}
tlsConfig.NextProtos = nextProtos
- dialContext := getDialHandler(doh.r, doh.proxyAdapter)
- // First, we attempt to create an HTTP3 transport. If the probe QUIC
- // connection is established successfully, we'll be using HTTP3 for this
- // upstream.
- transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext)
- if err == nil {
- log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String())
- return transportH3, nil
+ dialContext := getDialHandler(doh.r, doh.proxyAdapter, doh.proxyName)
+
+ if slices.Contains(doh.httpVersions, C.HTTPVersion3) {
+ // First, we attempt to create an HTTP3 transport. If the probe QUIC
+ // connection is established successfully, we'll be using HTTP3 for this
+ // upstream.
+ transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext)
+ if err == nil {
+ log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String())
+ return transportH3, nil
+ }
}
log.Debugln("[%s] using HTTP/2 for this upstream: %v", doh.url.String(), err)
@@ -533,7 +539,7 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
IP: net.ParseIP(ip),
Port: portInt,
}
- conn, err := listenPacket(ctx, doh.proxyAdapter, "udp", addr, doh.r)
+ conn, err := listenPacket(ctx, doh.proxyAdapter, doh.proxyName, "udp", addr, doh.r)
if err != nil {
return nil, err
}
diff --git a/dns/doq.go b/dns/doq.go
index 1354f177..73310340 100644
--- a/dns/doq.go
+++ b/dns/doq.go
@@ -13,9 +13,10 @@ import (
"time"
tlsC "github.com/Dreamacro/clash/component/tls"
+ C "github.com/Dreamacro/clash/constant"
+ "github.com/Dreamacro/clash/log"
"github.com/metacubex/quic-go"
- "github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
)
@@ -60,7 +61,8 @@ type dnsOverQUIC struct {
bytesPoolGuard sync.Mutex
addr string
- proxyAdapter string
+ proxyAdapter C.ProxyAdapter
+ proxyName string
r *Resolver
}
@@ -68,10 +70,11 @@ type dnsOverQUIC struct {
var _ dnsClient = (*dnsOverQUIC)(nil)
// newDoQ returns the DNS-over-QUIC Upstream.
-func newDoQ(resolver *Resolver, addr string, adapter string) (dnsClient, error) {
+func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) {
doq := &dnsOverQUIC{
addr: addr,
- proxyAdapter: adapter,
+ proxyAdapter: proxyAdapter,
+ proxyName: proxyName,
r: resolver,
quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod,
@@ -310,7 +313,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
// we're using bootstrapped address instead of what's passed to the function
// it does not create an actual connection, but it helps us determine
// what IP is actually reachable (when there're v4/v6 addresses).
- rawConn, err := getDialHandler(doq.r, doq.proxyAdapter)(ctx, "udp", doq.addr)
+ rawConn, err := getDialHandler(doq.r, doq.proxyAdapter, doq.proxyName)(ctx, "udp", doq.addr)
if err != nil {
return nil, fmt.Errorf("failed to open a QUIC connection: %w", err)
}
@@ -325,7 +328,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
p, err := strconv.Atoi(port)
udpAddr := net.UDPAddr{IP: net.ParseIP(ip), Port: p}
- udp, err := listenPacket(ctx, doq.proxyAdapter, "udp", addr, doq.r)
+ udp, err := listenPacket(ctx, doq.proxyAdapter, doq.proxyName, "udp", addr, doq.r)
if err != nil {
return nil, err
}
diff --git a/dns/resolver.go b/dns/resolver.go
index c16aad40..7e1b007d 100644
--- a/dns/resolver.go
+++ b/dns/resolver.go
@@ -3,23 +3,21 @@ package dns
import (
"context"
"errors"
- "fmt"
"net/netip"
"strings"
"time"
- "go.uber.org/atomic"
-
+ "github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata/router"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant"
+ "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
- "github.com/zhangyunhao116/fastrand"
"golang.org/x/sync/singleflight"
)
@@ -40,6 +38,11 @@ type geositePolicyRecord struct {
inversedMatching bool
}
+type domainSetPolicyRecord struct {
+ domainSetProvider provider.RuleProvider
+ policy *Policy
+}
+
type Resolver struct {
ipv6 bool
ipv6Timeout time.Duration
@@ -51,6 +54,7 @@ type Resolver struct {
group singleflight.Group
lruCache *cache.LruCache[string, *D.Msg]
policy *trie.DomainTrie[*Policy]
+ domainSetPolicy []domainSetPolicyRecord
geositePolicy []geositePolicyRecord
proxyServer []dnsClient
}
@@ -93,7 +97,7 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ips []netip.Addr,
ips, err = r.lookupIP(ctx, host, D.TypeA)
var waitIPv6 *time.Timer
- if r != nil {
+ if r != nil && r.ipv6Timeout > 0 {
waitIPv6 = time.NewTimer(r.ipv6Timeout)
} else {
waitIPv6 = time.NewTimer(100 * time.Millisecond)
@@ -112,49 +116,16 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ips []netip.Addr,
return ips, nil
}
-// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
-func (r *Resolver) ResolveIP(ctx context.Context, host string) (ip netip.Addr, err error) {
- ips, err := r.LookupIPPrimaryIPv4(ctx, host)
- if err != nil {
- return netip.Addr{}, err
- } else if len(ips) == 0 {
- return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
- }
- return ips[fastrand.Intn(len(ips))], nil
-}
-
// LookupIPv4 request with TypeA
func (r *Resolver) LookupIPv4(ctx context.Context, host string) ([]netip.Addr, error) {
return r.lookupIP(ctx, host, D.TypeA)
}
-// ResolveIPv4 request with TypeA
-func (r *Resolver) ResolveIPv4(ctx context.Context, host string) (ip netip.Addr, err error) {
- ips, err := r.lookupIP(ctx, host, D.TypeA)
- if err != nil {
- return netip.Addr{}, err
- } else if len(ips) == 0 {
- return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
- }
- return ips[fastrand.Intn(len(ips))], nil
-}
-
// LookupIPv6 request with TypeAAAA
func (r *Resolver) LookupIPv6(ctx context.Context, host string) ([]netip.Addr, error) {
return r.lookupIP(ctx, host, D.TypeAAAA)
}
-// ResolveIPv6 request with TypeAAAA
-func (r *Resolver) ResolveIPv6(ctx context.Context, host string) (ip netip.Addr, err error) {
- ips, err := r.lookupIP(ctx, host, D.TypeAAAA)
- if err != nil {
- return netip.Addr{}, err
- } else if len(ips) == 0 {
- return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
- }
- return ips[fastrand.Intn(len(ips))], nil
-}
-
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
for _, filter := range r.fallbackIPFilters {
if filter.Match(ip) {
@@ -301,6 +272,12 @@ func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
return geositeRecord.policy.GetData()
}
}
+ metadata := &C.Metadata{Host: domain}
+ for _, domainSetRecord := range r.domainSetPolicy {
+ if ok := domainSetRecord.domainSetProvider.Match(metadata); ok {
+ return domainSetRecord.policy.GetData()
+ }
+ }
return nil
}
@@ -399,16 +376,20 @@ func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D
return ch
}
-// HasProxyServer has proxy server dns client
-func (r *Resolver) HasProxyServer() bool {
+// Invalid return this resolver can or can't be used
+func (r *Resolver) Invalid() bool {
+ if r == nil {
+ return false
+ }
return len(r.main) > 0
}
type NameServer struct {
Net string
Addr string
- Interface *atomic.String
- ProxyAdapter string
+ Interface *atomic.TypedValue[string]
+ ProxyAdapter C.ProxyAdapter
+ ProxyName string
Params map[string]string
PreferH3 bool
}
@@ -422,16 +403,18 @@ type FallbackFilter struct {
}
type Config struct {
- Main, Fallback []NameServer
- Default []NameServer
- ProxyServer []NameServer
- IPv6 bool
- IPv6Timeout uint
- EnhancedMode C.DNSMode
- FallbackFilter FallbackFilter
- Pool *fakeip.Pool
- Hosts *trie.DomainTrie[resolver.HostValue]
- Policy map[string][]NameServer
+ Main, Fallback []NameServer
+ Default []NameServer
+ ProxyServer []NameServer
+ IPv6 bool
+ IPv6Timeout uint
+ EnhancedMode C.DNSMode
+ FallbackFilter FallbackFilter
+ Pool *fakeip.Pool
+ Hosts *trie.DomainTrie[resolver.HostValue]
+ Policy map[string][]NameServer
+ DomainSetPolicy map[provider.RuleProvider][]NameServer
+ GeositePolicy map[router.DomainMatcher][]NameServer
}
func NewResolver(config Config) *Resolver {
@@ -483,6 +466,14 @@ func NewResolver(config Config) *Resolver {
}
r.policy.Optimize()
}
+ if len(config.DomainSetPolicy) > 0 {
+ for p, n := range config.DomainSetPolicy {
+ r.domainSetPolicy = append(r.domainSetPolicy, domainSetPolicyRecord{
+ domainSetProvider: p,
+ policy: NewPolicy(transform(n, defaultResolver)),
+ })
+ }
+ }
fallbackIPFilters := []fallbackIPFilter{}
if config.FallbackFilter.GeoIP {
@@ -521,3 +512,5 @@ func NewProxyServerHostResolver(old *Resolver) *Resolver {
}
return r
}
+
+var ParseNameServer func(servers []string) ([]NameServer, error) // define in config/config.go
diff --git a/dns/util.go b/dns/util.go
index 4821195d..bfd2e9ed 100644
--- a/dns/util.go
+++ b/dns/util.go
@@ -74,13 +74,13 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
for _, s := range servers {
switch s.Net {
case "https":
- ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter))
+ ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName))
continue
case "dhcp":
ret = append(ret, newDHCPClient(s.Addr))
continue
case "quic":
- if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter); err == nil {
+ if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter, s.ProxyName); err == nil {
ret = append(ret, doq)
} else {
log.Fatalln("DoQ format error: %v", err)
@@ -103,6 +103,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
iface: s.Interface,
r: resolver,
proxyAdapter: s.ProxyAdapter,
+ proxyName: s.ProxyName,
})
}
return ret
@@ -144,9 +145,9 @@ func msgToDomain(msg *D.Msg) string {
type dialHandler func(ctx context.Context, network, addr string) (net.Conn, error)
-func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dialHandler {
+func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string, opts ...dialer.Option) dialHandler {
return func(ctx context.Context, network, addr string) (net.Conn, error) {
- if len(proxyAdapter) == 0 {
+ if len(proxyName) == 0 && proxyAdapter == nil {
opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...)
} else {
@@ -154,10 +155,14 @@ func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dia
if err != nil {
return nil, err
}
- adapter, ok := tunnel.Proxies()[proxyAdapter]
- if !ok {
- opts = append(opts, dialer.WithInterface(proxyAdapter))
+ if proxyAdapter == nil {
+ var ok bool
+ proxyAdapter, ok = tunnel.Proxies()[proxyName]
+ if !ok {
+ opts = append(opts, dialer.WithInterface(proxyName))
+ }
}
+
if strings.Contains(network, "tcp") {
// tcp can resolve host by remote
metadata := &C.Metadata{
@@ -165,8 +170,16 @@ func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dia
Host: host,
DstPort: port,
}
- if ok {
- return adapter.DialContext(ctx, metadata, opts...)
+ if proxyAdapter != nil {
+ if proxyAdapter.IsL3Protocol(metadata) { // L3 proxy should resolve domain before to avoid loopback
+ dstIP, err := resolver.ResolveIPWithResolver(ctx, host, r)
+ if err != nil {
+ return nil, err
+ }
+ metadata.Host = ""
+ metadata.DstIP = dstIP
+ }
+ return proxyAdapter.DialContext(ctx, metadata, opts...)
}
opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...)
@@ -182,15 +195,15 @@ func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dia
DstIP: dstIP,
DstPort: port,
}
- if !ok {
+ if proxyAdapter == nil {
return dialer.DialContext(ctx, network, addr, opts...)
}
- if !adapter.SupportUDP() {
+ if !proxyAdapter.SupportUDP() {
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter)
}
- packetConn, err := adapter.ListenPacketContext(ctx, metadata, opts...)
+ packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
if err != nil {
return nil, err
}
@@ -201,14 +214,17 @@ func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dia
}
}
-func listenPacket(ctx context.Context, proxyAdapter string, network string, addr string, r *Resolver, opts ...dialer.Option) (net.PacketConn, error) {
+func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName string, network string, addr string, r *Resolver, opts ...dialer.Option) (net.PacketConn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
- adapter, ok := tunnel.Proxies()[proxyAdapter]
- if !ok && len(proxyAdapter) != 0 {
- opts = append(opts, dialer.WithInterface(proxyAdapter))
+ if proxyAdapter == nil {
+ var ok bool
+ proxyAdapter, ok = tunnel.Proxies()[proxyName]
+ if !ok {
+ opts = append(opts, dialer.WithInterface(proxyName))
+ }
}
// udp must resolve host first
@@ -222,15 +238,15 @@ func listenPacket(ctx context.Context, proxyAdapter string, network string, addr
DstIP: dstIP,
DstPort: port,
}
- if !ok {
+ if proxyAdapter == nil {
return dialer.ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", opts...)
}
- if !adapter.SupportUDP() {
+ if !proxyAdapter.SupportUDP() {
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter)
}
- return adapter.ListenPacketContext(ctx, metadata, opts...)
+ return proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
}
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
diff --git a/docs/allocs.svg b/docs/allocs.svg
deleted file mode 100644
index 57deec50..00000000
--- a/docs/allocs.svg
+++ /dev/null
@@ -1,2387 +0,0 @@
-
-
-
-
-
diff --git a/docs/config.yaml b/docs/config.yaml
index 3ef09342..97bd40ec 100644
--- a/docs/config.yaml
+++ b/docs/config.yaml
@@ -238,6 +238,9 @@ dns:
- https://doh.pub/dns-query
- https://dns.alidns.com/dns-query
"www.baidu.com,+.google.cn": [223.5.5.5, https://dns.alidns.com/dns-query]
+ ## global,dns 为 rule-providers 中的名为 global 和 dns 规则订阅,
+ ## 且 behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则
+ # "rule-set:global,dns": 8.8.8.8
proxies: # socks5
- name: "socks"
@@ -299,6 +302,15 @@ proxies: # socks5
# UDP 则为双栈解析,获取结果中的第一个 IPv4
# ipv6-prefer 同 ipv4-prefer
# 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效
+ smux:
+ enabled: false
+ protocol: smux # smux/yamux/h2mux
+ # max-connections: 4 # Maximum connections. Conflict with max-streams.
+ # min-streams: 4 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
+ # max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
+ # padding: false # Enable padding. Requires sing-box server version 1.3-beta9 or later.
+ # statistic: false # 控制是否将底层连接显示在面板中,方便打断底层连接
+ # only-tcp: false # 如果设置为true, smux的设置将不会对udp生效,udp连接会直接走底层协议
- name: "ss2"
type: ss
@@ -591,6 +603,7 @@ proxies: # socks5
type: hysteria
server: server.com
port: 443
+ # ports: 1000,2000-3000,5000 # port 不可省略,
auth_str: yourpassword # 将会在未来某个时候删除
# auth-str: yourpassword
# obfs: obfs_str
@@ -619,12 +632,27 @@ proxies: # socks5
port: 2480
ip: 172.16.0.2
ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5
- private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
+ # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=
+ private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
udp: true
reserved: "U4An"
# 数组格式也是合法的
# reserved: [209,98,59]
+ # 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接
+ # dialer-proxy: "ss1"
+ # remote-dns-resolve: true # 强制dns远程解析,默认值为false
+ # dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效
+ # 如果peers不为空,该段落中的allowed_ips不可为空;前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定
+ # peers:
+ # - server: 162.159.192.1
+ # port: 2480
+ # ip: 172.16.0.2
+ # ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5
+ # public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
+ # # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=
+ # allowed_ips: ['0.0.0.0/0']
+ # reserved: [209,98,59]
# tuic
- name: tuic
@@ -666,7 +694,9 @@ proxies: # socks5
# protocol-param: "#"
# udp: true
-proxy-groups: # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
+proxy-groups:
+ # 代理链,目前relay可以支持udp的只有vmess/vless/trojan/ss/ssr/tuic
+ # wireguard目前不支持在relay中使用,请使用proxy中的dialer-proxy配置项
# Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
- name: "relay"
type: relay
diff --git a/docs/heap.svg b/docs/heap.svg
deleted file mode 100644
index 0795977f..00000000
--- a/docs/heap.svg
+++ /dev/null
@@ -1,2182 +0,0 @@
-
-
-
-
-
diff --git a/go.mod b/go.mod
index c8eb6e10..5e805c16 100644
--- a/go.mod
+++ b/go.mod
@@ -11,74 +11,97 @@ require (
github.com/go-chi/chi/v5 v5.0.8
github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.2
- github.com/gofrs/uuid v4.4.0+incompatible
+ github.com/gofrs/uuid/v5 v5.0.0
github.com/google/gopacket v1.1.19
github.com/gorilla/websocket v1.5.0
github.com/hashicorp/golang-lru v0.5.4
- github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8
+ github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
github.com/jpillora/backoff v1.0.0
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7
github.com/metacubex/quic-go v0.33.3-0.20230322045857-901b636b4594
- github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947
- github.com/metacubex/sing-tun v0.1.3-0.20230323115055-7935ba0ac8b3
- github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb
- github.com/miekg/dns v1.1.52
+ github.com/metacubex/sing-shadowsocks v0.2.2-0.20230422111054-f54786eee8ba
+ github.com/metacubex/sing-tun v0.1.4
+ github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a
+ github.com/miekg/dns v1.1.53
github.com/mroth/weightedrand/v2 v2.0.0
+ github.com/openacid/low v0.1.21
github.com/oschwald/geoip2-golang v1.8.0
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
- github.com/sagernet/sing v0.2.1-0.20230323071235-f8038854d286
- github.com/sagernet/sing-shadowtls v0.1.0
- github.com/sagernet/sing-vmess v0.1.3
+ github.com/sagernet/sing v0.2.5-0.20230425211221-a23ffbaeb5b9
+ github.com/sagernet/sing-mux v0.0.0-20230425130511-b0a6ffd8406f
+ github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b
+ github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
- github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
- github.com/samber/lo v1.37.0
+ github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77
+ github.com/samber/lo v1.38.1
+ github.com/shirou/gopsutil/v3 v3.23.3
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.2
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837
github.com/zhangyunhao116/fastrand v0.3.0
go.etcd.io/bbolt v1.3.6
- go.uber.org/atomic v1.10.0
- go.uber.org/automaxprocs v1.5.1
- golang.org/x/crypto v0.7.0
+ go.uber.org/automaxprocs v1.5.2
+ golang.org/x/crypto v0.8.0
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
- golang.org/x/net v0.8.0
+ golang.org/x/net v0.9.0
golang.org/x/sync v0.1.0
- golang.org/x/sys v0.6.0
+ golang.org/x/sys v0.7.0
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d
gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.1.7
)
require (
+ github.com/RyuaNerin/go-krypto v1.0.2 // indirect
+ github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
+ github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
+ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
+ github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
+ github.com/hashicorp/yamux v0.1.1 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
+ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mdlayher/socket v0.4.0 // indirect
- github.com/metacubex/gvisor v0.0.0-20230323114922-412956fb6a03 // indirect
+ github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c // indirect
+ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
+ github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
+ github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
github.com/quic-go/qtls-go1-20 v0.1.1 // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
- github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
+ github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
+ github.com/shoenig/go-m1cpu v0.1.5 // indirect
+ github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
+ github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
+ github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
+ github.com/tklauser/go-sysconf v0.3.11 // indirect
+ github.com/tklauser/numcpus v0.6.0 // indirect
+ github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
+ github.com/yusufpapurcu/wmi v1.2.2 // indirect
+ gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
golang.org/x/mod v0.8.0 // indirect
- golang.org/x/text v0.8.0 // indirect
+ golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
golang.org/x/tools v0.6.0 // indirect
+ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
)
-
-replace go.uber.org/atomic v1.10.0 => github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370
diff --git a/go.sum b/go.sum
index f56aed7a..5d251c05 100644
--- a/go.sum
+++ b/go.sum
@@ -1,11 +1,16 @@
github.com/3andne/restls-client-go v0.1.4 h1:kLNC2aSRHPlEVYmTj6EOqJoorCpobEe2toMRSfBF7FU=
github.com/3andne/restls-client-go v0.1.4/go.mod h1:04CGbRk1BwBiEDles8b5mlKgTqIwE5MqF7JDloJV47I=
+github.com/RyuaNerin/go-krypto v1.0.2 h1:9KiZrrBs+tDrQ66dNy4nrX6SzntKtSKdm0wKHhdB4WM=
+github.com/RyuaNerin/go-krypto v1.0.2/go.mod h1:17LzMeJCgzGTkPH3TmfzRnEJ/yA7ErhTPp9sxIqONtA=
+github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
+github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -18,7 +23,15 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
-github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
+github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
+github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
+github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
+github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
+github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
+github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY=
+github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
+github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
+github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
@@ -28,92 +41,96 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
-github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
-github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
+github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
-github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
+github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
+github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg=
-github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE=
+github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 h1:+aAGyK41KRn8jbF2Q7PLL0Sxwg6dShGcQSeCC7nZQ8E=
+github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
-github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
-github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
-github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
-github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
-github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
-github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
-github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
-github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
-github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7 h1:HSkXG1bE/qcRuuPlZ2Jyf0Od8HLxOowi7CzKQqNtWn4=
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7/go.mod h1:1ztDZHGbU5MjN5lNZpkpG8ygndjjWzcojp/H7r6l6QQ=
-github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
-github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
-github.com/metacubex/gvisor v0.0.0-20230323114922-412956fb6a03 h1:gREIdurac9fpyBMBRPPMF/Sk3gKfPfdNCa4GQyR9FoA=
-github.com/metacubex/gvisor v0.0.0-20230323114922-412956fb6a03/go.mod h1:wqEuzdImyqD2MCGE8CYRJXbB77oSEJeoSSXXdwKjnsE=
+github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c h1:D62872jiuzC6b+3aI8tqfeyc6YgbfarYKywTnnvXwEM=
+github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c/go.mod h1:wqEuzdImyqD2MCGE8CYRJXbB77oSEJeoSSXXdwKjnsE=
github.com/metacubex/quic-go v0.33.3-0.20230322045857-901b636b4594 h1:KD96JPdTIayTGGgRl6PuVqo2Bpo6+x3LqDDyqrYDDXw=
github.com/metacubex/quic-go v0.33.3-0.20230322045857-901b636b4594/go.mod h1:9nOiGX6kqV3+ZbkDKdTNzdFD726QQHPH6WDb36jUSpA=
-github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947 h1:NnjC2+aIiyzzvFlo+C2WzBOJdsp+HAtu18FZomqYhUE=
-github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947/go.mod h1:U2gwhxzqgbhKCgn2B4z3t0Cj0LpMWFl/02BGCoG421w=
-github.com/metacubex/sing-tun v0.1.3-0.20230323115055-7935ba0ac8b3 h1:LnKcLs0HI0HX4xH/2XerX+1BLXS1Uj6Xvzn20xFuCOk=
-github.com/metacubex/sing-tun v0.1.3-0.20230323115055-7935ba0ac8b3/go.mod h1:0i22nk0tgkQz/N96hrhPib1O/C5AjxSnco7Mwi2YSF0=
-github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb h1:uhvzbtOvyg2c1k1H2EeVPuPvTEjDHCq4+U0AljG40P8=
-github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb/go.mod h1:7mPG9qYln+CLKBcDt7Dk4c7b3S53VzEfexMVPe6T6FM=
-github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370 h1:UkViS4DCESAUEYgbIEQdD02hyMacFt6Dny+1MOJtNIo=
-github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
-github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
-github.com/miekg/dns v1.1.52/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
+github.com/metacubex/sing-shadowsocks v0.2.2-0.20230422111054-f54786eee8ba h1:He8YwyK600lHAS1xxNsP4k/jnZ8zqQ34XjCGn925+Yk=
+github.com/metacubex/sing-shadowsocks v0.2.2-0.20230422111054-f54786eee8ba/go.mod h1:4uQQReKMTU7KTfOykVBe/oGJ00pl38d+BYJ99+mx26s=
+github.com/metacubex/sing-tun v0.1.4 h1:OQDBNHjuPKrOprCiK+sLt97YQ0K6b9ZWmJB6z51ibZQ=
+github.com/metacubex/sing-tun v0.1.4/go.mod h1:BMfG00enVf90/CzcdX9PK3Dymgl7BZqHXJfexEyB7Cc=
+github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a h1:cWKym33Qvl6HA3hj4/YuYD8hHyqQPb47wT5cJRAPgco=
+github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a/go.mod h1:Bsw2BvKMMMY0FhZPseDI50ZOalvoUPMKYyGpyqvIIqY=
+github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
+github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/mroth/weightedrand/v2 v2.0.0 h1:ADehnByWbliEDIazDAKFdBHoqgHSXAkgyKqM/9YsPoo=
github.com/mroth/weightedrand/v2 v2.0.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
+github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
+github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
+github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
+github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
+github.com/openacid/must v0.1.3/go.mod h1:luPiXCuJlEo3UUFQngVQokV0MPGryeYvtCbQPs3U1+I=
+github.com/openacid/testkeys v0.1.6/go.mod h1:MfA7cACzBpbiwekivj8StqX0WIRmqlMsci1c37CA3Do=
github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
+github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
+github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
@@ -127,27 +144,44 @@ github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
-github.com/sagernet/sing v0.2.1-0.20230323071235-f8038854d286 h1:0Td2b5l1KgrdlOnbRWgFFWsyb0TLoq/tP6j9Lut4JN0=
-github.com/sagernet/sing v0.2.1-0.20230323071235-f8038854d286/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
-github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
-github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
-github.com/sagernet/sing-vmess v0.1.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
-github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
+github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
+github.com/sagernet/sing v0.2.5-0.20230425211221-a23ffbaeb5b9 h1:kpgKJbhesj6BBLTKIfBCJGQPm2ww7pNxn566C6TrHdA=
+github.com/sagernet/sing v0.2.5-0.20230425211221-a23ffbaeb5b9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
+github.com/sagernet/sing-mux v0.0.0-20230425130511-b0a6ffd8406f h1:iEpOTgBTjt0vZJVXMTqYq13XyIu/337TWbq6WZ3CMWc=
+github.com/sagernet/sing-mux v0.0.0-20230425130511-b0a6ffd8406f/go.mod h1:pF+RnLvCAOhECrvauy6LYOpBakJ/vuaF1Wm4lPsWryI=
+github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4=
+github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
+github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3 h1:BHOnxrbC929JonuKqFdJ7ZbDp7zs4oTlH5KFvKtWu9U=
+github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3/go.mod h1:yKrAr+dqZd64DxBXCHWrYicp+n4qbqO73mtwv3dck8U=
+github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
+github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9/go.mod h1:FUyTEc5ye5NjKnDTDMuiLF2M6T4BE6y6KZuax//UCEg=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
-github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
-github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
-github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
-github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
+github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
+github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
+github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
+github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
+github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
+github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
+github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
+github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
+github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
+github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
+github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
+github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
+github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
+github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
+github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c/go.mod h1:NV/a66PhhWYVmUMaotlXJ8fIEFB98u+c8l/CQIEFLrU=
+github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e h1:ur8uMsPIFG3i4Gi093BQITvwH9znsz2VUZmnmwHvpIo=
+github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e5fBW3bpPyo+3uLo513gIUblc03egGjMM0+5GKbzK8=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -155,24 +189,31 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww=
-github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E=
+github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
+github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
+github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
+github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
+github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
+github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 h1:AHhUwwFJGl27E46OpdJHplZkK09m7aETNBNzhT6t15M=
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
+github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc=
+gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
+gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
-go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
-go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
+go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
+go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
-golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
+golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
@@ -180,56 +221,43 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@@ -243,7 +271,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ=
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/hub/executor/executor.go b/hub/executor/executor.go
index 1b2ec572..724150e7 100644
--- a/hub/executor/executor.go
+++ b/hub/executor/executor.go
@@ -5,6 +5,7 @@ import (
"net/netip"
"os"
"runtime"
+ "strings"
"sync"
"github.com/Dreamacro/clash/adapter"
@@ -91,7 +92,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateSniffer(cfg.Sniffer)
updateHosts(cfg.Hosts)
updateGeneral(cfg.General)
- updateDNS(cfg.DNS, cfg.General.IPv6)
+ updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6)
updateListeners(cfg.General, cfg.Listeners, force)
updateIPTables(cfg)
updateTun(cfg.General)
@@ -104,7 +105,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
loadProxyProvider(cfg.Providers)
updateProfile(cfg)
loadRuleProvider(cfg.RuleProviders)
-
+ runtime.GC()
tunnel.OnRunning()
log.SetLevel(cfg.General.LogLevel)
@@ -175,10 +176,9 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
}
func updateExperimental(c *config.Config) {
- runtime.GC()
}
-func updateDNS(c *config.DNS, generalIPv6 bool) {
+func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, generalIPv6 bool) {
if !c.Enable {
resolver.DefaultResolver = nil
resolver.DefaultHostMapper = nil
@@ -186,7 +186,25 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
dns.ReCreateServer("", nil, nil)
return
}
-
+ policy := make(map[string][]dns.NameServer)
+ domainSetPolicies := make(map[provider.RuleProvider][]dns.NameServer)
+ for key, nameservers := range c.NameServerPolicy {
+ temp := strings.Split(key, ":")
+ if len(temp) == 2 {
+ prefix := temp[0]
+ key := temp[1]
+ switch strings.ToLower(prefix) {
+ case "rule-set":
+ if p, ok := ruleProvider[key]; ok {
+ domainSetPolicies[p] = nameservers
+ }
+ case "geosite":
+ // TODO:
+ }
+ } else {
+ policy[key] = nameservers
+ }
+ }
cfg := dns.Config{
Main: c.NameServer,
Fallback: c.Fallback,
@@ -202,9 +220,10 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
Domain: c.FallbackFilter.Domain,
GeoSite: c.FallbackFilter.GeoSite,
},
- Default: c.DefaultNameserver,
- Policy: c.NameServerPolicy,
- ProxyServer: c.ProxyServerNameserver,
+ Default: c.DefaultNameserver,
+ Policy: c.NameServerPolicy,
+ ProxyServer: c.ProxyServerNameserver,
+ DomainSetPolicy: domainSetPolicies,
}
r := dns.NewResolver(cfg)
@@ -220,7 +239,7 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
resolver.DefaultHostMapper = m
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
- if pr.HasProxyServer() {
+ if pr.Invalid() {
resolver.ProxyServerHostResolver = pr
}
@@ -394,7 +413,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
continue
}
- selector.Set(selected)
+ selector.ForceSet(selected)
}
}
diff --git a/hub/route/restart.go b/hub/route/restart.go
index 9539296e..6c3f27f3 100644
--- a/hub/route/restart.go
+++ b/hub/route/restart.go
@@ -8,6 +8,7 @@ import (
"runtime"
"syscall"
+ "github.com/Dreamacro/clash/listener"
"github.com/Dreamacro/clash/log"
"github.com/go-chi/chi/v5"
@@ -38,25 +39,29 @@ func restart(w http.ResponseWriter, r *http.Request) {
// The background context is used because the underlying functions wrap it
// with timeout and shut down the server, which handles current request. It
// also should be done in a separate goroutine for the same reason.
- go func() {
- if runtime.GOOS == "windows" {
- cmd := exec.Command(execPath, os.Args[1:]...)
- log.Infoln("restarting: %q %q", execPath, os.Args[1:])
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- err = cmd.Start()
- if err != nil {
- log.Fatalln("restarting: %s", err)
- }
-
- os.Exit(0)
- }
+ go runRestart(execPath)
+}
+func runRestart(execPath string) {
+ var err error
+ listener.Cleanup(false)
+ if runtime.GOOS == "windows" {
+ cmd := exec.Command(execPath, os.Args[1:]...)
log.Infoln("restarting: %q %q", execPath, os.Args[1:])
- err = syscall.Exec(execPath, os.Args, os.Environ())
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err = cmd.Start()
if err != nil {
log.Fatalln("restarting: %s", err)
}
- }()
+
+ os.Exit(0)
+ }
+
+ log.Infoln("restarting: %q %q", execPath, os.Args[1:])
+ err = syscall.Exec(execPath, os.Args, os.Environ())
+ if err != nil {
+ log.Fatalln("restarting: %s", err)
+ }
}
diff --git a/hub/route/server.go b/hub/route/server.go
index 848face9..d8124d33 100644
--- a/hub/route/server.go
+++ b/hub/route/server.go
@@ -39,6 +39,11 @@ type Traffic struct {
Down int64 `json:"down"`
}
+type Memory struct {
+ Inuse uint64 `json:"inuse"`
+ OSLimit uint64 `json:"oslimit"` // maybe we need it in the future
+}
+
func SetUIPath(path string) {
uiPath = C.Path.Resolve(path)
}
@@ -76,6 +81,7 @@ func Start(addr string, tlsAddr string, secret string,
r.Get("/", hello)
r.Get("/logs", getLogs)
r.Get("/traffic", traffic)
+ r.Get("/memory", memory)
r.Get("/version", version)
r.Mount("/configs", configRouter())
r.Mount("/proxies", proxyRouter())
@@ -224,6 +230,56 @@ func traffic(w http.ResponseWriter, r *http.Request) {
}
}
+func memory(w http.ResponseWriter, r *http.Request) {
+ var wsConn *websocket.Conn
+ if websocket.IsWebSocketUpgrade(r) {
+ var err error
+ wsConn, err = upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ return
+ }
+ }
+
+ if wsConn == nil {
+ w.Header().Set("Content-Type", "application/json")
+ render.Status(r, http.StatusOK)
+ }
+
+ tick := time.NewTicker(time.Second)
+ defer tick.Stop()
+ t := statistic.DefaultManager
+ buf := &bytes.Buffer{}
+ var err error
+ first := true
+ for range tick.C {
+ buf.Reset()
+
+ inuse := t.Memory()
+ // make chat.js begin with zero
+ // this is shit var,but we need output 0 for first time
+ if first {
+ inuse = 0
+ first = false
+ }
+ if err := json.NewEncoder(buf).Encode(Memory{
+ Inuse: inuse,
+ OSLimit: 0,
+ }); err != nil {
+ break
+ }
+ if wsConn == nil {
+ _, err = w.Write(buf.Bytes())
+ w.(http.Flusher).Flush()
+ } else {
+ err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
+ }
+
+ if err != nil {
+ break
+ }
+ }
+}
+
type Log struct {
Type string `json:"type"`
Payload string `json:"payload"`
diff --git a/hub/route/upgrade.go b/hub/route/upgrade.go
index 5adf79eb..0a7c54f9 100644
--- a/hub/route/upgrade.go
+++ b/hub/route/upgrade.go
@@ -1,12 +1,15 @@
package route
import (
+ "fmt"
"net/http"
+ "os"
"github.com/Dreamacro/clash/hub/updater"
"github.com/Dreamacro/clash/log"
"github.com/go-chi/chi/v5"
+ "github.com/go-chi/render"
)
func upgradeRouter() http.Handler {
@@ -18,11 +21,24 @@ func upgradeRouter() http.Handler {
func upgrade(w http.ResponseWriter, r *http.Request) {
// modify from https://github.com/AdguardTeam/AdGuardHome/blob/595484e0b3fb4c457f9bb727a6b94faa78a66c5f/internal/home/controlupdate.go#L108
log.Infoln("start update")
- err := updater.Update()
+ execPath, err := os.Executable()
if err != nil {
- log.Errorln("err:%s", err)
+ render.Status(r, http.StatusInternalServerError)
+ render.JSON(w, r, newError(fmt.Sprintf("getting path: %s", err)))
return
}
- restart(w, r)
+ err = updater.Update(execPath)
+ if err != nil {
+ render.Status(r, http.StatusInternalServerError)
+ render.JSON(w, r, newError(fmt.Sprintf("Upgrade: %s", err)))
+ return
+ }
+
+ render.JSON(w, r, render.M{"status": "ok"})
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
+ }
+
+ go runRestart(execPath)
}
diff --git a/hub/updater/updater.go b/hub/updater/updater.go
index 077818fd..3d80707a 100644
--- a/hub/updater/updater.go
+++ b/hub/updater/updater.go
@@ -50,12 +50,12 @@ type updateError struct {
}
func (e *updateError) Error() string {
- return fmt.Sprintf("error: %s", e.Message)
+ return fmt.Sprintf("update error: %s", e.Message)
}
// Update performs the auto-updater. It returns an error if the updater failed.
// If firstRun is true, it assumes the configuration file doesn't exist.
-func Update() (err error) {
+func Update(execPath string) (err error) {
mu.Lock()
defer mu.Unlock()
@@ -63,11 +63,10 @@ func Update() (err error) {
goarch = runtime.GOARCH
latestVersion, err = getLatestVersion()
if err != nil {
- err := &updateError{Message: err.Error()}
return err
}
- log.Infoln("current version alpha-%s, latest version alpha-%s", constant.Version, latestVersion)
+ log.Infoln("current version %s, latest version %s", constant.Version, latestVersion)
if latestVersion == constant.Version {
err := &updateError{Message: "Already using latest version"}
@@ -84,11 +83,6 @@ func Update() (err error) {
}
}()
- execPath, err := os.Executable()
- if err != nil {
- return fmt.Errorf("getting executable path: %w", err)
- }
-
workDir = filepath.Dir(execPath)
err = prepare(execPath)
@@ -110,7 +104,7 @@ func Update() (err error) {
err = backup()
if err != nil {
- return fmt.Errorf("replacing: %w", err)
+ return fmt.Errorf("backuping: %w", err)
}
err = replace()
@@ -134,8 +128,10 @@ func prepare(exePath string) (err error) {
//log.Infoln(packageName)
backupDir = filepath.Join(workDir, "meta-backup")
- if goos == "windows" {
- updateExeName = "clash.meta" + "-" + goos + "-" + goarch + ".exe"
+ if goos == "windows" && goarch == "amd64" {
+ updateExeName = "clash.meta" + "-" + goos + "-" + goarch + "-compatible.exe"
+ } else if goos == "linux" && goarch == "amd64" {
+ updateExeName = "clash.meta" + "-" + goos + "-" + goarch + "-compatible"
} else {
updateExeName = "clash.meta" + "-" + goos + "-" + goarch
}
@@ -164,7 +160,7 @@ func unpack() error {
var err error
_, pkgNameOnly := filepath.Split(packageURL)
- log.Debugln("updater: unpacking package")
+ log.Infoln("updater: unpacking package")
if strings.HasSuffix(pkgNameOnly, ".zip") {
_, err = zipFileUnpack(packageName, updateDir)
if err != nil {
@@ -184,43 +180,36 @@ func unpack() error {
return nil
}
-// backup makes a backup of the current configuration and supporting files. It
-// ignores the configuration file if firstRun is true.
+// backup makes a backup of the current executable file
func backup() (err error) {
- log.Infoln("updater: backing up current Exefile")
+ log.Infoln("updater: backing up current ExecFile:%s to %s", currentExeName, backupExeName)
_ = os.Mkdir(backupDir, 0o755)
- err = copyFile(currentExeName, backupExeName)
+ err = os.Rename(currentExeName, backupExeName)
if err != nil {
- return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", currentExeName, backupExeName, err)
+ return err
}
return nil
}
-// replace moves the current executable with the updated one and also copies the
-// supporting files.
+// replace moves the current executable with the updated one
func replace() error {
var err error
- // log.Infoln("updater: renaming: %s to %s", currentExeName, backupExeName)
- // err := os.Rename(currentExeName, backupExeName)
- // if err != nil {
- // return err
- // }
-
+ log.Infoln("replacing: %s to %s", updateExeName, currentExeName)
if goos == "windows" {
// rename fails with "File in use" error
- log.Infoln("copying: %s to %s", updateExeName, currentExeName)
err = copyFile(updateExeName, currentExeName)
} else {
- log.Infoln("copying: %s to %s", updateExeName, currentExeName)
err = os.Rename(updateExeName, currentExeName)
}
if err != nil {
return err
}
+ log.Infoln("updater: renamed: %s to %s", updateExeName, currentExeName)
+
return nil
}
@@ -236,8 +225,6 @@ const MaxPackageFileSize = 32 * 1024 * 1024
// Download package file and save it to disk
func downloadPackageFile() (err error) {
- // var resp *http.Response
- // resp, err = client.Get(packageURL)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, packageURL, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
@@ -447,6 +434,8 @@ func updateDownloadURL() {
middle = fmt.Sprintf("-%s-%sv%s-%s", goos, goarch, goarm, latestVersion)
} else if isMIPS(goarch) && gomips != "" {
middle = fmt.Sprintf("-%s-%s-%s-%s", goos, goarch, gomips, latestVersion)
+ } else if goarch == "amd64" && (goos == "windows" || goos == "linux") {
+ middle = fmt.Sprintf("-%s-%s-compatible-%s", goos, goarch, latestVersion)
} else {
middle = fmt.Sprintf("-%s-%s-%s", goos, goarch, latestVersion)
}
diff --git a/listener/shadowsocks/tcp.go b/listener/shadowsocks/tcp.go
index c0fd490f..6884b960 100644
--- a/listener/shadowsocks/tcp.go
+++ b/listener/shadowsocks/tcp.go
@@ -5,6 +5,7 @@ import (
"strings"
"github.com/Dreamacro/clash/adapter/inbound"
+ N "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant"
LC "github.com/Dreamacro/clash/listener/config"
"github.com/Dreamacro/clash/transport/shadowsocks/core"
@@ -100,6 +101,7 @@ func (l *Listener) AddrList() (addrList []net.Addr) {
func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) {
conn = l.pickCipher.StreamConn(conn)
+ conn = N.NewDeadlineConn(conn) // embed ss can't handle readDeadline correctly
target, err := socks5.ReadAddr(conn, make([]byte, socks5.MaxAddrLen))
if err != nil {
diff --git a/listener/sing/sing.go b/listener/sing/sing.go
index 70462728..2ccdfe2d 100644
--- a/listener/sing/sing.go
+++ b/listener/sing/sing.go
@@ -10,12 +10,15 @@ import (
"time"
"github.com/Dreamacro/clash/adapter/inbound"
+ N "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/transport/socks5"
+ mux "github.com/sagernet/sing-mux"
vmess "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common/buf"
+ "github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/network"
@@ -33,7 +36,7 @@ type ListenerHandler struct {
}
type waitCloseConn struct {
- net.Conn
+ N.ExtendedConn
wg *sync.WaitGroup
close sync.Once
rAddr net.Addr
@@ -43,7 +46,7 @@ func (c *waitCloseConn) Close() error { // call from handleTCPConn(connCtx C.Con
c.close.Do(func() {
c.wg.Done()
})
- return c.Conn.Close()
+ return c.ExtendedConn.Close()
}
func (c *waitCloseConn) RemoteAddr() net.Addr {
@@ -51,7 +54,14 @@ func (c *waitCloseConn) RemoteAddr() net.Addr {
}
func (c *waitCloseConn) Upstream() any {
- return c.Conn
+ return c.ExtendedConn
+}
+
+func UpstreamMetadata(metadata M.Metadata) M.Metadata {
+ return M.Metadata{
+ Source: metadata.Source,
+ Destination: metadata.Destination,
+ }
}
func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
@@ -61,6 +71,8 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta
additions = append(additions, ctxAdditions...)
}
switch metadata.Destination.Fqdn {
+ case mux.Destination.Fqdn:
+ return mux.HandleConnection(ctx, h, log.SingLogger, conn, UpstreamMetadata(metadata))
case vmess.MuxDestination.Fqdn:
return vmess.HandleMuxConnection(ctx, conn, h)
case uot.MagicAddress:
@@ -79,7 +91,10 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta
defer wg.Wait() // this goroutine must exit after conn.Close()
wg.Add(1)
- h.TcpIn <- inbound.NewSocket(target, &waitCloseConn{Conn: conn, wg: wg, rAddr: metadata.Source.TCPAddr()}, h.Type, additions...)
+ if deadline.NeedAdditionalReadDeadline(conn) {
+ conn = N.NewDeadlineConn(conn) // conn from sing should check NeedAdditionalReadDeadline
+ }
+ h.TcpIn <- inbound.NewSocket(target, &waitCloseConn{ExtendedConn: N.NewExtendedConn(conn), wg: wg, rAddr: metadata.Source.TCPAddr()}, h.Type, additions...)
return nil
}
@@ -102,7 +117,7 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
dest, err := conn.ReadPacket(buff)
if err != nil {
buff.Release()
- if E.IsClosed(err) {
+ if ShouldIgnorePacketError(err) {
break
}
return err
@@ -127,6 +142,14 @@ func (h *ListenerHandler) NewError(ctx context.Context, err error) {
log.Warnln("%s listener get error: %+v", h.Type.String(), err)
}
+func ShouldIgnorePacketError(err error) bool {
+ // ignore simple error
+ if E.IsTimeout(err) || E.IsClosed(err) || E.IsCanceled(err) {
+ return true
+ }
+ return false
+}
+
type packet struct {
conn *network.PacketConn
mutex *sync.Mutex
diff --git a/listener/sing_tun/dns.go b/listener/sing_tun/dns.go
index 21dee43c..dc33be91 100644
--- a/listener/sing_tun/dns.go
+++ b/listener/sing_tun/dns.go
@@ -17,7 +17,6 @@ import (
D "github.com/miekg/dns"
"github.com/sagernet/sing/common/buf"
- E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/network"
)
@@ -110,11 +109,14 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
conn2 = nil
}()
for {
- buff := buf.NewPacket()
+ // safe size which is 1232 from https://dnsflagday.net/2020/.
+ // so 2048 is enough
+ buff := buf.NewSize(2 * 1024)
+ _ = conn.SetReadDeadline(time.Now().Add(DefaultDnsReadTimeout))
dest, err := conn.ReadPacket(buff)
if err != nil {
buff.Release()
- if E.IsClosed(err) {
+ if sing.ShouldIgnorePacketError(err) {
break
}
return err
diff --git a/listener/sing_tun/server.go b/listener/sing_tun/server.go
index ada9d1c2..084c701b 100644
--- a/listener/sing_tun/server.go
+++ b/listener/sing_tun/server.go
@@ -229,8 +229,8 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte
err = E.Cause(err, "configure tun interface")
return
}
- l.tunIf = tunIf
- l.tunStack, err = tun.NewStack(strings.ToLower(options.Stack.String()), tun.StackOptions{
+
+ stackOptions := tun.StackOptions{
Context: context.TODO(),
Tun: tunIf,
MTU: tunOptions.MTU,
@@ -241,7 +241,16 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte
UDPTimeout: udpTimeout,
Handler: handler,
Logger: log.SingLogger,
- })
+ }
+
+ if options.FileDescriptor > 0 {
+ if tunName, err := getTunnelName(int32(options.FileDescriptor)); err != nil {
+ stackOptions.Name = tunName
+ stackOptions.ForwarderBindInterface = true
+ }
+ }
+ l.tunIf = tunIf
+ l.tunStack, err = tun.NewStack(strings.ToLower(options.Stack.String()), stackOptions)
if err != nil {
return
}
diff --git a/listener/sing_tun/tun_name_darwin.go b/listener/sing_tun/tun_name_darwin.go
new file mode 100644
index 00000000..5b4686ea
--- /dev/null
+++ b/listener/sing_tun/tun_name_darwin.go
@@ -0,0 +1,11 @@
+package sing_tun
+
+import "golang.org/x/sys/unix"
+
+func getTunnelName(fd int32) (string, error) {
+ return unix.GetsockoptString(
+ int(fd),
+ 2, /* #define SYSPROTO_CONTROL 2 */
+ 2, /* #define UTUN_OPT_IFNAME 2 */
+ )
+}
diff --git a/listener/sing_tun/tun_name_linux.go b/listener/sing_tun/tun_name_linux.go
new file mode 100644
index 00000000..9ae9800b
--- /dev/null
+++ b/listener/sing_tun/tun_name_linux.go
@@ -0,0 +1,25 @@
+package sing_tun
+
+import (
+ "fmt"
+ "golang.org/x/sys/unix"
+ "syscall"
+ "unsafe"
+)
+
+const ifReqSize = unix.IFNAMSIZ + 64
+
+func getTunnelName(fd int32) (string, error) {
+ var ifr [ifReqSize]byte
+ var errno syscall.Errno
+ _, _, errno = unix.Syscall(
+ unix.SYS_IOCTL,
+ uintptr(fd),
+ uintptr(unix.TUNGETIFF),
+ uintptr(unsafe.Pointer(&ifr[0])),
+ )
+ if errno != 0 {
+ return "", fmt.Errorf("failed to get name of TUN device: %w", errno)
+ }
+ return unix.ByteSliceToString(ifr[:]), nil
+}
diff --git a/listener/sing_tun/tun_name_other.go b/listener/sing_tun/tun_name_other.go
new file mode 100644
index 00000000..c47c8cbe
--- /dev/null
+++ b/listener/sing_tun/tun_name_other.go
@@ -0,0 +1,9 @@
+//go:build !(darwin || linux)
+
+package sing_tun
+
+import "os"
+
+func getTunnelName(fd int32) (string, error) {
+ return "", os.ErrInvalid
+}
diff --git a/patch/add_debug_api.patch b/patch/add_debug_api.patch
deleted file mode 100644
index 7134b378..00000000
--- a/patch/add_debug_api.patch
+++ /dev/null
@@ -1,53 +0,0 @@
-Subject: [PATCH] Chore: add debug api
----
-Index: hub/route/debug.go
-IDEA additional info:
-Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
-<+>UTF-8
-===================================================================
-diff --git a/hub/route/debug.go b/hub/route/debug.go
-new file mode 100644
---- /dev/null (revision df1007e2b14f7a526d176410995998bf06054657)
-+++ b/hub/route/debug.go (revision df1007e2b14f7a526d176410995998bf06054657)
-@@ -0,0 +1,21 @@
-+package route
-+
-+import (
-+ "github.com/Dreamacro/clash/log"
-+ "github.com/go-chi/chi/v5"
-+ "github.com/go-chi/chi/v5/middleware"
-+ "net/http"
-+ "runtime"
-+)
-+
-+func debugRouter() http.Handler {
-+ handler := middleware.Profiler()
-+ r := chi.NewRouter()
-+ r.Mount("/", handler)
-+ r.Put("/gc", func(writer http.ResponseWriter, request *http.Request) {
-+ log.Debugln("trigger GC")
-+ runtime.GC()
-+ })
-+
-+ return r
-+}
-Index: hub/route/server.go
-IDEA additional info:
-Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
-<+>UTF-8
-===================================================================
-diff --git a/hub/route/server.go b/hub/route/server.go
---- a/hub/route/server.go (revision f83fd6c690928ca7861196e3ca5af566303f95d5)
-+++ b/hub/route/server.go (revision df1007e2b14f7a526d176410995998bf06054657)
-@@ -59,6 +59,11 @@
- MaxAge: 300,
- })
- r.Use(corsM.Handler)
-+
-+ r.Group(func(r chi.Router) {
-+ r.Mount("/debug", debugRouter())
-+ })
-+
- r.Group(func(r chi.Router) {
- r.Use(authentication)
- r.Get("/", hello)
diff --git a/rules/common/domain.go b/rules/common/domain.go
index 6b3eba22..35a06a70 100644
--- a/rules/common/domain.go
+++ b/rules/common/domain.go
@@ -1,7 +1,6 @@
package common
import (
- "golang.org/x/net/idna"
"strings"
C "github.com/Dreamacro/clash/constant"
@@ -11,7 +10,6 @@ type Domain struct {
*Base
domain string
adapter string
- isIDNA bool
}
func (d *Domain) RuleType() C.RuleType {
@@ -27,20 +25,14 @@ func (d *Domain) Adapter() string {
}
func (d *Domain) Payload() string {
- domain := d.domain
- if d.isIDNA {
- domain, _ = idna.ToUnicode(domain)
- }
- return domain
+ return d.domain
}
func NewDomain(domain string, adapter string) *Domain {
- actualDomain, _ := idna.ToASCII(domain)
return &Domain{
Base: &Base{},
- domain: strings.ToLower(actualDomain),
+ domain: strings.ToLower(domain),
adapter: adapter,
- isIDNA: actualDomain != domain,
}
}
diff --git a/rules/common/domain_keyword.go b/rules/common/domain_keyword.go
index 94d2a949..d945f200 100644
--- a/rules/common/domain_keyword.go
+++ b/rules/common/domain_keyword.go
@@ -1,7 +1,6 @@
package common
import (
- "golang.org/x/net/idna"
"strings"
C "github.com/Dreamacro/clash/constant"
@@ -11,7 +10,6 @@ type DomainKeyword struct {
*Base
keyword string
adapter string
- isIDNA bool
}
func (dk *DomainKeyword) RuleType() C.RuleType {
@@ -28,20 +26,14 @@ func (dk *DomainKeyword) Adapter() string {
}
func (dk *DomainKeyword) Payload() string {
- keyword := dk.keyword
- if dk.isIDNA {
- keyword, _ = idna.ToUnicode(keyword)
- }
- return keyword
+ return dk.keyword
}
func NewDomainKeyword(keyword string, adapter string) *DomainKeyword {
- actualDomainKeyword, _ := idna.ToASCII(keyword)
return &DomainKeyword{
Base: &Base{},
- keyword: strings.ToLower(actualDomainKeyword),
+ keyword: strings.ToLower(keyword),
adapter: adapter,
- isIDNA: keyword != actualDomainKeyword,
}
}
diff --git a/rules/common/domain_suffix.go b/rules/common/domain_suffix.go
index 4bdc2e2e..b13036a3 100644
--- a/rules/common/domain_suffix.go
+++ b/rules/common/domain_suffix.go
@@ -1,7 +1,6 @@
package common
import (
- "golang.org/x/net/idna"
"strings"
C "github.com/Dreamacro/clash/constant"
@@ -11,7 +10,6 @@ type DomainSuffix struct {
*Base
suffix string
adapter string
- isIDNA bool
}
func (ds *DomainSuffix) RuleType() C.RuleType {
@@ -28,20 +26,14 @@ func (ds *DomainSuffix) Adapter() string {
}
func (ds *DomainSuffix) Payload() string {
- suffix := ds.suffix
- if ds.isIDNA {
- suffix, _ = idna.ToUnicode(suffix)
- }
- return suffix
+ return ds.suffix
}
func NewDomainSuffix(suffix string, adapter string) *DomainSuffix {
- actualDomainSuffix, _ := idna.ToASCII(suffix)
return &DomainSuffix{
Base: &Base{},
- suffix: strings.ToLower(actualDomainSuffix),
+ suffix: strings.ToLower(suffix),
adapter: adapter,
- isIDNA: suffix != actualDomainSuffix,
}
}
diff --git a/rules/common/network_type.go b/rules/common/network_type.go
index fb6b5077..1184ba89 100644
--- a/rules/common/network_type.go
+++ b/rules/common/network_type.go
@@ -21,10 +21,8 @@ func NewNetworkType(network, adapter string) (*NetworkType, error) {
switch strings.ToUpper(network) {
case "TCP":
ntType.network = C.TCP
- break
case "UDP":
ntType.network = C.UDP
- break
default:
return nil, fmt.Errorf("unsupported network type, only TCP/UDP")
}
diff --git a/rules/provider/classical_strategy.go b/rules/provider/classical_strategy.go
index 25360ec7..e187e213 100644
--- a/rules/provider/classical_strategy.go
+++ b/rules/provider/classical_strategy.go
@@ -37,36 +37,38 @@ func (c *classicalStrategy) ShouldFindProcess() bool {
return c.shouldFindProcess
}
-func (c *classicalStrategy) OnUpdate(rules []string) {
- var classicalRules []C.Rule
- shouldResolveIP := false
- for _, rawRule := range rules {
- ruleType, rule, params := ruleParse(rawRule)
+func (c *classicalStrategy) Reset() {
+ c.rules = nil
+ c.count = 0
+ c.shouldFindProcess = false
+ c.shouldResolveIP = false
+}
- if ruleType == "PROCESS-NAME" {
+func (c *classicalStrategy) Insert(rule string) {
+ ruleType, rule, params := ruleParse(rule)
+
+ if ruleType == "PROCESS-NAME" {
+ c.shouldFindProcess = true
+ }
+
+ r, err := c.parse(ruleType, rule, "", params)
+ if err != nil {
+ log.Warnln("parse rule error:[%s]", err.Error())
+ } else {
+ if r.ShouldResolveIP() {
+ c.shouldResolveIP = true
+ }
+ if r.ShouldFindProcess() {
c.shouldFindProcess = true
}
- r, err := c.parse(ruleType, rule, "", params)
- if err != nil {
- log.Warnln("parse rule error:[%s]", err.Error())
- } else {
- if !shouldResolveIP {
- shouldResolveIP = r.ShouldResolveIP()
- }
-
- if !c.shouldFindProcess {
- c.shouldFindProcess = r.ShouldFindProcess()
- }
-
- classicalRules = append(classicalRules, r)
- }
+ c.rules = append(c.rules, r)
+ c.count++
}
-
- c.rules = classicalRules
- c.count = len(classicalRules)
}
+func (c *classicalStrategy) FinishInsert() {}
+
func ruleParse(ruleRaw string) (string, string, []string) {
item := strings.Split(ruleRaw, ",")
if len(item) == 1 {
diff --git a/rules/provider/domain_strategy.go b/rules/provider/domain_strategy.go
index add64e76..d686d598 100644
--- a/rules/provider/domain_strategy.go
+++ b/rules/provider/domain_strategy.go
@@ -4,12 +4,12 @@ import (
"github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
- "golang.org/x/net/idna"
)
type domainStrategy struct {
- count int
- domainRules *trie.DomainTrie[struct{}]
+ count int
+ domainTrie *trie.DomainTrie[struct{}]
+ domainSet *trie.DomainSet
}
func (d *domainStrategy) ShouldFindProcess() bool {
@@ -17,7 +17,7 @@ func (d *domainStrategy) ShouldFindProcess() bool {
}
func (d *domainStrategy) Match(metadata *C.Metadata) bool {
- return d.domainRules != nil && d.domainRules.Search(metadata.RuleHost()) != nil
+ return d.domainSet != nil && d.domainSet.Has(metadata.RuleHost())
}
func (d *domainStrategy) Count() int {
@@ -28,22 +28,24 @@ func (d *domainStrategy) ShouldResolveIP() bool {
return false
}
-func (d *domainStrategy) OnUpdate(rules []string) {
- domainTrie := trie.New[struct{}]()
- count := 0
- for _, rule := range rules {
- actualDomain, _ := idna.ToASCII(rule)
- err := domainTrie.Insert(actualDomain, struct{}{})
- if err != nil {
- log.Warnln("invalid domain:[%s]", rule)
- } else {
- count++
- }
- }
- domainTrie.Optimize()
+func (d *domainStrategy) Reset() {
+ d.domainTrie = trie.New[struct{}]()
+ d.domainSet = nil
+ d.count = 0
+}
- d.domainRules = domainTrie
- d.count = count
+func (d *domainStrategy) Insert(rule string) {
+ err := d.domainTrie.Insert(rule, struct{}{})
+ if err != nil {
+ log.Warnln("invalid domain:[%s]", rule)
+ } else {
+ d.count++
+ }
+}
+
+func (d *domainStrategy) FinishInsert() {
+ d.domainSet = d.domainTrie.NewDomainSet()
+ d.domainTrie = nil
}
func NewDomainStrategy() *domainStrategy {
diff --git a/rules/provider/ipcidr_strategy.go b/rules/provider/ipcidr_strategy.go
index 88228301..f54302f1 100644
--- a/rules/provider/ipcidr_strategy.go
+++ b/rules/provider/ipcidr_strategy.go
@@ -28,23 +28,24 @@ func (i *ipcidrStrategy) ShouldResolveIP() bool {
return i.shouldResolveIP
}
-func (i *ipcidrStrategy) OnUpdate(rules []string) {
- ipCidrTrie := trie.NewIpCidrTrie()
- count := 0
- for _, rule := range rules {
- err := ipCidrTrie.AddIpCidrForString(rule)
- if err != nil {
- log.Warnln("invalid Ipcidr:[%s]", rule)
- } else {
- count++
- }
- }
-
- i.trie = ipCidrTrie
- i.count = count
- i.shouldResolveIP = i.count > 0
+func (i *ipcidrStrategy) Reset() {
+ i.trie = trie.NewIpCidrTrie()
+ i.count = 0
+ i.shouldResolveIP = false
}
+func (i *ipcidrStrategy) Insert(rule string) {
+ err := i.trie.AddIpCidrForString(rule)
+ if err != nil {
+ log.Warnln("invalid Ipcidr:[%s]", rule)
+ } else {
+ i.shouldResolveIP = true
+ i.count++
+ }
+}
+
+func (i *ipcidrStrategy) FinishInsert() {}
+
func NewIPCidrStrategy() *ipcidrStrategy {
return &ipcidrStrategy{}
}
diff --git a/rules/provider/parse.go b/rules/provider/parse.go
index 206bef10..6a001b50 100644
--- a/rules/provider/parse.go
+++ b/rules/provider/parse.go
@@ -14,6 +14,7 @@ type ruleProviderSchema struct {
Behavior string `provider:"behavior"`
Path string `provider:"path"`
URL string `provider:"url,omitempty"`
+ Format string `provider:"format,omitempty"`
Interval int `provider:"interval,omitempty"`
}
@@ -23,7 +24,7 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
if err := decoder.Decode(mapping, schema); err != nil {
return nil, err
}
- var behavior P.RuleType
+ var behavior P.RuleBehavior
switch schema.Behavior {
case "domain":
@@ -36,6 +37,17 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior)
}
+ var format P.RuleFormat
+
+ switch schema.Format {
+ case "", "yaml":
+ format = P.YamlRule
+ case "text":
+ format = P.TextRule
+ default:
+ return nil, fmt.Errorf("unsupported format type: %s", schema.Format)
+ }
+
path := C.Path.Resolve(schema.Path)
var vehicle P.Vehicle
switch schema.Type {
@@ -47,5 +59,5 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
}
- return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle, parse), nil
+ return NewRuleSetProvider(name, behavior, format, time.Duration(uint(schema.Interval))*time.Second, vehicle, parse), nil
}
diff --git a/rules/provider/provider.go b/rules/provider/provider.go
index 175917c2..65d21d2f 100644
--- a/rules/provider/provider.go
+++ b/rules/provider/provider.go
@@ -1,13 +1,18 @@
package provider
import (
+ "bytes"
"encoding/json"
+ "errors"
+ "gopkg.in/yaml.v3"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/resource"
C "github.com/Dreamacro/clash/constant"
P "github.com/Dreamacro/clash/constant/provider"
- "gopkg.in/yaml.v3"
- "runtime"
- "time"
)
var (
@@ -16,7 +21,8 @@ var (
type ruleSetProvider struct {
*resource.Fetcher[any]
- behavior P.RuleType
+ behavior P.RuleBehavior
+ format P.RuleFormat
strategy ruleStrategy
}
@@ -29,8 +35,8 @@ type RulePayload struct {
key: Domain or IP Cidr
value: Rule type or is empty
*/
- Rules []string `yaml:"payload"`
- Rules2 []string `yaml:"rules"`
+ Payload []string `yaml:"payload"`
+ Rules []string `yaml:"rules"`
}
type ruleStrategy interface {
@@ -38,7 +44,9 @@ type ruleStrategy interface {
Count() int
ShouldResolveIP() bool
ShouldFindProcess() bool
- OnUpdate(rules []string)
+ Reset()
+ Insert(rule string)
+ FinishInsert()
}
func RuleProviders() map[string]P.RuleProvider {
@@ -75,7 +83,7 @@ func (rp *ruleSetProvider) Update() error {
return err
}
-func (rp *ruleSetProvider) Behavior() P.RuleType {
+func (rp *ruleSetProvider) Behavior() P.RuleBehavior {
return rp.behavior
}
@@ -99,6 +107,7 @@ func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(
map[string]interface{}{
"behavior": rp.behavior.String(),
+ "format": rp.format.String(),
"name": rp.Name(),
"ruleCount": rp.strategy.Count(),
"type": rp.Type().String(),
@@ -107,20 +116,20 @@ func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) {
})
}
-func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration, vehicle P.Vehicle,
+func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleFormat, interval time.Duration, vehicle P.Vehicle,
parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) P.RuleProvider {
rp := &ruleSetProvider{
behavior: behavior,
+ format: format,
}
onUpdate := func(elm interface{}) {
- rulesRaw := elm.([]string)
- rp.strategy.OnUpdate(rulesRaw)
+ strategy := elm.(ruleStrategy)
+ rp.strategy = strategy
}
- fetcher := resource.NewFetcher(name, interval, vehicle, rulesParse, onUpdate)
- rp.Fetcher = fetcher
rp.strategy = newStrategy(behavior, parse)
+ rp.Fetcher = resource.NewFetcher(name, interval, vehicle, func(bytes []byte) (any, error) { return rulesParse(bytes, newStrategy(behavior, parse), format) }, onUpdate)
wrapper := &RuleSetProvider{
rp,
@@ -131,7 +140,7 @@ func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration
return wrapper
}
-func newStrategy(behavior P.RuleType, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) ruleStrategy {
+func newStrategy(behavior P.RuleBehavior, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) ruleStrategy {
switch behavior {
case P.Domain:
strategy := NewDomainStrategy()
@@ -147,12 +156,94 @@ func newStrategy(behavior P.RuleType, parse func(tp, payload, target string, par
}
}
-func rulesParse(buf []byte) (any, error) {
- rulePayload := RulePayload{}
- err := yaml.Unmarshal(buf, &rulePayload)
- if err != nil {
- return nil, err
+var ErrNoPayload = errors.New("file must have a `payload` field")
+
+func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (any, error) {
+ strategy.Reset()
+
+ schema := &RulePayload{}
+
+ firstLineBuffer := pool.GetBuffer()
+ defer pool.PutBuffer(firstLineBuffer)
+ firstLineLength := 0
+
+ s := 0 // search start index
+ for s < len(buf) {
+ // search buffer for a new line.
+ line := buf[s:]
+ if i := bytes.IndexByte(line, '\n'); i >= 0 {
+ i += s
+ line = buf[s : i+1]
+ s = i + 1
+ } else {
+ s = len(buf) // stop loop in next step
+ if firstLineLength == 0 { // no head or only one line body
+ return nil, ErrNoPayload
+ }
+ }
+ var str string
+ switch format {
+ case P.TextRule:
+ firstLineLength = -1 // don't return ErrNoPayload when read last line
+ str = string(line)
+ str = strings.TrimSpace(str)
+ if len(str) == 0 {
+ continue
+ }
+ if str[0] == '#' { // comment
+ continue
+ }
+ if strings.HasPrefix(str, "//") { // comment in Premium core
+ continue
+ }
+ case P.YamlRule:
+ trimLine := bytes.TrimSpace(line)
+ if len(trimLine) == 0 {
+ continue
+ }
+ if trimLine[0] == '#' { // comment
+ continue
+ }
+ firstLineBuffer.Write(line)
+ if firstLineLength == 0 { // find payload head
+ firstLineLength = firstLineBuffer.Len()
+ firstLineBuffer.WriteString(" - ''") // a test line
+
+ err := yaml.Unmarshal(firstLineBuffer.Bytes(), schema)
+ firstLineBuffer.Truncate(firstLineLength)
+ if err == nil && (len(schema.Rules) > 0 || len(schema.Payload) > 0) { // found
+ continue
+ }
+
+ // not found or err!=nil
+ firstLineBuffer.Truncate(0)
+ firstLineLength = 0
+ continue
+ }
+
+ // parse payload body
+ err := yaml.Unmarshal(firstLineBuffer.Bytes(), schema)
+ firstLineBuffer.Truncate(firstLineLength)
+ if err != nil {
+ continue
+ }
+
+ if len(schema.Rules) > 0 {
+ str = schema.Rules[0]
+ }
+ if len(schema.Payload) > 0 {
+ str = schema.Payload[0]
+ }
+ }
+
+ if str == "" {
+ continue
+ }
+
+ strategy.Insert(str)
}
- return append(rulePayload.Rules, rulePayload.Rules2...), nil
+ strategy.FinishInsert()
+
+ return strategy, nil
}
diff --git a/transport/gun/gun.go b/transport/gun/gun.go
index ae2ea6a4..36cf68f8 100644
--- a/transport/gun/gun.go
+++ b/transport/gun/gun.go
@@ -17,11 +17,11 @@ import (
"sync"
"time"
+ "github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/buf"
"github.com/Dreamacro/clash/common/pool"
tlsC "github.com/Dreamacro/clash/component/tls"
- "go.uber.org/atomic"
"golang.org/x/net/http2"
)
@@ -147,6 +147,7 @@ func (g *Conn) WriteBuffer(buffer *buf.Buffer) error {
dataLen := buffer.Len()
varLen := UVarintLen(uint64(dataLen))
header := buffer.ExtendHeader(6 + varLen)
+ _ = header[6] // bounds check hint to compiler
header[0] = 0x00
binary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+dataLen))
header[5] = 0x0A
diff --git a/transport/hysteria/conns/faketcp/tcp_linux.go b/transport/hysteria/conns/faketcp/tcp_linux.go
index dadb0912..cdee9fda 100644
--- a/transport/hysteria/conns/faketcp/tcp_linux.go
+++ b/transport/hysteria/conns/faketcp/tcp_linux.go
@@ -1,5 +1,5 @@
-//go:build linux
-// +build linux
+//go:build linux && !no_fake_tcp
+// +build linux,!no_fake_tcp
package faketcp
diff --git a/transport/hysteria/conns/faketcp/tcp_stub.go b/transport/hysteria/conns/faketcp/tcp_stub.go
index 9bc55077..9f9ff97d 100644
--- a/transport/hysteria/conns/faketcp/tcp_stub.go
+++ b/transport/hysteria/conns/faketcp/tcp_stub.go
@@ -1,5 +1,5 @@
-//go:build !linux
-// +build !linux
+//go:build !linux || no_fake_tcp
+// +build !linux no_fake_tcp
package faketcp
diff --git a/transport/simple-obfs/tls.go b/transport/simple-obfs/tls.go
index f41e3263..20166bbe 100644
--- a/transport/simple-obfs/tls.go
+++ b/transport/simple-obfs/tls.go
@@ -28,10 +28,10 @@ type TLSObfs struct {
func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
buf := pool.Get(discardN)
_, err := io.ReadFull(to.Conn, buf)
+ pool.Put(buf)
if err != nil {
return 0, err
}
- pool.Put(buf)
sizeBuf := make([]byte, 2)
_, err = io.ReadFull(to.Conn, sizeBuf)
diff --git a/transport/tuic/client.go b/transport/tuic/client.go
index 4932dc9b..af00da03 100644
--- a/transport/tuic/client.go
+++ b/transport/tuic/client.go
@@ -12,6 +12,7 @@ import (
"sync/atomic"
"time"
+ "github.com/Dreamacro/clash/common/buf"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant"
@@ -338,6 +339,14 @@ func (conn *earlyConn) Read(b []byte) (n int, err error) {
return conn.BufferedConn.Read(b)
}
+func (conn *earlyConn) ReadBuffer(buffer *buf.Buffer) (err error) {
+ err = conn.Response()
+ if err != nil {
+ return err
+ }
+ return conn.BufferedConn.ReadBuffer(buffer)
+}
+
func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.PacketConn, error) {
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
if err != nil {
diff --git a/transport/tuic/congestion/bbr_sender.go b/transport/tuic/congestion/bbr_sender.go
index d848a9a8..99164362 100644
--- a/transport/tuic/congestion/bbr_sender.go
+++ b/transport/tuic/congestion/bbr_sender.go
@@ -955,7 +955,7 @@ func (b *bbrSender) CalculateRecoveryWindow(ackedBytes, lostBytes congestion.Byt
b.recoveryWindow = maxByteCount(b.recoveryWindow, b.minCongestionWindow())
}
-var _ congestion.CongestionControl = &bbrSender{}
+var _ congestion.CongestionControl = (*bbrSender)(nil)
func (b *bbrSender) GetMinRtt() time.Duration {
if b.minRtt > 0 {
diff --git a/transport/tuic/conn.go b/transport/tuic/conn.go
index d5955e13..567f6ce5 100644
--- a/transport/tuic/conn.go
+++ b/transport/tuic/conn.go
@@ -2,7 +2,6 @@ package tuic
import (
"net"
- "net/netip"
"sync"
"sync/atomic"
"time"
@@ -103,7 +102,7 @@ func (q *quicStreamConn) RemoteAddr() net.Addr {
return q.rAddr
}
-var _ net.Conn = &quicStreamConn{}
+var _ net.Conn = (*quicStreamConn)(nil)
type quicStreamPacketConn struct {
connId uint32
@@ -216,11 +215,11 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro
}
buf := pool.GetBuffer()
defer pool.PutBuffer(buf)
- addrPort, err := netip.ParseAddrPort(addr.String())
+ address, err := NewAddressNetAddr(addr)
if err != nil {
return
}
- err = NewPacket(q.connId, uint16(len(p)), NewAddressAddrPort(addrPort), p).WriteTo(buf)
+ err = NewPacket(q.connId, uint16(len(p)), address, p).WriteTo(buf)
if err != nil {
return
}
@@ -252,4 +251,4 @@ func (q *quicStreamPacketConn) LocalAddr() net.Addr {
return q.quicConn.LocalAddr()
}
-var _ net.PacketConn = &quicStreamPacketConn{}
+var _ net.PacketConn = (*quicStreamPacketConn)(nil)
diff --git a/transport/tuic/pool_client.go b/transport/tuic/pool_client.go
index fe06c2f3..04ada7c0 100644
--- a/transport/tuic/pool_client.go
+++ b/transport/tuic/pool_client.go
@@ -67,11 +67,14 @@ func (t *PoolClient) dial(ctx context.Context, dialer C.Dialer, dialFn DialFunc)
return nil, nil, err
}
- dr.pc, dr.addr, dr.err = pc, addr, err
+ if _, ok := pc.(*net.UDPConn); ok { // only cache the system's UDPConn
+ dr.pc, dr.addr, dr.err = pc, addr, err
+
+ t.dialResultMutex.Lock()
+ t.dialResultMap[dialer] = dr
+ t.dialResultMutex.Unlock()
+ }
- t.dialResultMutex.Lock()
- t.dialResultMap[dialer] = dr
- t.dialResultMutex.Unlock()
return pc, addr, err
}
diff --git a/transport/tuic/protocol.go b/transport/tuic/protocol.go
index 570b6e54..a460eecc 100644
--- a/transport/tuic/protocol.go
+++ b/transport/tuic/protocol.go
@@ -464,6 +464,18 @@ func NewAddress(metadata *C.Metadata) Address {
}
}
+func NewAddressNetAddr(addr net.Addr) (Address, error) {
+ addrStr := addr.String()
+ if addrPort, err := netip.ParseAddrPort(addrStr); err == nil {
+ return NewAddressAddrPort(addrPort), nil
+ }
+ metadata := &C.Metadata{}
+ if err := metadata.SetRemoteAddress(addrStr); err != nil {
+ return Address{}, err
+ }
+ return NewAddress(metadata), nil
+}
+
func NewAddressAddrPort(addrPort netip.AddrPort) Address {
var addrType byte
port := addrPort.Port()
diff --git a/transport/tuic/server.go b/transport/tuic/server.go
index 5eb6e611..00c33fcb 100644
--- a/transport/tuic/server.go
+++ b/transport/tuic/server.go
@@ -17,7 +17,7 @@ import (
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
"github.com/metacubex/quic-go"
)
@@ -294,5 +294,5 @@ func (s *serverUDPPacket) Drop() {
s.packet.DATA = nil
}
-var _ C.UDPPacket = &serverUDPPacket{}
-var _ C.UDPPacketInAddr = &serverUDPPacket{}
+var _ C.UDPPacket = (*serverUDPPacket)(nil)
+var _ C.UDPPacketInAddr = (*serverUDPPacket)(nil)
diff --git a/transport/vless/conn.go b/transport/vless/conn.go
index 1f7d2cb3..6c3714e0 100644
--- a/transport/vless/conn.go
+++ b/transport/vless/conn.go
@@ -18,7 +18,7 @@ import (
tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/log"
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
utls "github.com/sagernet/utls"
xtls "github.com/xtls/go"
"google.golang.org/protobuf/proto"
diff --git a/transport/vless/filter.go b/transport/vless/filter.go
index 3ddfb8b9..f577be7a 100644
--- a/transport/vless/filter.go
+++ b/transport/vless/filter.go
@@ -34,7 +34,7 @@ func (vc *Conn) FilterTLS(buffer []byte) (index int) {
lenP := len(buffer)
vc.packetsToFilter--
if index = bytes.Index(buffer, tlsServerHandshakeStart); index != -1 {
- if lenP >= index+5 {
+ if lenP > index+5 {
if buffer[0] == 22 && buffer[1] == 3 && buffer[2] == 3 {
vc.isTLS = true
if buffer[5] == tlsHandshakeTypeServerHello {
@@ -49,7 +49,7 @@ func (vc *Conn) FilterTLS(buffer []byte) (index int) {
}
}
} else if index = bytes.Index(buffer, tlsClientHandshakeStart); index != -1 {
- if lenP >= index+5 && buffer[index+5] == tlsHandshakeTypeClientHello {
+ if lenP > index+5 && buffer[index+5] == tlsHandshakeTypeClientHello {
vc.isTLS = true
}
}
diff --git a/transport/vless/vision.go b/transport/vless/vision.go
index 8dc84e40..5649dde4 100644
--- a/transport/vless/vision.go
+++ b/transport/vless/vision.go
@@ -7,7 +7,7 @@ import (
"github.com/Dreamacro/clash/common/buf"
"github.com/Dreamacro/clash/log"
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
"github.com/zhangyunhao116/fastrand"
)
diff --git a/transport/vless/vless.go b/transport/vless/vless.go
index 6989374c..c2066afe 100644
--- a/transport/vless/vless.go
+++ b/transport/vless/vless.go
@@ -5,7 +5,7 @@ import (
"github.com/Dreamacro/clash/common/utils"
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
)
const (
diff --git a/transport/vmess/h2.go b/transport/vmess/h2.go
index 6901f61e..f91c2766 100644
--- a/transport/vmess/h2.go
+++ b/transport/vmess/h2.go
@@ -1,6 +1,7 @@
package vmess
import (
+ "context"
"io"
"net"
"net/http"
@@ -84,10 +85,16 @@ func (hc *h2Conn) Write(b []byte) (int, error) {
}
func (hc *h2Conn) Close() error {
- if err := hc.pwriter.Close(); err != nil {
- return err
+ if hc.pwriter != nil {
+ if err := hc.pwriter.Close(); err != nil {
+ return err
+ }
}
- if err := hc.ClientConn.Shutdown(hc.res.Request.Context()); err != nil {
+ ctx := context.Background()
+ if hc.res != nil {
+ ctx = hc.res.Request.Context()
+ }
+ if err := hc.ClientConn.Shutdown(ctx); err != nil {
return err
}
return hc.Conn.Close()
diff --git a/transport/vmess/user.go b/transport/vmess/user.go
index c098389e..091df0a8 100644
--- a/transport/vmess/user.go
+++ b/transport/vmess/user.go
@@ -4,7 +4,7 @@ import (
"bytes"
"crypto/md5"
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
)
// ID cmdKey length
diff --git a/transport/vmess/vmess.go b/transport/vmess/vmess.go
index ee7ce121..2dd071eb 100644
--- a/transport/vmess/vmess.go
+++ b/transport/vmess/vmess.go
@@ -7,7 +7,7 @@ import (
"github.com/Dreamacro/clash/common/utils"
- "github.com/gofrs/uuid"
+ "github.com/gofrs/uuid/v5"
"github.com/zhangyunhao116/fastrand"
)
diff --git a/transport/vmess/websocket.go b/transport/vmess/websocket.go
index 5fcaa0b8..e7335d84 100644
--- a/transport/vmess/websocket.go
+++ b/transport/vmess/websocket.go
@@ -107,6 +107,7 @@ func (wsc *websocketConn) WriteBuffer(buffer *buf.Buffer) error {
headerLen += 4 // MASK KEY
header := buffer.ExtendHeader(headerLen)
+ _ = header[2] // bounds check hint to compiler
header[0] = websocket.BinaryMessage | 1<<7
header[1] = 1 << 7
@@ -333,7 +334,10 @@ func streamWebsocketWithEarlyDataConn(conn net.Conn, c *WebsocketConfig) (net.Co
underlay: conn,
config: c,
}
- return conn, nil
+ // websocketWithEarlyDataConn can't correct handle Deadline
+ // it will not apply the already set Deadline after Dial()
+ // so call N.NewDeadlineConn to add a safe wrapper
+ return N.NewDeadlineConn(conn), nil
}
func streamWebsocketConn(conn net.Conn, c *WebsocketConfig, earlyData *bytes.Buffer) (net.Conn, error) {
@@ -401,11 +405,16 @@ func streamWebsocketConn(conn net.Conn, c *WebsocketConfig, earlyData *bytes.Buf
return nil, fmt.Errorf("dial %s error: %s", uri.Host, reason)
}
- return &websocketConn{
+ conn = &websocketConn{
conn: wsConn,
rawWriter: N.NewExtendedWriter(wsConn.UnderlyingConn()),
remoteAddr: conn.RemoteAddr(),
- }, nil
+ }
+ // websocketConn can't correct handle ReadDeadline
+ // gorilla/websocket will cache the os.ErrDeadlineExceeded from conn.Read()
+ // it will cause read fail and event panic in *websocket.Conn.NextReader()
+ // so call N.NewDeadlineConn to add a safe wrapper
+ return N.NewDeadlineConn(conn), nil
}
func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
diff --git a/tunnel/connection.go b/tunnel/connection.go
index e21bbdbf..c64a5266 100644
--- a/tunnel/connection.go
+++ b/tunnel/connection.go
@@ -13,8 +13,6 @@ import (
)
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error {
- defer packet.Drop()
-
addr := metadata.UDPAddr()
if addr == nil {
return errors.New("udp addr invalid")
diff --git a/tunnel/statistic/manager.go b/tunnel/statistic/manager.go
index e67d3871..381ea7e6 100644
--- a/tunnel/statistic/manager.go
+++ b/tunnel/statistic/manager.go
@@ -1,10 +1,13 @@
package statistic
import (
+ "os"
"sync"
"time"
- "go.uber.org/atomic"
+ "github.com/Dreamacro/clash/common/atomic"
+
+ "github.com/shirou/gopsutil/v3/process"
)
var DefaultManager *Manager
@@ -17,6 +20,7 @@ func init() {
downloadBlip: atomic.NewInt64(0),
uploadTotal: atomic.NewInt64(0),
downloadTotal: atomic.NewInt64(0),
+ process: &process.Process{Pid: int32(os.Getpid())},
}
go DefaultManager.handle()
@@ -30,6 +34,8 @@ type Manager struct {
downloadBlip *atomic.Int64
uploadTotal *atomic.Int64
downloadTotal *atomic.Int64
+ process *process.Process
+ memory uint64
}
func (m *Manager) Join(c tracker) {
@@ -54,6 +60,10 @@ func (m *Manager) Now() (up int64, down int64) {
return m.uploadBlip.Load(), m.downloadBlip.Load()
}
+func (m *Manager) Memory() uint64 {
+ return m.memory
+}
+
func (m *Manager) Snapshot() *Snapshot {
connections := []tracker{}
m.connections.Range(func(key, value any) bool {
@@ -61,10 +71,20 @@ func (m *Manager) Snapshot() *Snapshot {
return true
})
+ getMem := func() uint64 {
+ stat, err := m.process.MemoryInfo()
+ if err != nil {
+ return 0
+ }
+ return stat.RSS
+ }
+ m.memory = getMem()
+
return &Snapshot{
UploadTotal: m.uploadTotal.Load(),
DownloadTotal: m.downloadTotal.Load(),
Connections: connections,
+ Memory: m.memory,
}
}
@@ -92,4 +112,5 @@ type Snapshot struct {
DownloadTotal int64 `json:"downloadTotal"`
UploadTotal int64 `json:"uploadTotal"`
Connections []tracker `json:"connections"`
+ Memory uint64 `json:"memory"`
}
diff --git a/tunnel/statistic/tracker.go b/tunnel/statistic/tracker.go
index f7e9d971..c4feeddb 100644
--- a/tunnel/statistic/tracker.go
+++ b/tunnel/statistic/tracker.go
@@ -1,21 +1,23 @@
package statistic
import (
+ "io"
"net"
"time"
+ "github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/buf"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant"
- "github.com/gofrs/uuid"
- "go.uber.org/atomic"
+ "github.com/gofrs/uuid/v5"
)
type tracker interface {
ID() string
Close() error
+ C.Connection
}
type trackerInfo struct {
@@ -32,9 +34,9 @@ type trackerInfo struct {
type tcpTracker struct {
C.Conn `json:"-"`
*trackerInfo
- manager *Manager
- extendedReader N.ExtendedReader
- extendedWriter N.ExtendedWriter
+ manager *Manager
+
+ pushToManager bool `json:"-"`
}
func (tt *tcpTracker) ID() string {
@@ -44,35 +46,61 @@ func (tt *tcpTracker) ID() string {
func (tt *tcpTracker) Read(b []byte) (int, error) {
n, err := tt.Conn.Read(b)
download := int64(n)
- tt.manager.PushDownloaded(download)
+ if tt.pushToManager {
+ tt.manager.PushDownloaded(download)
+ }
tt.DownloadTotal.Add(download)
return n, err
}
func (tt *tcpTracker) ReadBuffer(buffer *buf.Buffer) (err error) {
- err = tt.extendedReader.ReadBuffer(buffer)
+ err = tt.Conn.ReadBuffer(buffer)
download := int64(buffer.Len())
- tt.manager.PushDownloaded(download)
+ if tt.pushToManager {
+ tt.manager.PushDownloaded(download)
+ }
tt.DownloadTotal.Add(download)
return
}
+func (tt *tcpTracker) UnwrapReader() (io.Reader, []N.CountFunc) {
+ return tt.Conn, []N.CountFunc{func(download int64) {
+ if tt.pushToManager {
+ tt.manager.PushDownloaded(download)
+ }
+ tt.DownloadTotal.Add(download)
+ }}
+}
+
func (tt *tcpTracker) Write(b []byte) (int, error) {
n, err := tt.Conn.Write(b)
upload := int64(n)
- tt.manager.PushUploaded(upload)
+ if tt.pushToManager {
+ tt.manager.PushUploaded(upload)
+ }
tt.UploadTotal.Add(upload)
return n, err
}
func (tt *tcpTracker) WriteBuffer(buffer *buf.Buffer) (err error) {
upload := int64(buffer.Len())
- err = tt.extendedWriter.WriteBuffer(buffer)
- tt.manager.PushUploaded(upload)
+ err = tt.Conn.WriteBuffer(buffer)
+ if tt.pushToManager {
+ tt.manager.PushUploaded(upload)
+ }
tt.UploadTotal.Add(upload)
return
}
+func (tt *tcpTracker) UnwrapWriter() (io.Writer, []N.CountFunc) {
+ return tt.Conn, []N.CountFunc{func(upload int64) {
+ if tt.pushToManager {
+ tt.manager.PushUploaded(upload)
+ }
+ tt.UploadTotal.Add(upload)
+ }}
+}
+
func (tt *tcpTracker) Close() error {
tt.manager.Leave(tt)
return tt.Conn.Close()
@@ -82,7 +110,7 @@ func (tt *tcpTracker) Upstream() any {
return tt.Conn
}
-func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64) *tcpTracker {
+func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64, pushToManager bool) *tcpTracker {
if conn != nil {
if tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
metadata.RemoteDst = tcpAddr.IP.String()
@@ -103,8 +131,16 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
UploadTotal: atomic.NewInt64(uploadTotal),
DownloadTotal: atomic.NewInt64(downloadTotal),
},
- extendedReader: N.NewExtendedReader(conn),
- extendedWriter: N.NewExtendedWriter(conn),
+ pushToManager: pushToManager,
+ }
+
+ if pushToManager {
+ if uploadTotal > 0 {
+ manager.PushUploaded(uploadTotal)
+ }
+ if downloadTotal > 0 {
+ manager.PushDownloaded(downloadTotal)
+ }
}
if rule != nil {
@@ -120,6 +156,8 @@ type udpTracker struct {
C.PacketConn `json:"-"`
*trackerInfo
manager *Manager
+
+ pushToManager bool `json:"-"`
}
func (ut *udpTracker) ID() string {
@@ -129,7 +167,9 @@ func (ut *udpTracker) ID() string {
func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) {
n, addr, err := ut.PacketConn.ReadFrom(b)
download := int64(n)
- ut.manager.PushDownloaded(download)
+ if ut.pushToManager {
+ ut.manager.PushDownloaded(download)
+ }
ut.DownloadTotal.Add(download)
return n, addr, err
}
@@ -137,7 +177,9 @@ func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) {
func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) {
n, err := ut.PacketConn.WriteTo(b, addr)
upload := int64(n)
- ut.manager.PushUploaded(upload)
+ if ut.pushToManager {
+ ut.manager.PushUploaded(upload)
+ }
ut.UploadTotal.Add(upload)
return n, err
}
@@ -147,7 +189,7 @@ func (ut *udpTracker) Close() error {
return ut.PacketConn.Close()
}
-func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64) *udpTracker {
+func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64, pushToManager bool) *udpTracker {
metadata.RemoteDst = conn.RemoteDestination()
ut := &udpTracker{
@@ -162,6 +204,16 @@ func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru
UploadTotal: atomic.NewInt64(uploadTotal),
DownloadTotal: atomic.NewInt64(downloadTotal),
},
+ pushToManager: pushToManager,
+ }
+
+ if pushToManager {
+ if uploadTotal > 0 {
+ manager.PushUploaded(uploadTotal)
+ }
+ if downloadTotal > 0 {
+ manager.PushDownloaded(downloadTotal)
+ }
}
if rule != nil {
diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go
index c4f55fbd..a4a473e9 100644
--- a/tunnel/tunnel.go
+++ b/tunnel/tunnel.go
@@ -273,6 +273,7 @@ func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, r
func handleUDPConn(packet C.PacketAdapter) {
metadata := packet.Metadata()
if !metadata.Valid() {
+ packet.Drop()
log.Warnln("[Metadata] not valid: %#v", metadata)
return
}
@@ -284,6 +285,7 @@ func handleUDPConn(packet C.PacketAdapter) {
}
if err := preHandleMetadata(metadata); err != nil {
+ packet.Drop()
log.Debugln("[Metadata PreHandle] error: %s", err)
return
}
@@ -292,6 +294,7 @@ func handleUDPConn(packet C.PacketAdapter) {
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(context.Background(), metadata.Host)
if err != nil {
+ packet.Drop()
return
}
metadata.DstIP = ip
@@ -309,6 +312,7 @@ func handleUDPConn(packet C.PacketAdapter) {
}
if handle() {
+ packet.Drop()
return
}
@@ -316,6 +320,8 @@ func handleUDPConn(packet C.PacketAdapter) {
cond, loaded := natTable.GetOrCreateLock(lockKey)
go func() {
+ defer packet.Drop()
+
if loaded {
cond.L.Lock()
cond.Wait()
@@ -358,7 +364,7 @@ func handleUDPConn(packet C.PacketAdapter) {
}
pCtx.InjectPacketConn(rawPc)
- pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule, 0, 0)
+ pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule, 0, 0, true)
switch true {
case metadata.SpecialProxy != "":
@@ -488,7 +494,7 @@ func handleTCPConn(connCtx C.ConnContext) {
return
}
- remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule, 0, int64(peekLen))
+ remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule, 0, int64(peekLen), true)
defer func(remoteConn C.Conn) {
_ = remoteConn.Close()
}(remoteConn)