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 }