searcher_darwin.go 3.5 KB

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