diff --git a/component/dialer/bind_darwin.go b/component/dialer/bind_darwin.go index 91d74987..8e88b461 100644 --- a/component/dialer/bind_darwin.go +++ b/component/dialer/bind_darwin.go @@ -62,3 +62,7 @@ func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address lc.Control = bindControl(ifaceObj.Index, lc.Control) return address, nil } + +func ParseNetwork(network string, addr netip.Addr) string { + return network +} diff --git a/component/dialer/bind_linux.go b/component/dialer/bind_linux.go index ca88cb58..57a2e0c1 100644 --- a/component/dialer/bind_linux.go +++ b/component/dialer/bind_linux.go @@ -47,3 +47,7 @@ func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address return address, nil } + +func ParseNetwork(network string, addr netip.Addr) string { + return network +} diff --git a/component/dialer/bind_others.go b/component/dialer/bind_others.go index f732b1ce..5cb2fd62 100644 --- a/component/dialer/bind_others.go +++ b/component/dialer/bind_others.go @@ -1,4 +1,4 @@ -//go:build !linux && !darwin +//go:build !linux && !darwin && !windows package dialer @@ -91,3 +91,13 @@ func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, add return addr.String(), nil } + +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 +} diff --git a/component/dialer/bind_windows.go b/component/dialer/bind_windows.go new file mode 100644 index 00000000..87b39bc2 --- /dev/null +++ b/component/dialer/bind_windows.go @@ -0,0 +1,88 @@ +package dialer + +import ( + "encoding/binary" + "net" + "net/netip" + "syscall" + "unsafe" + + "github.com/Dreamacro/clash/component/iface" +) + +const ( + IP_UNICAST_IF = 31 + IPV6_UNICAST_IF = 31 +) + +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)) +} + +func bind6(handle syscall.Handle, ifaceIdx int) error { + return syscall.SetsockoptInt(handle, syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, ifaceIdx) +} + +type controlFn = 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) + } + }() + + addrPort, err := netip.ParseAddrPort(address) + if err == nil && !addrPort.Addr().IsGlobalUnicast() { + return + } + + var innerErr error + err = c.Control(func(fd uintptr) { + handle := syscall.Handle(fd) + switch network { + case "tcp6", "udp6": + innerErr = bind6(handle, ifaceIdx) + _ = bind4(handle, ifaceIdx) + default: + innerErr = bind4(handle, ifaceIdx) + // try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6 + _ = bind6(handle, ifaceIdx) + } + }) + + if innerErr != nil { + err = innerErr + } + + return + } +} + +func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error { + ifaceObj, err := iface.ResolveInterface(ifaceName) + if err != nil { + return err + } + + dialer.Control = bindControl(ifaceObj.Index, dialer.Control) + return nil +} + +func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { + ifaceObj, err := iface.ResolveInterface(ifaceName) + if err != nil { + return "", err + } + + lc.Control = bindControl(ifaceObj.Index, lc.Control) + return address, nil +} + +func ParseNetwork(network string, addr netip.Addr) string { + return network +} diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index d7c0f3db..e31936e9 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -6,7 +6,6 @@ import ( "fmt" "net" "net/netip" - "runtime" "strings" "sync" @@ -25,17 +24,6 @@ var ( ErrorDisableIPv6 = errors.New("IPv6 is disabled, dialer cancel") ) -func ParseNetwork(network string, addr netip.Addr) string { - if runtime.GOOS == "windows" { // fix bindIfaceToListenConfig() in windows force bind to an ipv4 address - if !strings.HasSuffix(network, "4") && - !strings.HasSuffix(network, "6") && - addr.Unmap().Is6() { - network += "6" - } - } - return network -} - func applyOptions(options ...Option) *option { opt := &option{ interfaceName: DefaultInterface.Load(),