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 }