diff --git a/component/dhcp/conn.go b/component/dhcp/conn.go index 90a9e25b..5b71d3cd 100644 --- a/component/dhcp/conn.go +++ b/component/dhcp/conn.go @@ -14,5 +14,15 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er listenAddr = "255.255.255.255:68" } - return dialer.ListenPacket(ctx, "udp4", listenAddr, dialer.WithInterface(ifaceName), dialer.WithAddrReuse(true)) + options := []dialer.Option{ + dialer.WithInterface(ifaceName), + dialer.WithAddrReuse(true), + } + + // fallback bind on windows, because syscall bind can not receive broadcast + if runtime.GOOS == "windows" { + options = append(options, dialer.WithFallbackBind(true)) + } + + return dialer.ListenPacket(ctx, "udp4", listenAddr, options...) } diff --git a/component/dialer/bind.go b/component/dialer/bind.go index edfc79c7..4accabb3 100644 --- a/component/dialer/bind.go +++ b/component/dialer/bind.go @@ -3,6 +3,7 @@ package dialer import ( "net" "net/netip" + "strconv" "strings" "github.com/Dreamacro/clash/component/iface" @@ -49,3 +50,52 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination return nil, iface.ErrAddrNotFound } + +func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error { + if !destination.IsGlobalUnicast() { + return nil + } + + local := uint64(0) + if dialer.LocalAddr != nil { + _, port, err := net.SplitHostPort(dialer.LocalAddr.String()) + if err == nil { + local, _ = strconv.ParseUint(port, 10, 16) + } + } + + addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local)) + if err != nil { + return err + } + + dialer.LocalAddr = addr + + return nil +} + +func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) { + _, port, err := net.SplitHostPort(address) + if err != nil { + port = "0" + } + + local, _ := strconv.ParseUint(port, 10, 16) + + addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local)) + if err != nil { + return "", err + } + + return addr.String(), nil +} + +func fallbackParseNetwork(network string, addr netip.Addr) string { + // fix fallbackBindIfaceToListenConfig() force bind to an ipv4 address + if !strings.HasSuffix(network, "4") && + !strings.HasSuffix(network, "6") && + addr.Unmap().Is6() { + network += "6" + } + return network +} diff --git a/component/dialer/bind_others.go b/component/dialer/bind_others.go index 5fd02a66..44181610 100644 --- a/component/dialer/bind_others.go +++ b/component/dialer/bind_others.go @@ -5,55 +5,16 @@ package dialer import ( "net" "net/netip" - "strconv" - "strings" ) func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error { - if !destination.IsGlobalUnicast() { - return nil - } - - local := uint64(0) - if dialer.LocalAddr != nil { - _, port, err := net.SplitHostPort(dialer.LocalAddr.String()) - if err == nil { - local, _ = strconv.ParseUint(port, 10, 16) - } - } - - addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local)) - if err != nil { - return err - } - - dialer.LocalAddr = addr - - return nil + return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination) } -func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) { - _, port, err := net.SplitHostPort(address) - if err != nil { - port = "0" - } - - local, _ := strconv.ParseUint(port, 10, 16) - - addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local)) - if err != nil { - return "", err - } - - return addr.String(), nil +func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) { + return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address) } func ParseNetwork(network string, addr netip.Addr) string { - // fix bindIfaceToListenConfig() force bind to an ipv4 address - if !strings.HasSuffix(network, "4") && - !strings.HasSuffix(network, "6") && - addr.Unmap().Is6() { - network += "6" - } - return network + return fallbackParseNetwork(network, addr) } diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index 0cfa1b6c..3cb8bba9 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -74,7 +74,11 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio lc := &net.ListenConfig{} if cfg.interfaceName != "" { - addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address) + bind := bindIfaceToListenConfig + if cfg.fallbackBind { + bind = fallbackBindIfaceToListenConfig + } + addr, err := bind(cfg.interfaceName, lc, network, address) if err != nil { return nil, err } @@ -125,7 +129,11 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po dialer := netDialer.(*net.Dialer) if opt.interfaceName != "" { - if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil { + bind := bindIfaceToDialer + if opt.fallbackBind { + bind = fallbackBindIfaceToDialer + } + if err := bind(opt.interfaceName, dialer, network, destination); err != nil { return nil, err } } diff --git a/component/dialer/options.go b/component/dialer/options.go index 30771e71..781d7164 100644 --- a/component/dialer/options.go +++ b/component/dialer/options.go @@ -20,6 +20,7 @@ type NetDialer interface { type option struct { interfaceName string + fallbackBind bool addrReuse bool routingMark int network int @@ -38,6 +39,12 @@ func WithInterface(name string) Option { } } +func WithFallbackBind(fallback bool) Option { + return func(opt *option) { + opt.fallbackBind = fallback + } +} + func WithAddrReuse(reuse bool) Option { return func(opt *option) { opt.addrReuse = reuse diff --git a/dns/util.go b/dns/util.go index 668a3a2e..34f7aa94 100644 --- a/dns/util.go +++ b/dns/util.go @@ -280,7 +280,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st DstPort: uint16(uintPort), } if proxyAdapter == nil { - return dialer.NewDialer(opts...).ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort)) + return dialer.NewDialer(opts...).ListenPacket(ctx, network, "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort)) } if !proxyAdapter.SupportUDP() {