searcher_windows.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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. if s.tcpState >= 0 {
  105. tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4])
  106. // MIB_TCP_STATE_ESTAB, only check established connections for TCP
  107. if tcpState != 5 {
  108. continue
  109. }
  110. }
  111. // according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian.
  112. // this field can be illustrated as follows depends on different machine endianess:
  113. // little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB)
  114. // big endian: [ 0 0 MSB LSB ] interpret as native uint32 is ((MSB<<8)|LSB)
  115. // so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32
  116. srcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4])))
  117. if srcPort != port {
  118. continue
  119. }
  120. srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
  121. // windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
  122. if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
  123. continue
  124. }
  125. pid := readNativeUint32(row[s.pid : s.pid+4])
  126. return pid, nil
  127. }
  128. return 0, ErrNotFound
  129. }
  130. func newSearcher(isV4, isTCP bool) *searcher {
  131. var itemSize, port, ip, ipSize, pid int
  132. tcpState := -1
  133. switch {
  134. case isV4 && isTCP:
  135. // struct MIB_TCPROW_OWNER_PID
  136. itemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0
  137. case isV4 && !isTCP:
  138. // struct MIB_UDPROW_OWNER_PID
  139. itemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8
  140. case !isV4 && isTCP:
  141. // struct MIB_TCP6ROW_OWNER_PID
  142. itemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48
  143. case !isV4 && !isTCP:
  144. // struct MIB_UDP6ROW_OWNER_PID
  145. itemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24
  146. }
  147. return &searcher{
  148. itemSize: itemSize,
  149. port: port,
  150. ip: ip,
  151. ipSize: ipSize,
  152. pid: pid,
  153. tcpState: tcpState,
  154. }
  155. }
  156. func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
  157. for size, buf := uint32(8), make([]byte, 8); ; {
  158. ptr := unsafe.Pointer(&buf[0])
  159. err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
  160. switch err {
  161. case 0:
  162. return buf, nil
  163. case uintptr(syscall.ERROR_INSUFFICIENT_BUFFER):
  164. buf = make([]byte, size)
  165. default:
  166. return nil, fmt.Errorf("syscall error: %d", err)
  167. }
  168. }
  169. }
  170. func readNativeUint32(b []byte) uint32 {
  171. return *(*uint32)(unsafe.Pointer(&b[0]))
  172. }
  173. func getExecPathFromPID(pid uint32) (string, error) {
  174. // kernel process starts with a colon in order to distinguish with normal processes
  175. switch pid {
  176. case 0:
  177. // reserved pid for system idle process
  178. return ":System Idle Process", nil
  179. case 4:
  180. // reserved pid for windows kernel image
  181. return ":System", nil
  182. }
  183. h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
  184. if err != nil {
  185. return "", err
  186. }
  187. defer windows.CloseHandle(h)
  188. buf := make([]uint16, syscall.MAX_LONG_PATH)
  189. size := uint32(len(buf))
  190. r1, _, err := syscall.SyscallN(
  191. procQueryFullProcessImageNameW.Addr(),
  192. uintptr(h),
  193. uintptr(1),
  194. uintptr(unsafe.Pointer(&buf[0])),
  195. uintptr(unsafe.Pointer(&size)),
  196. )
  197. if r1 == 0 {
  198. return "", err
  199. }
  200. return syscall.UTF16ToString(buf[:size]), nil
  201. }