1
0

searcher_darwin.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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. var fallbackUDPProcess string
  67. // skip the first xinpgen(24 bytes) block
  68. for i := 24; i+itemSize <= len(buf); i += itemSize {
  69. // offset of xinpcb_n and xsocket_n
  70. inp, so := i, i+104
  71. srcPort := binary.BigEndian.Uint16(buf[inp+18 : inp+20])
  72. if uint16(port) != srcPort {
  73. continue
  74. }
  75. // xinpcb_n.inp_vflag
  76. flag := buf[inp+44]
  77. var srcIP netip.Addr
  78. srcIsIPv4 := false
  79. switch {
  80. case flag&0x1 > 0 && isIPv4:
  81. // ipv4
  82. srcIP = netip.AddrFrom4([4]byte(buf[inp+76 : inp+80]))
  83. srcIsIPv4 = true
  84. case flag&0x2 > 0 && !isIPv4:
  85. // ipv6
  86. srcIP = netip.AddrFrom16([16]byte(buf[inp+64 : inp+80]))
  87. default:
  88. continue
  89. }
  90. if ip == srcIP {
  91. // xsocket_n.so_last_pid
  92. pid := readNativeUint32(buf[so+68 : so+72])
  93. return getExecPathFromPID(pid)
  94. }
  95. // udp packet connection may be not equal with srcIP
  96. if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
  97. pid := readNativeUint32(buf[so+68 : so+72])
  98. fallbackUDPProcess, _ = getExecPathFromPID(pid)
  99. }
  100. }
  101. if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
  102. return fallbackUDPProcess, nil
  103. }
  104. return "", ErrNotFound
  105. }
  106. func getExecPathFromPID(pid uint32) (string, error) {
  107. const (
  108. procpidpathinfo = 0xb
  109. procpidpathinfosize = 1024
  110. proccallnumpidinfo = 0x2
  111. )
  112. buf := make([]byte, procpidpathinfosize)
  113. _, _, errno := syscall.Syscall6(
  114. syscall.SYS_PROC_INFO,
  115. proccallnumpidinfo,
  116. uintptr(pid),
  117. procpidpathinfo,
  118. 0,
  119. uintptr(unsafe.Pointer(&buf[0])),
  120. procpidpathinfosize)
  121. if errno != 0 {
  122. return "", errno
  123. }
  124. return unix.ByteSliceToString(buf), nil
  125. }
  126. func readNativeUint32(b []byte) uint32 {
  127. return *(*uint32)(unsafe.Pointer(&b[0]))
  128. }