searcher_darwin.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package process
  2. import (
  3. "context"
  4. "encoding/binary"
  5. "net/netip"
  6. "os"
  7. "strconv"
  8. "strings"
  9. "syscall"
  10. "unsafe"
  11. N "github.com/sagernet/sing/common/network"
  12. "golang.org/x/sys/unix"
  13. )
  14. var _ Searcher = (*darwinSearcher)(nil)
  15. type darwinSearcher struct{}
  16. func NewSearcher(_ Config) (Searcher, error) {
  17. return &darwinSearcher{}, nil
  18. }
  19. func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
  20. processName, err := findProcessName(network, source.Addr(), int(source.Port()))
  21. if err != nil {
  22. return nil, err
  23. }
  24. return &Info{ProcessPath: processName, UserId: -1}, nil
  25. }
  26. var structSize = func() int {
  27. value, _ := syscall.Sysctl("kern.osrelease")
  28. major, _, _ := strings.Cut(value, ".")
  29. n, _ := strconv.ParseInt(major, 10, 64)
  30. switch true {
  31. case n >= 22:
  32. return 408
  33. default:
  34. // from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
  35. // size/offset are round up (aligned) to 8 bytes in darwin
  36. // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
  37. // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
  38. return 384
  39. }
  40. }()
  41. func findProcessName(network string, ip netip.Addr, port int) (string, error) {
  42. var spath string
  43. switch network {
  44. case N.NetworkTCP:
  45. spath = "net.inet.tcp.pcblist_n"
  46. case N.NetworkUDP:
  47. spath = "net.inet.udp.pcblist_n"
  48. default:
  49. return "", os.ErrInvalid
  50. }
  51. isIPv4 := ip.Is4()
  52. value, err := unix.SysctlRaw(spath)
  53. if err != nil {
  54. return "", err
  55. }
  56. buf := value
  57. // from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
  58. // size/offset are round up (aligned) to 8 bytes in darwin
  59. // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
  60. // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
  61. itemSize := structSize
  62. if network == N.NetworkTCP {
  63. // rup8(sizeof(xtcpcb_n))
  64. itemSize += 208
  65. }
  66. // skip the first xinpgen(24 bytes) block
  67. for i := 24; i+itemSize <= len(buf); i += itemSize {
  68. // offset of xinpcb_n and xsocket_n
  69. inp, so := i, i+104
  70. srcPort := binary.BigEndian.Uint16(buf[inp+18 : inp+20])
  71. if uint16(port) != srcPort {
  72. continue
  73. }
  74. // xinpcb_n.inp_vflag
  75. flag := buf[inp+44]
  76. var srcIP netip.Addr
  77. switch {
  78. case flag&0x1 > 0 && isIPv4:
  79. // ipv4
  80. srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
  81. case flag&0x2 > 0 && !isIPv4:
  82. // ipv6
  83. srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
  84. default:
  85. continue
  86. }
  87. if ip != srcIP {
  88. continue
  89. }
  90. // xsocket_n.so_last_pid
  91. pid := readNativeUint32(buf[so+68 : so+72])
  92. return getExecPathFromPID(pid)
  93. }
  94. return "", ErrNotFound
  95. }
  96. func getExecPathFromPID(pid uint32) (string, error) {
  97. const (
  98. procpidpathinfo = 0xb
  99. procpidpathinfosize = 1024
  100. proccallnumpidinfo = 0x2
  101. )
  102. buf := make([]byte, procpidpathinfosize)
  103. _, _, errno := syscall.Syscall6(
  104. syscall.SYS_PROC_INFO,
  105. proccallnumpidinfo,
  106. uintptr(pid),
  107. procpidpathinfo,
  108. 0,
  109. uintptr(unsafe.Pointer(&buf[0])),
  110. procpidpathinfosize)
  111. if errno != 0 {
  112. return "", errno
  113. }
  114. return unix.ByteSliceToString(buf), nil
  115. }
  116. func readNativeUint32(b []byte) uint32 {
  117. return *(*uint32)(unsafe.Pointer(&b[0]))
  118. }