github.com/kelleygo/clashcore@v1.0.2/component/process/process_darwin.go (about)

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