From a5b950a779871ae7045653a242bc7b3906011cdd Mon Sep 17 00:00:00 2001 From: Kr328 Date: Mon, 6 Sep 2021 23:07:34 +0800 Subject: [PATCH] Feature: add dhcp type dns client (#1509) --- adapter/outbound/direct.go | 2 +- adapter/outbound/shadowsocks.go | 2 +- adapter/outbound/shadowsocksr.go | 2 +- adapter/outbound/socks5.go | 2 +- adapter/provider/vehicle.go | 5 +- component/dhcp/conn.go | 18 ++++ component/dhcp/dhcp.go | 94 +++++++++++++++++++ component/dialer/bind.go | 118 ------------------------ component/dialer/bind_darwin.go | 40 +++++---- component/dialer/bind_linux.go | 26 ++++-- component/dialer/bind_others.go | 89 ++++++++++++++++-- component/dialer/dialer.go | 97 ++++++++++---------- component/dialer/hook.go | 43 --------- component/dialer/options.go | 31 +++++++ component/dialer/reuse_others.go | 10 +++ component/dialer/reuse_unix.go | 28 ++++++ component/dialer/reuse_windows.go | 24 +++++ component/iface/iface.go | 113 +++++++++++++++++++++++ config/config.go | 3 + dns/client.go | 33 ++++--- dns/dhcp.go | 144 ++++++++++++++++++++++++++++++ dns/resolver.go | 35 ++++---- dns/util.go | 6 +- go.mod | 8 +- go.sum | 65 ++++++++++++-- hub/executor/executor.go | 9 +- 26 files changed, 759 insertions(+), 288 deletions(-) create mode 100644 component/dhcp/conn.go create mode 100644 component/dhcp/dhcp.go delete mode 100644 component/dialer/bind.go delete mode 100644 component/dialer/hook.go create mode 100644 component/dialer/options.go create mode 100644 component/dialer/reuse_others.go create mode 100644 component/dialer/reuse_unix.go create mode 100644 component/dialer/reuse_windows.go create mode 100644 component/iface/iface.go create mode 100644 dns/dhcp.go diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index 83ea22eb..4b53e306 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -24,7 +24,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, // DialUDP implements C.ProxyAdapter func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket("udp", "") + pc, err := dialer.ListenPacket(context.Background(), "udp", "") if err != nil { return nil, err } diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 39d1e36d..64b431a9 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -89,7 +89,7 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ // DialUDP implements C.ProxyAdapter func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket("udp", "") + pc, err := dialer.ListenPacket(context.Background(), "udp", "") if err != nil { return nil, err } diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go index b44f46e5..9ba8bc22 100644 --- a/adapter/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -74,7 +74,7 @@ func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) // DialUDP implements C.ProxyAdapter func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket("udp", "") + pc, err := dialer.ListenPacket(context.Background(), "udp", "") if err != nil { return nil, err } diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index 8106e0e2..1402c618 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -110,7 +110,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { return } - pc, err := dialer.ListenPacket("udp", "") + pc, err := dialer.ListenPacket(context.Background(), "udp", "") if err != nil { return } diff --git a/adapter/provider/vehicle.go b/adapter/provider/vehicle.go index ea6a835a..f556e69c 100644 --- a/adapter/provider/vehicle.go +++ b/adapter/provider/vehicle.go @@ -3,6 +3,7 @@ package provider import ( "context" "io/ioutil" + "net" "net/http" "net/url" "time" @@ -71,7 +72,9 @@ func (h *HTTPVehicle) Read() ([]byte, error) { IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, - DialContext: dialer.DialContext, + DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + return dialer.DialContext(ctx, network, address) + }, } client := http.Client{Transport: transport} diff --git a/component/dhcp/conn.go b/component/dhcp/conn.go new file mode 100644 index 00000000..90a9e25b --- /dev/null +++ b/component/dhcp/conn.go @@ -0,0 +1,18 @@ +package dhcp + +import ( + "context" + "net" + "runtime" + + "github.com/Dreamacro/clash/component/dialer" +) + +func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, error) { + listenAddr := "0.0.0.0:68" + if runtime.GOOS == "linux" || runtime.GOOS == "android" { + listenAddr = "255.255.255.255:68" + } + + return dialer.ListenPacket(ctx, "udp4", listenAddr, dialer.WithInterface(ifaceName), dialer.WithAddrReuse(true)) +} diff --git a/component/dhcp/dhcp.go b/component/dhcp/dhcp.go new file mode 100644 index 00000000..b2ca6928 --- /dev/null +++ b/component/dhcp/dhcp.go @@ -0,0 +1,94 @@ +package dhcp + +import ( + "context" + "errors" + "math/rand" + "net" + + "github.com/insomniacslk/dhcp/dhcpv4" +) + +var ( + ErrNotResponding = errors.New("DHCP not responding") + ErrNotFound = errors.New("DNS option not found") +) + +func ResolveDNSFromDHCP(context context.Context, ifaceName string) ([]net.IP, error) { + conn, err := ListenDHCPClient(context, ifaceName) + if err != nil { + return nil, err + } + defer conn.Close() + + result := make(chan []net.IP, 1) + + discovery, err := dhcpv4.NewDiscovery(randomHardware(), dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer)) + if err != nil { + return nil, err + } + + go receiveOffer(conn, discovery.TransactionID, result) + + _, err = conn.WriteTo(discovery.ToBytes(), &net.UDPAddr{IP: net.IPv4bcast, Port: 67}) + if err != nil { + return nil, err + } + + select { + case r, ok := <-result: + if !ok { + return nil, ErrNotFound + } + return r, nil + case <-context.Done(): + return nil, ErrNotResponding + } +} + +func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- []net.IP) { + defer close(result) + + buf := make([]byte, dhcpv4.MaxMessageSize) + + for { + n, _, err := conn.ReadFrom(buf) + if err != nil { + return + } + + pkt, err := dhcpv4.FromBytes(buf[:n]) + if err != nil { + continue + } + + if pkt.MessageType() != dhcpv4.MessageTypeOffer { + continue + } + + if pkt.TransactionID != id { + continue + } + + dns := pkt.DNS() + if len(dns) == 0 { + return + } + + result <- dns + + return + } +} + +func randomHardware() net.HardwareAddr { + addr := make(net.HardwareAddr, 6) + + addr[0] = 0xff + + for i := 1; i < len(addr); i++ { + addr[i] = byte(rand.Intn(254) + 1) + } + + return addr +} diff --git a/component/dialer/bind.go b/component/dialer/bind.go deleted file mode 100644 index 0ad52068..00000000 --- a/component/dialer/bind.go +++ /dev/null @@ -1,118 +0,0 @@ -package dialer - -import ( - "errors" - "net" - "time" - - "github.com/Dreamacro/clash/common/singledo" -) - -// In some OS, such as Windows, it takes a little longer to get interface information -var ifaceSingle = singledo.NewSingle(time.Second * 20) - -var ( - errPlatformNotSupport = errors.New("unsupport platform") -) - -func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) { - ipv4 := ip.To4() != nil - - for _, elm := range addrs { - addr, ok := elm.(*net.IPNet) - if !ok { - continue - } - - addrV4 := addr.IP.To4() != nil - - if addrV4 && ipv4 { - return &net.TCPAddr{IP: addr.IP, Port: 0}, nil - } else if !addrV4 && !ipv4 { - return &net.TCPAddr{IP: addr.IP, Port: 0}, nil - } - } - - return nil, ErrAddrNotFound -} - -func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) { - ipv4 := ip.To4() != nil - - for _, elm := range addrs { - addr, ok := elm.(*net.IPNet) - if !ok { - continue - } - - addrV4 := addr.IP.To4() != nil - - if addrV4 && ipv4 { - return &net.UDPAddr{IP: addr.IP, Port: 0}, nil - } else if !addrV4 && !ipv4 { - return &net.UDPAddr{IP: addr.IP, Port: 0}, nil - } - } - - return nil, ErrAddrNotFound -} - -func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error { - if !ip.IsGlobalUnicast() { - return nil - } - - iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { - return net.InterfaceByName(name) - }) - if err != nil { - return err - } - - addrs, err := iface.(*net.Interface).Addrs() - if err != nil { - return err - } - - switch network { - case "tcp", "tcp4", "tcp6": - if addr, err := lookupTCPAddr(ip, addrs); err == nil { - dialer.LocalAddr = addr - } else { - return err - } - case "udp", "udp4", "udp6": - if addr, err := lookupUDPAddr(ip, addrs); err == nil { - dialer.LocalAddr = addr - } else { - return err - } - } - - return nil -} - -func fallbackBindToListenConfig(name string) (string, error) { - iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { - return net.InterfaceByName(name) - }) - if err != nil { - return "", err - } - - addrs, err := iface.(*net.Interface).Addrs() - if err != nil { - return "", err - } - - for _, elm := range addrs { - addr, ok := elm.(*net.IPNet) - if !ok || addr.IP.To4() == nil { - continue - } - - return net.JoinHostPort(addr.IP.String(), "0"), nil - } - - return "", ErrAddrNotFound -} diff --git a/component/dialer/bind_darwin.go b/component/dialer/bind_darwin.go index 0ce50541..b3ae9d81 100644 --- a/component/dialer/bind_darwin.go +++ b/component/dialer/bind_darwin.go @@ -3,51 +3,57 @@ package dialer import ( "net" "syscall" + + "golang.org/x/sys/unix" + + "github.com/Dreamacro/clash/component/iface" ) type controlFn = func(network, address string, c syscall.RawConn) error -func bindControl(ifaceIdx int) controlFn { - return func(network, address string, c syscall.RawConn) error { +func bindControl(ifaceIdx int, chain controlFn) controlFn { + return func(network, address string, c syscall.RawConn) (err error) { + defer func() { + if err == nil && chain != nil { + err = chain(network, address, c) + } + }() + ipStr, _, err := net.SplitHostPort(address) if err == nil { ip := net.ParseIP(ipStr) if ip != nil && !ip.IsGlobalUnicast() { - return nil + return } } return c.Control(func(fd uintptr) { switch network { case "tcp4", "udp4": - syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, ifaceIdx) + unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, ifaceIdx) case "tcp6", "udp6": - syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, ifaceIdx) + unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, ifaceIdx) } }) } } -func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { - iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { - return net.InterfaceByName(ifaceName) - }) +func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error { + ifaceObj, err := iface.ResolveInterface(ifaceName) if err != nil { return err } - dialer.Control = bindControl(iface.(*net.Interface).Index) + dialer.Control = bindControl(ifaceObj.Index, dialer.Control) return nil } -func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { - iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { - return net.InterfaceByName(ifaceName) - }) +func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { + ifaceObj, err := iface.ResolveInterface(ifaceName) if err != nil { - return err + return "", err } - lc.Control = bindControl(iface.(*net.Interface).Index) - return nil + lc.Control = bindControl(ifaceObj.Index, lc.Control) + return address, nil } diff --git a/component/dialer/bind_linux.go b/component/dialer/bind_linux.go index 7e9d308d..ae772a71 100644 --- a/component/dialer/bind_linux.go +++ b/component/dialer/bind_linux.go @@ -3,34 +3,42 @@ package dialer import ( "net" "syscall" + + "golang.org/x/sys/unix" ) type controlFn = func(network, address string, c syscall.RawConn) error -func bindControl(ifaceName string) controlFn { - return func(network, address string, c syscall.RawConn) error { +func bindControl(ifaceName string, chain controlFn) controlFn { + return func(network, address string, c syscall.RawConn) (err error) { + defer func() { + if err == nil && chain != nil { + err = chain(network, address, c) + } + }() + ipStr, _, err := net.SplitHostPort(address) if err == nil { ip := net.ParseIP(ipStr) if ip != nil && !ip.IsGlobalUnicast() { - return nil + return } } return c.Control(func(fd uintptr) { - syscall.BindToDevice(int(fd), ifaceName) + unix.BindToDevice(int(fd), ifaceName) }) } } -func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { - dialer.Control = bindControl(ifaceName) +func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error { + dialer.Control = bindControl(ifaceName, dialer.Control) return nil } -func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { - lc.Control = bindControl(ifaceName) +func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { + lc.Control = bindControl(ifaceName, lc.Control) - return nil + return address, nil } diff --git a/component/dialer/bind_others.go b/component/dialer/bind_others.go index e09b5ff8..7f1923aa 100644 --- a/component/dialer/bind_others.go +++ b/component/dialer/bind_others.go @@ -3,12 +3,91 @@ package dialer -import "net" +import ( + "net" + "strconv" + "strings" -func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { - return errPlatformNotSupport + "github.com/Dreamacro/clash/component/iface" +) + +func lookupLocalAddr(ifaceName string, network string, destination net.IP, port int) (net.Addr, error) { + ifaceObj, err := iface.ResolveInterface(ifaceName) + if err != nil { + return nil, err + } + + var addr *net.IPNet + switch network { + case "udp4", "tcp4": + addr, err = ifaceObj.PickIPv4Addr(destination) + case "tcp6", "udp6": + addr, err = ifaceObj.PickIPv6Addr(destination) + default: + if destination != nil { + if destination.To4() != nil { + addr, err = ifaceObj.PickIPv4Addr(destination) + } else { + addr, err = ifaceObj.PickIPv6Addr(destination) + } + } else { + addr, err = ifaceObj.PickIPv4Addr(destination) + } + } + if err != nil { + return nil, err + } + + if strings.HasPrefix(network, "tcp") { + return &net.TCPAddr{ + IP: addr.IP, + Port: port, + }, nil + } else if strings.HasPrefix(network, "udp") { + return &net.UDPAddr{ + IP: addr.IP, + Port: port, + }, nil + } + + return nil, iface.ErrAddrNotFound } -func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { - return errPlatformNotSupport +func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination net.IP) error { + if !destination.IsGlobalUnicast() { + return nil + } + + local := 0 + if dialer.LocalAddr != nil { + _, port, err := net.SplitHostPort(dialer.LocalAddr.String()) + if err == nil { + local, _ = strconv.Atoi(port) + } + } + + addr, err := lookupLocalAddr(ifaceName, network, destination, local) + if err != nil { + return err + } + + dialer.LocalAddr = addr + + return nil +} + +func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) { + _, port, err := net.SplitHostPort(address) + if err != nil { + port = "0" + } + + local, _ := strconv.Atoi(port) + + addr, err := lookupLocalAddr(ifaceName, network, nil, local) + if err != nil { + return "", err + } + + return addr.String(), nil } diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index be26681a..75bbb868 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -8,22 +8,7 @@ import ( "github.com/Dreamacro/clash/component/resolver" ) -func Dialer() (*net.Dialer, error) { - dialer := &net.Dialer{} - if DialerHook != nil { - if err := DialerHook(dialer); err != nil { - return nil, err - } - } - - return dialer, nil -} - -func Dial(network, address string) (net.Conn, error) { - return DialContext(context.Background(), network, address) -} - -func DialContext(ctx context.Context, network, address string) (net.Conn, error) { +func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) { switch network { case "tcp4", "tcp6", "udp4", "udp6": host, port, err := net.SplitHostPort(address) @@ -31,11 +16,6 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error) return nil, err } - dialer, err := Dialer() - if err != nil { - return nil, err - } - var ip net.IP switch network { case "tcp4", "udp4": @@ -43,38 +23,70 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error) default: ip, err = resolver.ResolveIPv6(host) } - if err != nil { return nil, err } - if DialHook != nil { - if err := DialHook(dialer, network, ip); err != nil { - return nil, err - } - } - return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) + return dialContext(ctx, network, ip, port, options) case "tcp", "udp": - return dualStackDialContext(ctx, network, address) + return dualStackDialContext(ctx, network, address, options) default: return nil, errors.New("network invalid") } } -func ListenPacket(network, address string) (net.PacketConn, error) { - cfg := &net.ListenConfig{} - if ListenPacketHook != nil { - var err error - address, err = ListenPacketHook(cfg, address) +func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) { + cfg := &config{} + + if !cfg.skipDefault { + for _, o := range DefaultOptions { + o(cfg) + } + } + + for _, o := range options { + o(cfg) + } + + lc := &net.ListenConfig{} + if cfg.interfaceName != "" { + addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address) if err != nil { return nil, err } + address = addr + } + if cfg.addrReuse { + addrReuseToListenConfig(lc) + } + + return lc.ListenPacket(ctx, network, address) +} + +func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) { + opt := &config{} + + if !opt.skipDefault { + for _, o := range DefaultOptions { + o(opt) + } + } + + for _, o := range options { + o(opt) + } + + dialer := &net.Dialer{} + if opt.interfaceName != "" { + if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil { + return nil, err + } } - return cfg.ListenPacket(context.Background(), network, address) + return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port)) } -func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) { +func dualStackDialContext(ctx context.Context, network, address string, options []Option) (net.Conn, error) { host, port, err := net.SplitHostPort(address) if err != nil { return nil, err @@ -105,12 +117,6 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con } }() - dialer, err := Dialer() - if err != nil { - result.error = err - return - } - var ip net.IP if ipv6 { ip, result.error = resolver.ResolveIPv6(host) @@ -122,12 +128,7 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con } result.resolved = true - if DialHook != nil { - if result.error = DialHook(dialer, network, ip); result.error != nil { - return - } - } - result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) + result.Conn, result.error = dialContext(ctx, network, ip, port, options) } go startRacer(ctx, network+"4", host, false) diff --git a/component/dialer/hook.go b/component/dialer/hook.go deleted file mode 100644 index 356e4b25..00000000 --- a/component/dialer/hook.go +++ /dev/null @@ -1,43 +0,0 @@ -package dialer - -import ( - "errors" - "net" -) - -type DialerHookFunc = func(dialer *net.Dialer) error -type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error -type ListenPacketHookFunc = func(lc *net.ListenConfig, address string) (string, error) - -var ( - DialerHook DialerHookFunc - DialHook DialHookFunc - ListenPacketHook ListenPacketHookFunc -) - -var ( - ErrAddrNotFound = errors.New("addr not found") - ErrNetworkNotSupport = errors.New("network not support") -) - -func ListenPacketWithInterface(name string) ListenPacketHookFunc { - return func(lc *net.ListenConfig, address string) (string, error) { - err := bindIfaceToListenConfig(lc, name) - if err == errPlatformNotSupport { - address, err = fallbackBindToListenConfig(name) - } - - return address, err - } -} - -func DialerWithInterface(name string) DialHookFunc { - return func(dialer *net.Dialer, network string, ip net.IP) error { - err := bindIfaceToDialer(dialer, name) - if err == errPlatformNotSupport { - err = fallbackBindToDialer(dialer, network, ip, name) - } - - return err - } -} diff --git a/component/dialer/options.go b/component/dialer/options.go new file mode 100644 index 00000000..4b5d64c0 --- /dev/null +++ b/component/dialer/options.go @@ -0,0 +1,31 @@ +package dialer + +var ( + DefaultOptions []Option +) + +type config struct { + skipDefault bool + interfaceName string + addrReuse bool +} + +type Option func(opt *config) + +func WithInterface(name string) Option { + return func(opt *config) { + opt.interfaceName = name + } +} + +func WithAddrReuse(reuse bool) Option { + return func(opt *config) { + opt.addrReuse = reuse + } +} + +func WithSkipDefault(skip bool) Option { + return func(opt *config) { + opt.skipDefault = skip + } +} diff --git a/component/dialer/reuse_others.go b/component/dialer/reuse_others.go new file mode 100644 index 00000000..b76213a7 --- /dev/null +++ b/component/dialer/reuse_others.go @@ -0,0 +1,10 @@ +//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows +// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows + +package dialer + +import ( + "net" +) + +func addrReuseToListenConfig(*net.ListenConfig) {} diff --git a/component/dialer/reuse_unix.go b/component/dialer/reuse_unix.go new file mode 100644 index 00000000..d5f43d8f --- /dev/null +++ b/component/dialer/reuse_unix.go @@ -0,0 +1,28 @@ +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris + +package dialer + +import ( + "net" + "syscall" + + "golang.org/x/sys/unix" +) + +func addrReuseToListenConfig(lc *net.ListenConfig) { + chain := lc.Control + + lc.Control = func(network, address string, c syscall.RawConn) (err error) { + defer func() { + if err == nil && chain != nil { + err = chain(network, address, c) + } + }() + + return c.Control(func(fd uintptr) { + unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) + unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + }) + } +} diff --git a/component/dialer/reuse_windows.go b/component/dialer/reuse_windows.go new file mode 100644 index 00000000..77fcf7ab --- /dev/null +++ b/component/dialer/reuse_windows.go @@ -0,0 +1,24 @@ +package dialer + +import ( + "net" + "syscall" + + "golang.org/x/sys/windows" +) + +func addrReuseToListenConfig(lc *net.ListenConfig) { + chain := lc.Control + + lc.Control = func(network, address string, c syscall.RawConn) (err error) { + defer func() { + if err == nil && chain != nil { + err = chain(network, address, c) + } + }() + + return c.Control(func(fd uintptr) { + windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1) + }) + } +} diff --git a/component/iface/iface.go b/component/iface/iface.go new file mode 100644 index 00000000..30c6a6bc --- /dev/null +++ b/component/iface/iface.go @@ -0,0 +1,113 @@ +package iface + +import ( + "errors" + "net" + "time" + + "github.com/Dreamacro/clash/common/singledo" +) + +type Interface struct { + Index int + Name string + Addrs []*net.IPNet + HardwareAddr net.HardwareAddr +} + +var ErrIfaceNotFound = errors.New("interface not found") +var ErrAddrNotFound = errors.New("addr not found") + +var interfaces = singledo.NewSingle(time.Second * 20) + +func ResolveInterface(name string) (*Interface, error) { + value, err, _ := interfaces.Do(func() (interface{}, error) { + ifaces, err := net.Interfaces() + if err != nil { + return nil, err + } + + r := map[string]*Interface{} + + for _, iface := range ifaces { + addrs, err := iface.Addrs() + if err != nil { + continue + } + + ipNets := make([]*net.IPNet, 0, len(addrs)) + for _, addr := range addrs { + ipNet := addr.(*net.IPNet) + if v4 := ipNet.IP.To4(); v4 != nil { + ipNet.IP = v4 + } + + ipNets = append(ipNets, ipNet) + } + + r[iface.Name] = &Interface{ + Index: iface.Index, + Name: iface.Name, + Addrs: ipNets, + HardwareAddr: iface.HardwareAddr, + } + } + + return r, nil + }) + if err != nil { + return nil, err + } + + ifaces := value.(map[string]*Interface) + iface, ok := ifaces[name] + if !ok { + return nil, ErrIfaceNotFound + } + + return iface, nil +} + +func FlushCache() { + interfaces.Reset() +} + +func (iface *Interface) PickIPv4Addr(destination net.IP) (*net.IPNet, error) { + return iface.pickIPAddr(destination, func(addr *net.IPNet) bool { + return addr.IP.To4() != nil + }) +} + +func (iface *Interface) PickIPv6Addr(destination net.IP) (*net.IPNet, error) { + return iface.pickIPAddr(destination, func(addr *net.IPNet) bool { + return addr.IP.To4() == nil + }) +} + +func (iface *Interface) pickIPAddr(destination net.IP, accept func(addr *net.IPNet) bool) (*net.IPNet, error) { + var fallback *net.IPNet + + for _, addr := range iface.Addrs { + if !accept(addr) { + continue + } + + if fallback == nil && !addr.IP.IsLinkLocalUnicast() { + fallback = addr + + if destination == nil { + break + } + } + + if destination != nil && addr.Contains(destination) { + return addr, nil + } + } + + if fallback == nil { + return nil, ErrAddrNotFound + } + + return fallback, nil +} diff --git a/config/config.go b/config/config.go index dc559ecf..82417512 100644 --- a/config/config.go +++ b/config/config.go @@ -488,6 +488,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path} addr = clearURL.String() dnsNetType = "https" // DNS over HTTPS + case "dhcp": + addr = u.Host + dnsNetType = "dhcp" // UDP from DHCP default: return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) } diff --git a/dns/client.go b/dns/client.go index 405b3a98..d386ed4c 100644 --- a/dns/client.go +++ b/dns/client.go @@ -2,6 +2,7 @@ package dns import ( "context" + "crypto/tls" "fmt" "net" "strings" @@ -34,22 +35,16 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err } } - d, err := dialer.Dialer() + network := "udp" + if strings.HasPrefix(c.Client.Net, "tcp") { + network = "tcp" + } + + conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port)) if err != nil { return nil, err } - - if ip != nil && ip.IsGlobalUnicast() && dialer.DialHook != nil { - network := "udp" - if strings.HasPrefix(c.Client.Net, "tcp") { - network = "tcp" - } - if err := dialer.DialHook(d, network, ip); err != nil { - return nil, err - } - } - - c.Client.Dialer = d + defer conn.Close() // miekg/dns ExchangeContext doesn't respond to context cancel. // this is a workaround @@ -59,7 +54,17 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err } ch := make(chan result, 1) go func() { - msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port)) + if strings.HasSuffix(c.Client.Net, "tls") { + conn = tls.Client(conn, c.Client.TLSConfig) + } + + msg, _, err = c.Client.ExchangeWithConn(m, &D.Conn{ + Conn: conn, + UDPSize: c.Client.UDPSize, + TsigSecret: c.Client.TsigSecret, + TsigProvider: c.Client.TsigProvider, + }) + ch <- result{msg, err} }() diff --git a/dns/dhcp.go b/dns/dhcp.go new file mode 100644 index 00000000..94f8a36c --- /dev/null +++ b/dns/dhcp.go @@ -0,0 +1,144 @@ +package dns + +import ( + "bytes" + "context" + "net" + "sync" + "time" + + "github.com/Dreamacro/clash/component/dhcp" + "github.com/Dreamacro/clash/component/iface" + "github.com/Dreamacro/clash/component/resolver" + + D "github.com/miekg/dns" +) + +const ( + IfaceTTL = time.Second * 20 + DHCPTTL = time.Hour + DHCPTimeout = time.Minute +) + +type dhcpClient struct { + ifaceName string + + lock sync.Mutex + ifaceInvalidate time.Time + dnsInvalidate time.Time + + ifaceAddr *net.IPNet + done chan struct{} + resolver *Resolver + err error +} + +func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { + ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) + defer cancel() + + return d.ExchangeContext(ctx, m) +} + +func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { + res, err := d.resolve(ctx) + if err != nil { + return nil, err + } + + return res.ExchangeContext(ctx, m) +} + +func (d *dhcpClient) resolve(ctx context.Context) (*Resolver, error) { + d.lock.Lock() + + invalidated, err := d.invalidate() + if err != nil { + d.err = err + } else if invalidated { + done := make(chan struct{}) + + d.done = done + + go func() { + ctx, cancel := context.WithTimeout(context.Background(), DHCPTimeout) + defer cancel() + + var res *Resolver + dns, err := dhcp.ResolveDNSFromDHCP(ctx, d.ifaceName) + if err == nil { + nameserver := make([]NameServer, 0, len(dns)) + for _, d := range dns { + nameserver = append(nameserver, NameServer{Addr: net.JoinHostPort(d.String(), "53")}) + } + + res = NewResolver(Config{ + Main: nameserver, + }) + } + + d.lock.Lock() + defer d.lock.Unlock() + + close(done) + + d.done = nil + d.resolver = res + d.err = err + }() + } + + d.lock.Unlock() + + for { + d.lock.Lock() + + res, err, done := d.resolver, d.err, d.done + + d.lock.Unlock() + + // initializing + if res == nil && err == nil { + select { + case <-done: + continue + case <-ctx.Done(): + return nil, ctx.Err() + } + } + + // dirty return + return res, err + } +} + +func (d *dhcpClient) invalidate() (bool, error) { + if time.Now().Before(d.ifaceInvalidate) { + return false, nil + } + + d.ifaceInvalidate = time.Now().Add(IfaceTTL) + + ifaceObj, err := iface.ResolveInterface(d.ifaceName) + if err != nil { + return false, err + } + + addr, err := ifaceObj.PickIPv4Addr(nil) + if err != nil { + return false, err + } + + if time.Now().Before(d.dnsInvalidate) && d.ifaceAddr.IP.Equal(addr.IP) && bytes.Equal(d.ifaceAddr.Mask, addr.Mask) { + return false, nil + } + + d.dnsInvalidate = time.Now().Add(DHCPTTL) + d.ifaceAddr = addr + + return d.done == nil, nil +} + +func newDHCPClient(ifaceName string) *dhcpClient { + return &dhcpClient{ifaceName: ifaceName} +} diff --git a/dns/resolver.go b/dns/resolver.go index 56b5e326..914f1418 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -87,6 +87,11 @@ func (r *Resolver) shouldIPFallback(ip net.IP) bool { // Exchange a batch of dns request, and it use cache func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { + return r.ExchangeContext(context.Background(), m) +} + +// ExchangeContext a batch of dns request with context.Context, and it use cache +func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { if len(m.Question) == 0 { return nil, errors.New("should have one question at least") } @@ -98,17 +103,17 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { msg = cache.(*D.Msg).Copy() if expireTime.Before(now) { setMsgTTL(msg, uint32(1)) // Continue fetch - go r.exchangeWithoutCache(m) + go r.exchangeWithoutCache(ctx, m) } else { setMsgTTL(msg, uint32(time.Until(expireTime).Seconds())) } return } - return r.exchangeWithoutCache(m) + return r.exchangeWithoutCache(ctx, m) } // ExchangeWithoutCache a batch of dns request, and it do NOT GET from cache -func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { +func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { q := m.Question[0] ret, err, shared := r.group.Do(q.String(), func() (result interface{}, err error) { @@ -124,13 +129,13 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { isIPReq := isIPRequest(q) if isIPReq { - return r.ipExchange(m) + return r.ipExchange(ctx, m) } if matched := r.matchPolicy(m); len(matched) != 0 { - return r.batchExchange(matched, m) + return r.batchExchange(ctx, matched, m) } - return r.batchExchange(r.main, m) + return r.batchExchange(ctx, r.main, m) }) if err == nil { @@ -143,8 +148,8 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { return } -func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { - fast, ctx := picker.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) +func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { + fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout) for _, client := range clients { r := client fast.Go(func() (interface{}, error) { @@ -209,21 +214,21 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool { return false } -func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) { +func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { if matched := r.matchPolicy(m); len(matched) != 0 { - res := <-r.asyncExchange(matched, m) + res := <-r.asyncExchange(ctx, matched, m) return res.Msg, res.Error } onlyFallback := r.shouldOnlyQueryFallback(m) if onlyFallback { - res := <-r.asyncExchange(r.fallback, m) + res := <-r.asyncExchange(ctx, r.fallback, m) return res.Msg, res.Error } - msgCh := r.asyncExchange(r.main, m) + msgCh := r.asyncExchange(ctx, r.main, m) if r.fallback == nil { // directly return if no fallback servers are available res := <-msgCh @@ -231,7 +236,7 @@ func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) { return } - fallbackMsg := r.asyncExchange(r.fallback, m) + fallbackMsg := r.asyncExchange(ctx, r.fallback, m) res := <-msgCh if res.Error == nil { if ips := msgToIP(res.Msg); len(ips) != 0 { @@ -287,10 +292,10 @@ func (r *Resolver) msgToDomain(msg *D.Msg) string { return "" } -func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result { +func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result { ch := make(chan *result, 1) go func() { - res, err := r.batchExchange(client, msg) + res, err := r.batchExchange(ctx, client, msg) ch <- &result{Msg: res, Error: err} }() return ch diff --git a/dns/util.go b/dns/util.go index e56aaeb5..0c3f42e5 100644 --- a/dns/util.go +++ b/dns/util.go @@ -117,9 +117,13 @@ func isIPRequest(q D.Question) bool { func transform(servers []NameServer, resolver *Resolver) []dnsClient { ret := []dnsClient{} for _, s := range servers { - if s.Net == "https" { + switch s.Net { + case "https": ret = append(ret, newDoHClient(s.Addr, resolver)) continue + case "dhcp": + ret = append(ret, newDHCPClient(s.Addr)) + continue } host, port, _ := net.SplitHostPort(s.Addr) diff --git a/go.mod b/go.mod index 34915dd7..d288f5fe 100644 --- a/go.mod +++ b/go.mod @@ -4,20 +4,21 @@ go 1.17 require ( github.com/Dreamacro/go-shadowsocks2 v0.1.7 - github.com/go-chi/chi/v5 v5.0.3 + github.com/go-chi/chi/v5 v5.0.4 github.com/go-chi/cors v1.2.0 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v4.0.0+incompatible github.com/gorilla/websocket v1.4.2 + github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac github.com/miekg/dns v1.1.43 github.com/oschwald/geoip2-golang v1.5.0 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 go.uber.org/atomic v1.9.0 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 - golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d + golang.org/x/net v0.0.0-20210825183410-e898025ed96a golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 + golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e gopkg.in/yaml.v2 v2.4.0 ) @@ -25,6 +26,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/oschwald/maxminddb-golang v1.8.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect golang.org/x/text v0.3.6 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index 2c433ac0..f5346e53 100644 --- a/go.sum +++ b/go.sum @@ -3,16 +3,38 @@ github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4= -github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= +github.com/go-chi/chi/v5 v5.0.4 h1:5e494iHzsYBiyXQAHHuI4tyJS9M3V84OuX3ufIIGHFo= +github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE= github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= +github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac h1:IO6EfdRnPhxgKOsk9DbewdtQZHKZKnGlW7QCUttvNys= +github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= +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/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/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/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw= @@ -23,35 +45,66 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA= +github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +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-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/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-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo= -golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/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.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 121e1d01..5c002b64 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -10,6 +10,7 @@ import ( "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/profile" "github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/resolver" @@ -171,13 +172,13 @@ func updateGeneral(general *config.General, force bool) { resolver.DisableIPv6 = !general.IPv6 if general.Interface != "" { - dialer.DialHook = dialer.DialerWithInterface(general.Interface) - dialer.ListenPacketHook = dialer.ListenPacketWithInterface(general.Interface) + dialer.DefaultOptions = []dialer.Option{dialer.WithInterface(general.Interface)} } else { - dialer.DialHook = nil - dialer.ListenPacketHook = nil + dialer.DefaultOptions = nil } + iface.FlushCache() + if !force { return }