From 1a7830f18ecd9185dedef1340b7d4fcd55c6e503 Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Sun, 7 Nov 2021 16:48:51 +0800 Subject: [PATCH] Feature: dial different NIC for all proxies (#1714) --- adapter/adapter.go | 9 +++--- adapter/outbound/base.go | 43 +++++++++++++++++++++++----- adapter/outbound/direct.go | 8 +++--- adapter/outbound/http.go | 12 ++++---- adapter/outbound/reject.go | 5 ++-- adapter/outbound/shadowsocks.go | 18 ++++++------ adapter/outbound/shadowsocksr.go | 18 ++++++------ adapter/outbound/snell.go | 16 ++++++----- adapter/outbound/socks5.go | 20 +++++++------ adapter/outbound/trojan.go | 24 +++++++++------- adapter/outbound/vmess.go | 24 +++++++++------- adapter/outboundgroup/fallback.go | 19 +++++++----- adapter/outboundgroup/loadbalance.go | 19 +++++++----- adapter/outboundgroup/parser.go | 2 ++ adapter/outboundgroup/relay.go | 16 +++++++---- adapter/outboundgroup/selector.go | 19 +++++++----- adapter/outboundgroup/urltest.go | 19 +++++++----- component/dialer/dialer.go | 20 ++++++------- component/dialer/options.go | 22 +++++++------- constant/adapters.go | 6 ++-- hub/executor/executor.go | 6 +--- 21 files changed, 206 insertions(+), 139 deletions(-) diff --git a/adapter/adapter.go b/adapter/adapter.go index 263301636a..76bbe2e3d0 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -10,6 +10,7 @@ import ( "time" "github.com/Dreamacro/clash/common/queue" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "go.uber.org/atomic" @@ -34,8 +35,8 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { } // DialContext implements C.ProxyAdapter -func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - conn, err := p.ProxyAdapter.DialContext(ctx, metadata) +func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...) p.alive.Store(err == nil) return conn, err } @@ -48,8 +49,8 @@ func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { } // ListenPacketContext implements C.ProxyAdapter -func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { - pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata) +func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...) p.alive.Store(err == nil) return pc, err } diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go index 22c0142b0a..e028cd4e8f 100644 --- a/adapter/outbound/base.go +++ b/adapter/outbound/base.go @@ -6,14 +6,16 @@ import ( "errors" "net" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" ) type Base struct { - name string - addr string - tp C.AdapterType - udp bool + name string + addr string + iface string + tp C.AdapterType + udp bool } // Name implements C.ProxyAdapter @@ -32,7 +34,7 @@ func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { } // ListenPacketContext implements C.ProxyAdapter -func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { +func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { return nil, errors.New("no support") } @@ -58,8 +60,35 @@ func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy { return nil } -func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base { - return &Base{name, addr, tp, udp} +// DialOptions return []dialer.Option from struct +func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option { + if b.iface != "" { + opts = append(opts, dialer.WithInterface(b.iface)) + } + + return opts +} + +type BasicOption struct { + Interface string `proxy:"interface-name"` +} + +type BaseOption struct { + Name string + Addr string + Type C.AdapterType + UDP bool + Interface string +} + +func NewBase(opt BaseOption) *Base { + return &Base{ + name: opt.Name, + addr: opt.Addr, + tp: opt.Type, + udp: opt.UDP, + iface: opt.Interface, + } } type conn struct { diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index 42433c410c..4c4305f572 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -13,8 +13,8 @@ type Direct struct { } // DialContext implements C.ProxyAdapter -func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress()) +func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...) if err != nil { return nil, err } @@ -23,8 +23,8 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, } // ListenPacketContext implements C.ProxyAdapter -func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket(ctx, "udp", "") +func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...) if err != nil { return nil, err } diff --git a/adapter/outbound/http.go b/adapter/outbound/http.go index b4dffdf783..7f480ce6b0 100644 --- a/adapter/outbound/http.go +++ b/adapter/outbound/http.go @@ -25,6 +25,7 @@ type Http struct { } type HttpOption struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -53,8 +54,8 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { } // DialContext implements C.ProxyAdapter -func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { - c, err := dialer.DialContext(ctx, "tcp", h.addr) +func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + c, err := dialer.DialContext(ctx, "tcp", h.addr, h.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", h.addr, err) } @@ -131,9 +132,10 @@ func NewHttp(option HttpOption) *Http { return &Http{ Base: &Base{ - name: option.Name, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - tp: C.Http, + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.Http, + iface: option.Interface, }, user: option.UserName, pass: option.Password, diff --git a/adapter/outbound/reject.go b/adapter/outbound/reject.go index a97c6c7113..f1940d39af 100644 --- a/adapter/outbound/reject.go +++ b/adapter/outbound/reject.go @@ -7,6 +7,7 @@ import ( "net" "time" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" ) @@ -15,12 +16,12 @@ type Reject struct { } // DialContext implements C.ProxyAdapter -func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { +func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { return NewConn(&NopConn{}, r), nil } // ListenPacketContext implements C.ProxyAdapter -func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { +func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { return nil, errors.New("match reject rule") } diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 0194954e75..5e5ebd7c43 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -28,6 +28,7 @@ type ShadowSocks struct { } type ShadowSocksOption struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -74,8 +75,8 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e } // DialContext implements C.ProxyAdapter -func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { - c, err := dialer.DialContext(ctx, "tcp", ss.addr) +func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } @@ -88,8 +89,8 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ } // ListenPacketContext implements C.ProxyAdapter -func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket(ctx, "udp", "") +func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...) if err != nil { return nil, err } @@ -154,10 +155,11 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { return &ShadowSocks{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.Shadowsocks, - udp: option.UDP, + name: option.Name, + addr: addr, + tp: C.Shadowsocks, + udp: option.UDP, + iface: option.Interface, }, cipher: ciph, diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go index fb0dd7a5fb..d244df4068 100644 --- a/adapter/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -24,6 +24,7 @@ type ShadowSocksR struct { } type ShadowSocksROption struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -59,8 +60,8 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, } // DialContext implements C.ProxyAdapter -func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { - c, err := dialer.DialContext(ctx, "tcp", ssr.addr) +func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + c, err := dialer.DialContext(ctx, "tcp", ssr.addr, ssr.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) } @@ -73,8 +74,8 @@ func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) } // ListenPacketContext implements C.ProxyAdapter -func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket(ctx, "udp", "") +func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := dialer.ListenPacket(ctx, "udp", "", ssr.Base.DialOptions(opts...)...) if err != nil { return nil, err } @@ -136,10 +137,11 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { return &ShadowSocksR{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.ShadowsocksR, - udp: option.UDP, + name: option.Name, + addr: addr, + tp: C.ShadowsocksR, + udp: option.UDP, + iface: option.Interface, }, cipher: coreCiph, obfs: obfs, diff --git a/adapter/outbound/snell.go b/adapter/outbound/snell.go index 2dc16a5113..40dcf06649 100644 --- a/adapter/outbound/snell.go +++ b/adapter/outbound/snell.go @@ -22,6 +22,7 @@ type Snell struct { } type SnellOption struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -57,8 +58,8 @@ func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { } // DialContext implements C.ProxyAdapter -func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { - if s.version == snell.Version2 { +func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + if s.version == snell.Version2 && len(opts) == 0 { c, err := s.pool.Get() if err != nil { return nil, err @@ -72,7 +73,7 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn return NewConn(c, s), err } - c, err := dialer.DialContext(ctx, "tcp", s.addr) + c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", s.addr, err) } @@ -111,9 +112,10 @@ func NewSnell(option SnellOption) (*Snell, error) { s := &Snell{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.Snell, + name: option.Name, + addr: addr, + tp: C.Snell, + iface: option.Interface, }, psk: psk, obfsOption: obfsOption, @@ -122,7 +124,7 @@ 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) + c, err := dialer.DialContext(ctx, "tcp", addr, s.Base.DialOptions()...) if err != nil { return nil, err } diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index 7714c5c52f..874da9f145 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -24,6 +24,7 @@ type Socks5 struct { } type Socks5Option struct { + *BaseOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -59,8 +60,8 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) } // DialContext implements C.ProxyAdapter -func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { - c, err := dialer.DialContext(ctx, "tcp", ss.addr) +func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } @@ -77,8 +78,8 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Co } // ListenPacketContext implements C.ProxyAdapter -func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { - c, err := dialer.DialContext(ctx, "tcp", ss.addr) +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...)...) if err != nil { err = fmt.Errorf("%s connect error: %w", ss.addr, err) return @@ -107,7 +108,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata) return } - pc, err := dialer.ListenPacket(ctx, "udp", "") + pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...) if err != nil { return } @@ -148,10 +149,11 @@ func NewSocks5(option Socks5Option) *Socks5 { return &Socks5{ Base: &Base{ - name: option.Name, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - tp: C.Socks5, - udp: option.UDP, + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.Socks5, + udp: option.UDP, + iface: option.Interface, }, user: option.UserName, pass: option.Password, diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index 8bea0cc8ff..35dbea1e0e 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -28,6 +28,7 @@ type Trojan struct { } type TrojanOption struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -86,9 +87,9 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) } // DialContext implements C.ProxyAdapter -func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { +func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { // gun transport - if t.transport != nil { + if t.transport != nil && len(opts) == 0 { c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig) if err != nil { return nil, err @@ -102,7 +103,7 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con return NewConn(c, t), nil } - c, err := dialer.DialContext(ctx, "tcp", t.addr) + c, err := dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } @@ -119,18 +120,18 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con } // ListenPacketContext implements C.ProxyAdapter -func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { +func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { var c net.Conn // grpc transport - if t.transport != nil { + if t.transport != nil && len(opts) == 0 { c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } defer safeConnClose(c, err) } else { - c, err = dialer.DialContext(ctx, "tcp", t.addr) + c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } @@ -167,10 +168,11 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { t := &Trojan{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.Trojan, - udp: option.UDP, + name: option.Name, + addr: addr, + tp: C.Trojan, + udp: option.UDP, + iface: option.Interface, }, instance: trojan.New(tOption), option: &option, @@ -178,7 +180,7 @@ 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) + c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...) if err != nil { return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) } diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index b4db00ad93..6746eb5189 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -31,6 +31,7 @@ type Vmess struct { } type VmessOption struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -195,9 +196,9 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { } // DialContext implements C.ProxyAdapter -func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { +func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { // gun transport - if v.transport != nil { + if v.transport != nil && len(opts) == 0 { c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig) if err != nil { return nil, err @@ -212,7 +213,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn return NewConn(c, v), nil } - c, err := dialer.DialContext(ctx, "tcp", v.addr) + c, err := dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } @@ -224,7 +225,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn } // ListenPacketContext implements C.ProxyAdapter -func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { +func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr if !metadata.Resolved() { ip, err := resolver.ResolveIP(metadata.Host) @@ -236,7 +237,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata) ( var c net.Conn // gun transport - if v.transport != nil { + if v.transport != nil && len(opts) == 0 { c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig) if err != nil { return nil, err @@ -245,7 +246,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata) ( c, err = v.client.StreamConn(c, parseVmessAddr(metadata)) } else { - c, err = dialer.DialContext(ctx, "tcp", v.addr) + c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } @@ -285,10 +286,11 @@ func NewVmess(option VmessOption) (*Vmess, error) { v := &Vmess{ Base: &Base{ - name: option.Name, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - tp: C.Vmess, - udp: option.UDP, + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.Vmess, + udp: option.UDP, + iface: option.Interface, }, client: client, option: &option, @@ -301,7 +303,7 @@ 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) + c, err := dialer.DialContext(context.Background(), "tcp", v.addr, v.Base.DialOptions()...) if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 3221b55279..2f9f0887fe 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -6,6 +6,7 @@ import ( "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/singledo" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" ) @@ -23,9 +24,9 @@ func (f *Fallback) Now() string { } // DialContext implements C.ProxyAdapter -func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { +func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { proxy := f.findAliveProxy(true) - c, err := proxy.DialContext(ctx, metadata) + c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...) if err == nil { c.AppendToChains(f) } @@ -33,9 +34,9 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con } // ListenPacketContext implements C.ProxyAdapter -func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { +func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { proxy := f.findAliveProxy(true) - pc, err := proxy.ListenPacketContext(ctx, metadata) + pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...) if err == nil { pc.AppendToChains(f) } @@ -90,11 +91,15 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy { return proxies[0] } -func NewFallback(options *GroupCommonOption, providers []provider.ProxyProvider) *Fallback { +func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback { return &Fallback{ - Base: outbound.NewBase(options.Name, "", C.Fallback, false), + Base: outbound.NewBase(outbound.BaseOption{ + Name: option.Name, + Type: C.Fallback, + Interface: option.Interface, + }), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, - disableUDP: options.DisableUDP, + disableUDP: option.DisableUDP, } } diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index fb2840102b..df1c2ba49b 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -10,6 +10,7 @@ import ( "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/murmur3" "github.com/Dreamacro/clash/common/singledo" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" @@ -69,7 +70,7 @@ func jumpHash(key uint64, buckets int32) int32 { } // DialContext implements C.ProxyAdapter -func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { +func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) { defer func() { if err == nil { c.AppendToChains(lb) @@ -78,12 +79,12 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c proxy := lb.Unwrap(metadata) - c, err = proxy.DialContext(ctx, metadata) + c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...) return } // ListenPacketContext implements C.ProxyAdapter -func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (pc C.PacketConn, err error) { +func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (pc C.PacketConn, err error) { defer func() { if err == nil { pc.AppendToChains(lb) @@ -91,7 +92,7 @@ func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Meta }() proxy := lb.Unwrap(metadata) - return proxy.ListenPacketContext(ctx, metadata) + return proxy.ListenPacketContext(ctx, metadata, lb.Base.DialOptions(opts...)...) } // SupportUDP implements C.ProxyAdapter @@ -158,7 +159,7 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) { }) } -func NewLoadBalance(options *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) { +func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) { var strategyFn strategyFn switch strategy { case "consistent-hashing": @@ -169,10 +170,14 @@ func NewLoadBalance(options *GroupCommonOption, providers []provider.ProxyProvid return nil, fmt.Errorf("%w: %s", errStrategy, strategy) } return &LoadBalance{ - Base: outbound.NewBase(options.Name, "", C.LoadBalance, false), + Base: outbound.NewBase(outbound.BaseOption{ + Name: option.Name, + Type: C.LoadBalance, + Interface: option.Interface, + }), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, strategyFn: strategyFn, - disableUDP: options.DisableUDP, + disableUDP: option.DisableUDP, }, nil } diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index cf97579f9e..190d333de9 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/provider" "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" @@ -19,6 +20,7 @@ var ( ) type GroupCommonOption struct { + outbound.BasicOption Name string `group:"name"` Type string `group:"type"` Proxies []string `group:"proxies,omitempty"` diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index 78410a4493..4ec4ffacfd 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -19,7 +19,7 @@ type Relay struct { } // DialContext implements C.ProxyAdapter -func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { +func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { var proxies []C.Proxy for _, proxy := range r.proxies(metadata, true) { if proxy.Type() != C.Direct { @@ -29,15 +29,15 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, switch len(proxies) { case 0: - return outbound.NewDirect().DialContext(ctx, metadata) + return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...) case 1: - return proxies[0].DialContext(ctx, metadata) + return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...) } first := proxies[0] last := proxies[len(proxies)-1] - c, err := dialer.DialContext(ctx, "tcp", first.Addr()) + c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) } @@ -100,9 +100,13 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy { return proxies } -func NewRelay(options *GroupCommonOption, providers []provider.ProxyProvider) *Relay { +func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay { return &Relay{ - Base: outbound.NewBase(options.Name, "", C.Relay, false), + Base: outbound.NewBase(outbound.BaseOption{ + Name: option.Name, + Type: C.Relay, + Interface: option.Interface, + }), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, } diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index 008e8af8d5..4ec67cd1d9 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -7,6 +7,7 @@ import ( "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/singledo" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" ) @@ -20,8 +21,8 @@ type Selector struct { } // DialContext implements C.ProxyAdapter -func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := s.selectedProxy(true).DialContext(ctx, metadata) +func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + c, err := s.selectedProxy(true).DialContext(ctx, metadata, s.Base.DialOptions(opts...)...) if err == nil { c.AppendToChains(s) } @@ -29,8 +30,8 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con } // ListenPacketContext implements C.ProxyAdapter -func (s *Selector) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { - pc, err := s.selectedProxy(true).ListenPacketContext(ctx, metadata) +func (s *Selector) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := s.selectedProxy(true).ListenPacketContext(ctx, metadata, s.Base.DialOptions(opts...)...) if err == nil { pc.AppendToChains(s) } @@ -96,13 +97,17 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy { return elm.(C.Proxy) } -func NewSelector(options *GroupCommonOption, providers []provider.ProxyProvider) *Selector { +func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { selected := providers[0].Proxies()[0].Name() return &Selector{ - Base: outbound.NewBase(options.Name, "", C.Selector, false), + Base: outbound.NewBase(outbound.BaseOption{ + Name: option.Name, + Type: C.Selector, + Interface: option.Interface, + }), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, selected: selected, - disableUDP: options.DisableUDP, + disableUDP: option.DisableUDP, } } diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index b27f12a446..6866db0df9 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -7,6 +7,7 @@ import ( "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/singledo" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" ) @@ -34,8 +35,8 @@ func (u *URLTest) Now() string { } // DialContext implements C.ProxyAdapter -func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { - c, err = u.fast(true).DialContext(ctx, metadata) +func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) { + c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...) if err == nil { c.AppendToChains(u) } @@ -43,8 +44,8 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co } // ListenPacketContext implements C.ProxyAdapter -func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { - pc, err := u.fast(true).ListenPacketContext(ctx, metadata) +func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...) if err == nil { pc.AppendToChains(u) } @@ -133,13 +134,17 @@ func parseURLTestOption(config map[string]interface{}) []urlTestOption { return opts } -func NewURLTest(commonOptions *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { +func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { urlTest := &URLTest{ - Base: outbound.NewBase(commonOptions.Name, "", C.URLTest, false), + Base: outbound.NewBase(outbound.BaseOption{ + Name: option.Name, + Type: C.URLTest, + Interface: option.Interface, + }), single: singledo.NewSingle(defaultGetProxiesDuration), fastSingle: singledo.NewSingle(time.Second * 10), providers: providers, - disableUDP: commonOptions.DisableUDP, + disableUDP: option.DisableUDP, } for _, option := range options { diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index 75bbb8687f..87b62dd65a 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -36,12 +36,12 @@ func DialContext(ctx context.Context, network, address string, options ...Option } func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) { - cfg := &config{} + cfg := &option{ + interfaceName: DefaultInterface.Load(), + } - if !cfg.skipDefault { - for _, o := range DefaultOptions { - o(cfg) - } + for _, o := range DefaultOptions { + o(cfg) } for _, o := range options { @@ -64,12 +64,12 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio } func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) { - opt := &config{} + opt := &option{ + interfaceName: DefaultInterface.Load(), + } - if !opt.skipDefault { - for _, o := range DefaultOptions { - o(opt) - } + for _, o := range DefaultOptions { + o(opt) } for _, o := range options { diff --git a/component/dialer/options.go b/component/dialer/options.go index 33083864a3..96d9eb751f 100644 --- a/component/dialer/options.go +++ b/component/dialer/options.go @@ -1,29 +1,27 @@ package dialer -var DefaultOptions []Option +import "go.uber.org/atomic" -type config struct { - skipDefault bool +var ( + DefaultOptions []Option + DefaultInterface = atomic.NewString("") +) + +type option struct { interfaceName string addrReuse bool } -type Option func(opt *config) +type Option func(opt *option) func WithInterface(name string) Option { - return func(opt *config) { + return func(opt *option) { opt.interfaceName = name } } func WithAddrReuse(reuse bool) Option { - return func(opt *config) { + return func(opt *option) { opt.addrReuse = reuse } } - -func WithSkipDefault(skip bool) Option { - return func(opt *config) { - opt.skipDefault = skip - } -} diff --git a/constant/adapters.go b/constant/adapters.go index 2f7006e962..59fde96019 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -5,6 +5,8 @@ import ( "fmt" "net" "time" + + "github.com/Dreamacro/clash/component/dialer" ) // Adapter Type @@ -90,9 +92,9 @@ type ProxyAdapter interface { // DialContext return a C.Conn with protocol which // contains multiplexing-related reuse logic (if any) - DialContext(ctx context.Context, metadata *Metadata) (Conn, error) + DialContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (Conn, error) - ListenPacketContext(ctx context.Context, metadata *Metadata) (PacketConn, error) + ListenPacketContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (PacketConn, error) // Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract. Unwrap(metadata *Metadata) Proxy diff --git a/hub/executor/executor.go b/hub/executor/executor.go index ca8ee95ded..59f948817e 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -168,11 +168,7 @@ func updateGeneral(general *config.General, force bool) { tunnel.SetMode(general.Mode) resolver.DisableIPv6 = !general.IPv6 - if general.Interface != "" { - dialer.DefaultOptions = []dialer.Option{dialer.WithInterface(general.Interface)} - } else { - dialer.DefaultOptions = nil - } + dialer.DefaultInterface.Store(general.Interface) iface.FlushCache()