searcher_windows.go 5.7 KB

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