github.com/igoogolx/clash@v1.19.8/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 findProcessPath(network string, from netip.AddrPort, _ netip.AddrPort) (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 "", ErrInvalidNetwork
    45  	}
    46  
    47  	isIPv4 := from.Addr().Is4()
    48  
    49  	value, err := syscall.Sysctl(spath)
    50  	if err != nil {
    51  		return "", 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 from.Port() != srcPort {
    69  			continue
    70  		}
    71  
    72  		// FIXME: add dstPort check
    73  
    74  		// xinpcb_n.inp_vflag
    75  		flag := buf[inp+44]
    76  
    77  		var (
    78  			srcIP     netip.Addr
    79  			srcIPOk   bool
    80  			srcIsIPv4 bool
    81  		)
    82  		switch {
    83  		case flag&0x1 > 0 && isIPv4:
    84  			// ipv4
    85  			srcIP, srcIPOk = netip.AddrFromSlice(buf[inp+76 : inp+80])
    86  			srcIsIPv4 = true
    87  		case flag&0x2 > 0 && !isIPv4:
    88  			// ipv6
    89  			srcIP, srcIPOk = netip.AddrFromSlice(buf[inp+64 : inp+80])
    90  		default:
    91  			continue
    92  		}
    93  		if !srcIPOk {
    94  			continue
    95  		}
    96  
    97  		if from.Addr() == srcIP { // FIXME: add dstIP check
    98  			// xsocket_n.so_last_pid
    99  			pid := readNativeUint32(buf[so+68 : so+72])
   100  			return getExecPathFromPID(pid)
   101  		}
   102  
   103  		// udp packet connection may be not equal with srcIP
   104  		if network == UDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
   105  			fallbackUDPProcess, _ = getExecPathFromPID(readNativeUint32(buf[so+68 : so+72]))
   106  		}
   107  	}
   108  
   109  	if network == UDP && fallbackUDPProcess != "" {
   110  		return fallbackUDPProcess, nil
   111  	}
   112  
   113  	return "", ErrNotFound
   114  }
   115  
   116  func getExecPathFromPID(pid uint32) (string, error) {
   117  	buf := make([]byte, procpidpathinfosize)
   118  	_, _, errno := syscall.Syscall6(
   119  		syscall.SYS_PROC_INFO,
   120  		proccallnumpidinfo,
   121  		uintptr(pid),
   122  		procpidpathinfo,
   123  		0,
   124  		uintptr(unsafe.Pointer(&buf[0])),
   125  		procpidpathinfosize)
   126  	if errno != 0 {
   127  		return "", errno
   128  	}
   129  
   130  	return unix.ByteSliceToString(buf), nil
   131  }
   132  
   133  func readNativeUint32(b []byte) uint32 {
   134  	return *(*uint32)(unsafe.Pointer(&b[0]))
   135  }