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