github.com/igoogolx/clash@v1.19.8/component/process/process_windows.go (about)

     1  package process
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/netip"
     7  	"unsafe"
     8  
     9  	"github.com/igoogolx/clash/common/pool"
    10  
    11  	"golang.org/x/sys/windows"
    12  )
    13  
    14  var (
    15  	modIphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
    16  
    17  	procGetExtendedTcpTable = modIphlpapi.NewProc("GetExtendedTcpTable")
    18  	procGetExtendedUdpTable = modIphlpapi.NewProc("GetExtendedUdpTable")
    19  )
    20  
    21  func findProcessPath(network string, from netip.AddrPort, to netip.AddrPort) (string, error) {
    22  	family := uint32(windows.AF_INET)
    23  	if from.Addr().Is6() {
    24  		family = windows.AF_INET6
    25  	}
    26  
    27  	var protocol uint32
    28  	switch network {
    29  	case TCP:
    30  		protocol = windows.IPPROTO_TCP
    31  	case UDP:
    32  		protocol = windows.IPPROTO_UDP
    33  	default:
    34  		return "", ErrInvalidNetwork
    35  	}
    36  
    37  	pid, err := findPidByConnectionEndpoint(family, protocol, from, to)
    38  	if err != nil {
    39  		return "", err
    40  	}
    41  
    42  	return getExecPathFromPID(pid)
    43  }
    44  
    45  func findPidByConnectionEndpoint(family uint32, protocol uint32, from netip.AddrPort, to netip.AddrPort) (uint32, error) {
    46  	buf := pool.Get(0)
    47  	defer pool.Put(buf)
    48  
    49  	bufSize := uint32(len(buf))
    50  
    51  loop:
    52  	for {
    53  		var ret uintptr
    54  
    55  		switch protocol {
    56  		case windows.IPPROTO_TCP:
    57  			ret, _, _ = procGetExtendedTcpTable.Call(
    58  				uintptr(unsafe.Pointer(unsafe.SliceData(buf))),
    59  				uintptr(unsafe.Pointer(&bufSize)),
    60  				0,
    61  				uintptr(family),
    62  				4, // TCP_TABLE_OWNER_PID_CONNECTIONS
    63  				0,
    64  			)
    65  		case windows.IPPROTO_UDP:
    66  			ret, _, _ = procGetExtendedUdpTable.Call(
    67  				uintptr(unsafe.Pointer(unsafe.SliceData(buf))),
    68  				uintptr(unsafe.Pointer(&bufSize)),
    69  				0,
    70  				uintptr(family),
    71  				1, // UDP_TABLE_OWNER_PID
    72  				0,
    73  			)
    74  		default:
    75  			return 0, errors.New("unsupported network")
    76  		}
    77  
    78  		switch ret {
    79  		case 0:
    80  			buf = buf[:bufSize]
    81  
    82  			break loop
    83  		case uintptr(windows.ERROR_INSUFFICIENT_BUFFER):
    84  			pool.Put(buf)
    85  			buf = pool.Get(int(bufSize))
    86  
    87  			continue loop
    88  		default:
    89  			return 0, fmt.Errorf("syscall error: %d", ret)
    90  		}
    91  	}
    92  
    93  	if len(buf) < int(unsafe.Sizeof(uint32(0))) {
    94  		return 0, fmt.Errorf("invalid table size: %d", len(buf))
    95  	}
    96  
    97  	entriesSize := *(*uint32)(unsafe.Pointer(&buf[0]))
    98  
    99  	switch protocol {
   100  	case windows.IPPROTO_TCP:
   101  		if family == windows.AF_INET {
   102  			type MibTcpRowOwnerPid struct {
   103  				State      uint32
   104  				LocalAddr  [4]byte
   105  				LocalPort  uint32
   106  				RemoteAddr [4]byte
   107  				RemotePort uint32
   108  				OwningPid  uint32
   109  			}
   110  
   111  			if uint32(len(buf))-4 < entriesSize*uint32(unsafe.Sizeof(MibTcpRowOwnerPid{})) {
   112  				return 0, fmt.Errorf("invalid tables size: %d", len(buf))
   113  			}
   114  
   115  			entries := unsafe.Slice((*MibTcpRowOwnerPid)(unsafe.Pointer(&buf[4])), entriesSize)
   116  			for _, entry := range entries {
   117  				localAddr := netip.AddrFrom4(entry.LocalAddr)
   118  				localPort := windows.Ntohs(uint16(entry.LocalPort))
   119  				remoteAddr := netip.AddrFrom4(entry.RemoteAddr)
   120  				remotePort := windows.Ntohs(uint16(entry.RemotePort))
   121  
   122  				if localAddr == from.Addr() && remoteAddr == to.Addr() && localPort == from.Port() && remotePort == to.Port() {
   123  					return entry.OwningPid, nil
   124  				}
   125  			}
   126  		} else {
   127  			type MibTcp6RowOwnerPid struct {
   128  				LocalAddr     [16]byte
   129  				LocalScopeID  uint32
   130  				LocalPort     uint32
   131  				RemoteAddr    [16]byte
   132  				RemoteScopeID uint32
   133  				RemotePort    uint32
   134  				State         uint32
   135  				OwningPid     uint32
   136  			}
   137  
   138  			if uint32(len(buf))-4 < entriesSize*uint32(unsafe.Sizeof(MibTcp6RowOwnerPid{})) {
   139  				return 0, fmt.Errorf("invalid tables size: %d", len(buf))
   140  			}
   141  
   142  			entries := unsafe.Slice((*MibTcp6RowOwnerPid)(unsafe.Pointer(&buf[4])), entriesSize)
   143  			for _, entry := range entries {
   144  				localAddr := netip.AddrFrom16(entry.LocalAddr)
   145  				localPort := windows.Ntohs(uint16(entry.LocalPort))
   146  				remoteAddr := netip.AddrFrom16(entry.RemoteAddr)
   147  				remotePort := windows.Ntohs(uint16(entry.RemotePort))
   148  
   149  				if localAddr == from.Addr() && remoteAddr == to.Addr() && localPort == from.Port() && remotePort == to.Port() {
   150  					return entry.OwningPid, nil
   151  				}
   152  			}
   153  		}
   154  	case windows.IPPROTO_UDP:
   155  		if family == windows.AF_INET {
   156  			type MibUdpRowOwnerPid struct {
   157  				LocalAddr [4]byte
   158  				LocalPort uint32
   159  				OwningPid uint32
   160  			}
   161  
   162  			if uint32(len(buf))-4 < entriesSize*uint32(unsafe.Sizeof(MibUdpRowOwnerPid{})) {
   163  				return 0, fmt.Errorf("invalid tables size: %d", len(buf))
   164  			}
   165  
   166  			entries := unsafe.Slice((*MibUdpRowOwnerPid)(unsafe.Pointer(&buf[4])), entriesSize)
   167  			for _, entry := range entries {
   168  				localAddr := netip.AddrFrom4(entry.LocalAddr)
   169  				localPort := windows.Ntohs(uint16(entry.LocalPort))
   170  
   171  				if (localAddr == from.Addr() || localAddr.IsUnspecified()) && localPort == from.Port() {
   172  					return entry.OwningPid, nil
   173  				}
   174  			}
   175  		} else {
   176  			type MibUdp6RowOwnerPid struct {
   177  				LocalAddr    [16]byte
   178  				LocalScopeId uint32
   179  				LocalPort    uint32
   180  				OwningPid    uint32
   181  			}
   182  
   183  			if uint32(len(buf))-4 < entriesSize*uint32(unsafe.Sizeof(MibUdp6RowOwnerPid{})) {
   184  				return 0, fmt.Errorf("invalid tables size: %d", len(buf))
   185  			}
   186  
   187  			entries := unsafe.Slice((*MibUdp6RowOwnerPid)(unsafe.Pointer(&buf[4])), entriesSize)
   188  			for _, entry := range entries {
   189  				localAddr := netip.AddrFrom16(entry.LocalAddr)
   190  				localPort := windows.Ntohs(uint16(entry.LocalPort))
   191  
   192  				if (localAddr == from.Addr() || localAddr.IsUnspecified()) && localPort == from.Port() {
   193  					return entry.OwningPid, nil
   194  				}
   195  			}
   196  		}
   197  	default:
   198  		return 0, ErrInvalidNetwork
   199  	}
   200  
   201  	return 0, ErrNotFound
   202  }
   203  
   204  func getExecPathFromPID(pid uint32) (string, error) {
   205  	// kernel process starts with a colon in order to distinguish with normal processes
   206  	switch pid {
   207  	case 0:
   208  		// reserved pid for system idle process
   209  		return ":System Idle Process", nil
   210  	case 4:
   211  		// reserved pid for windows kernel image
   212  		return ":System", nil
   213  	}
   214  	h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
   215  	if err != nil {
   216  		return "", err
   217  	}
   218  	defer windows.CloseHandle(h)
   219  
   220  	buf := make([]uint16, windows.MAX_LONG_PATH)
   221  	size := uint32(len(buf))
   222  
   223  	err = windows.QueryFullProcessImageName(h, 0, &buf[0], &size)
   224  	if err != nil {
   225  		return "", err
   226  	}
   227  
   228  	return windows.UTF16ToString(buf[:size]), nil
   229  }