github.com/igoogolx/clash@v1.19.8/component/dialer/bind_windows.go (about)

     1  package dialer
     2  
     3  import (
     4  	"encoding/binary"
     5  	"net"
     6  	"strings"
     7  	"syscall"
     8  	"unsafe"
     9  
    10  	"github.com/igoogolx/clash/component/iface"
    11  
    12  	"golang.org/x/sys/windows"
    13  )
    14  
    15  const (
    16  	IP_UNICAST_IF   = 31
    17  	IPV6_UNICAST_IF = 31
    18  )
    19  
    20  type controlFn = func(network, address string, c syscall.RawConn) error
    21  
    22  func bindControl(ifaceIdx int, chain controlFn) controlFn {
    23  	return func(network, address string, c syscall.RawConn) (err error) {
    24  		defer func() {
    25  			if err == nil && chain != nil {
    26  				err = chain(network, address, c)
    27  			}
    28  		}()
    29  
    30  		ipStr, _, err := net.SplitHostPort(address)
    31  		if err == nil {
    32  			ip := net.ParseIP(ipStr)
    33  			if ip != nil && !ip.IsGlobalUnicast() {
    34  				return
    35  			}
    36  		}
    37  
    38  		var innerErr error
    39  		err = c.Control(func(fd uintptr) {
    40  			if ipStr == "" && strings.HasPrefix(network, "udp") {
    41  				// When listening udp ":0", we should bind socket to interface4 and interface6 at the same time
    42  				// and ignore the error of bind6
    43  				_ = bindSocketToInterface6(windows.Handle(fd), ifaceIdx)
    44  				innerErr = bindSocketToInterface4(windows.Handle(fd), ifaceIdx)
    45  				return
    46  			}
    47  			switch network {
    48  			case "tcp4", "udp4":
    49  				innerErr = bindSocketToInterface4(windows.Handle(fd), ifaceIdx)
    50  			case "tcp6", "udp6":
    51  				innerErr = bindSocketToInterface6(windows.Handle(fd), ifaceIdx)
    52  			}
    53  		})
    54  
    55  		if innerErr != nil {
    56  			err = innerErr
    57  		}
    58  
    59  		return
    60  	}
    61  }
    62  
    63  func bindSocketToInterface4(handle windows.Handle, ifaceIdx int) error {
    64  	// MSDN says for IPv4 this needs to be in net byte order, so that it's like an IP address with leading zeros.
    65  	// Ref: https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
    66  	var bytes [4]byte
    67  	binary.BigEndian.PutUint32(bytes[:], uint32(ifaceIdx))
    68  	index := *(*uint32)(unsafe.Pointer(&bytes[0]))
    69  	err := windows.SetsockoptInt(handle, windows.IPPROTO_IP, IP_UNICAST_IF, int(index))
    70  	if err != nil {
    71  		return err
    72  	}
    73  	return nil
    74  }
    75  
    76  func bindSocketToInterface6(handle windows.Handle, ifaceIdx int) error {
    77  	return windows.SetsockoptInt(handle, windows.IPPROTO_IPV6, IPV6_UNICAST_IF, ifaceIdx)
    78  }
    79  
    80  func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error {
    81  	ifaceObj, err := iface.ResolveInterface(ifaceName)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	dialer.Control = bindControl(ifaceObj.Index, dialer.Control)
    87  	return nil
    88  }
    89  
    90  func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
    91  	ifaceObj, err := iface.ResolveInterface(ifaceName)
    92  	if err != nil {
    93  		return "", err
    94  	}
    95  
    96  	lc.Control = bindControl(ifaceObj.Index, lc.Control)
    97  	return address, nil
    98  }