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 }