github.com/yaling888/clash@v1.53.0/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  	"github.com/yaling888/clash/common/pool"
    14  )
    15  
    16  const (
    17  	procpidpathinfo     = 0xb
    18  	procpidpathinfosize = 1024
    19  	proccallnumpidinfo  = 0x2
    20  )
    21  
    22  var offset = 408
    23  
    24  func init() {
    25  	value, _ := syscall.Sysctl("kern.osrelease")
    26  	before, _, _ := strings.Cut(value, ".")
    27  	n, _ := strconv.ParseInt(before, 10, 64)
    28  	if n < 22 {
    29  		offset = 384
    30  	}
    31  }
    32  
    33  func findProcessPath(network string, from netip.AddrPort, _ netip.AddrPort) (string, error) {
    34  	var spath string
    35  	switch network {
    36  	case TCP:
    37  		spath = "net.inet.tcp.pcblist_n"
    38  	case UDP:
    39  		spath = "net.inet.udp.pcblist_n"
    40  	default:
    41  		return "", ErrInvalidNetwork
    42  	}
    43  
    44  	value, err := syscall.Sysctl(spath)
    45  	if err != nil {
    46  		return "", err
    47  	}
    48  
    49  	buf := []byte(value)
    50  
    51  	itemSize := offset
    52  	if network == TCP {
    53  		// rup8(sizeof(xtcpcb_n))
    54  		itemSize += 208
    55  	}
    56  
    57  	var fallbackUDPProcess string
    58  	// skip the first xinpgen(24 bytes) block
    59  	for i := 24; i+itemSize <= len(buf); i += itemSize {
    60  		// offset of xinpcb_n and xsocket_n
    61  		so := i + 104
    62  
    63  		srcPort := binary.BigEndian.Uint16(buf[i+18 : i+20])
    64  		if from.Port() != srcPort {
    65  			continue
    66  		}
    67  
    68  		// xinpcb_n.inp_vflag
    69  		flag := buf[i+44]
    70  
    71  		var srcIP netip.Addr
    72  		switch {
    73  		case flag&0x1 > 0:
    74  			// ipv4
    75  			srcIP, _ = netip.AddrFromSlice(buf[i+76 : i+80])
    76  		case flag&0x2 > 0:
    77  			// ipv6
    78  			srcIP, _ = netip.AddrFromSlice(buf[i+64 : i+80])
    79  		default:
    80  			continue
    81  		}
    82  
    83  		if !srcIP.IsValid() {
    84  			continue
    85  		}
    86  
    87  		if from.Addr() == srcIP {
    88  			// xsocket_n.so_last_pid
    89  			pid := readNativeUint32(buf[so+68 : so+72])
    90  			return getExecPathFromPID(pid)
    91  		}
    92  
    93  		// udp packet connection may be not equal with srcIP
    94  		if network == UDP && srcIP.IsUnspecified() && from.Addr().Is4() == srcIP.Is4() {
    95  			fallbackUDPProcess, _ = getExecPathFromPID(readNativeUint32(buf[so+68 : so+72]))
    96  		}
    97  	}
    98  
    99  	if network == UDP && fallbackUDPProcess != "" {
   100  		return fallbackUDPProcess, nil
   101  	}
   102  
   103  	return "", ErrNotFound
   104  }
   105  
   106  func getExecPathFromPID(pid uint32) (string, error) {
   107  	bufP := pool.GetBufferWriter()
   108  	bufP.Grow(procpidpathinfosize)
   109  	defer pool.PutBufferWriter(bufP)
   110  	buf := *bufP
   111  	_, _, errno := syscall.Syscall6(
   112  		syscall.SYS_PROC_INFO,
   113  		proccallnumpidinfo,
   114  		uintptr(pid),
   115  		procpidpathinfo,
   116  		0,
   117  		uintptr(unsafe.Pointer(&buf[0])),
   118  		procpidpathinfosize)
   119  	if errno != 0 {
   120  		return "", errno
   121  	}
   122  
   123  	return unix.ByteSliceToString(buf), nil
   124  }
   125  
   126  func readNativeUint32(b []byte) uint32 {
   127  	return binary.NativeEndian.Uint32(b)
   128  }