github.com/kelleygo/clashcore@v1.0.2/component/process/process_windows.go (about)

     1  package process
     2  
     3  import (
     4  	"fmt"
     5  	"net/netip"
     6  	"sync"
     7  	"syscall"
     8  	"unsafe"
     9  
    10  	"github.com/kelleygo/clashcore/common/nnip"
    11  	"github.com/kelleygo/clashcore/log"
    12  
    13  	"golang.org/x/sys/windows"
    14  )
    15  
    16  const (
    17  	tcpTableFunc      = "GetExtendedTcpTable"
    18  	tcpTablePidConn   = 4
    19  	udpTableFunc      = "GetExtendedUdpTable"
    20  	udpTablePid       = 1
    21  	queryProcNameFunc = "QueryFullProcessImageNameW"
    22  )
    23  
    24  var (
    25  	getExTCPTable uintptr
    26  	getExUDPTable uintptr
    27  	queryProcName uintptr
    28  
    29  	once sync.Once
    30  )
    31  
    32  func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) {
    33  	return 0, 0, ErrPlatformNotSupport
    34  }
    35  
    36  func initWin32API() error {
    37  	h, err := windows.LoadLibrary("iphlpapi.dll")
    38  	if err != nil {
    39  		return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error())
    40  	}
    41  
    42  	getExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)
    43  	if err != nil {
    44  		return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error())
    45  	}
    46  
    47  	getExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)
    48  	if err != nil {
    49  		return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error())
    50  	}
    51  
    52  	h, err = windows.LoadLibrary("kernel32.dll")
    53  	if err != nil {
    54  		return fmt.Errorf("LoadLibrary kernel32.dll failed: %s", err.Error())
    55  	}
    56  
    57  	queryProcName, err = windows.GetProcAddress(h, queryProcNameFunc)
    58  	if err != nil {
    59  		return fmt.Errorf("GetProcAddress of %s failed: %s", queryProcNameFunc, err.Error())
    60  	}
    61  
    62  	return nil
    63  }
    64  
    65  func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) {
    66  	once.Do(func() {
    67  		err := initWin32API()
    68  		if err != nil {
    69  			log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
    70  			log.Warnln("All PROCESS-NAMES rules will be skipped")
    71  			return
    72  		}
    73  	})
    74  	family := windows.AF_INET
    75  	if ip.Is6() {
    76  		family = windows.AF_INET6
    77  	}
    78  
    79  	var class int
    80  	var fn uintptr
    81  	switch network {
    82  	case TCP:
    83  		fn = getExTCPTable
    84  		class = tcpTablePidConn
    85  	case UDP:
    86  		fn = getExUDPTable
    87  		class = udpTablePid
    88  	default:
    89  		return 0, "", ErrInvalidNetwork
    90  	}
    91  
    92  	buf, err := getTransportTable(fn, family, class)
    93  	if err != nil {
    94  		return 0, "", err
    95  	}
    96  
    97  	s := newSearcher(family == windows.AF_INET, network == TCP)
    98  
    99  	pid, err := s.Search(buf, ip, uint16(srcPort))
   100  	if err != nil {
   101  		return 0, "", err
   102  	}
   103  	pp, err := getExecPathFromPID(pid)
   104  	return 0, pp, err
   105  }
   106  
   107  type searcher struct {
   108  	itemSize int
   109  	port     int
   110  	ip       int
   111  	ipSize   int
   112  	pid      int
   113  	tcpState int
   114  }
   115  
   116  func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) {
   117  	n := int(readNativeUint32(b[:4]))
   118  	itemSize := s.itemSize
   119  	for i := 0; i < n; i++ {
   120  		row := b[4+itemSize*i : 4+itemSize*(i+1)]
   121  
   122  		if s.tcpState >= 0 {
   123  			tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4])
   124  			// MIB_TCP_STATE_ESTAB, only check established connections for TCP
   125  			if tcpState != 5 {
   126  				continue
   127  			}
   128  		}
   129  
   130  		// according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian.
   131  		// this field can be illustrated as follows depends on different machine endianess:
   132  		//     little endian: [ MSB LSB  0   0  ]   interpret as native uint32 is ((LSB<<8)|MSB)
   133  		//       big  endian: [  0   0  MSB LSB ]   interpret as native uint32 is ((MSB<<8)|LSB)
   134  		// so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32
   135  		srcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4])))
   136  		if srcPort != port {
   137  			continue
   138  		}
   139  
   140  		srcIP := nnip.IpToAddr(row[s.ip : s.ip+s.ipSize])
   141  		// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
   142  		if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
   143  			continue
   144  		}
   145  
   146  		pid := readNativeUint32(row[s.pid : s.pid+4])
   147  		return pid, nil
   148  	}
   149  	return 0, ErrNotFound
   150  }
   151  
   152  func newSearcher(isV4, isTCP bool) *searcher {
   153  	var itemSize, port, ip, ipSize, pid int
   154  	tcpState := -1
   155  	switch {
   156  	case isV4 && isTCP:
   157  		// struct MIB_TCPROW_OWNER_PID
   158  		itemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0
   159  	case isV4 && !isTCP:
   160  		// struct MIB_UDPROW_OWNER_PID
   161  		itemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8
   162  	case !isV4 && isTCP:
   163  		// struct MIB_TCP6ROW_OWNER_PID
   164  		itemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48
   165  	case !isV4 && !isTCP:
   166  		// struct MIB_UDP6ROW_OWNER_PID
   167  		itemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24
   168  	}
   169  
   170  	return &searcher{
   171  		itemSize: itemSize,
   172  		port:     port,
   173  		ip:       ip,
   174  		ipSize:   ipSize,
   175  		pid:      pid,
   176  		tcpState: tcpState,
   177  	}
   178  }
   179  
   180  func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
   181  	for size, buf := uint32(8), make([]byte, 8); ; {
   182  		ptr := unsafe.Pointer(&buf[0])
   183  		err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
   184  
   185  		switch err {
   186  		case 0:
   187  			return buf, nil
   188  		case uintptr(syscall.ERROR_INSUFFICIENT_BUFFER):
   189  			buf = make([]byte, size)
   190  		default:
   191  			return nil, fmt.Errorf("syscall error: %d", err)
   192  		}
   193  	}
   194  }
   195  
   196  func readNativeUint32(b []byte) uint32 {
   197  	return *(*uint32)(unsafe.Pointer(&b[0]))
   198  }
   199  
   200  func getExecPathFromPID(pid uint32) (string, error) {
   201  	// kernel process starts with a colon in order to distinguish with normal processes
   202  	switch pid {
   203  	case 0:
   204  		// reserved pid for system idle process
   205  		return ":System Idle Process", nil
   206  	case 4:
   207  		// reserved pid for windows kernel image
   208  		return ":System", nil
   209  	}
   210  	h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
   211  	if err != nil {
   212  		return "", err
   213  	}
   214  	defer windows.CloseHandle(h)
   215  
   216  	buf := make([]uint16, syscall.MAX_LONG_PATH)
   217  	size := uint32(len(buf))
   218  	r1, _, err := syscall.SyscallN(
   219  		queryProcName,
   220  		uintptr(h),
   221  		uintptr(0),
   222  		uintptr(unsafe.Pointer(&buf[0])),
   223  		uintptr(unsafe.Pointer(&size)),
   224  	)
   225  	if r1 == 0 {
   226  		return "", err
   227  	}
   228  	return syscall.UTF16ToString(buf[:size]), nil
   229  }