github.com/kelleygo/clashcore@v1.0.2/component/process/process_freebsd_amd64.go (about) 1 package process 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "net/netip" 7 "strconv" 8 "strings" 9 "sync" 10 "syscall" 11 "unsafe" 12 13 "github.com/kelleygo/clashcore/common/nnip" 14 "github.com/kelleygo/clashcore/log" 15 ) 16 17 // store process name for when dealing with multiple PROCESS-NAME rules 18 var ( 19 defaultSearcher *searcher 20 21 once sync.Once 22 ) 23 24 func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) { 25 once.Do(func() { 26 if err := initSearcher(); err != nil { 27 log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) 28 log.Warnln("All PROCESS-NAME rules will be skipped") 29 return 30 } 31 }) 32 33 if defaultSearcher == nil { 34 return 0, "", ErrPlatformNotSupport 35 } 36 37 var spath string 38 isTCP := network == TCP 39 switch network { 40 case TCP: 41 spath = "net.inet.tcp.pcblist" 42 case UDP: 43 spath = "net.inet.udp.pcblist" 44 default: 45 return 0, "", ErrInvalidNetwork 46 } 47 48 value, err := syscall.Sysctl(spath) 49 if err != nil { 50 return 0, "", err 51 } 52 53 buf := []byte(value) 54 pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP) 55 if err != nil { 56 return 0, "", err 57 } 58 59 pp, err := getExecPathFromPID(pid) 60 return 0, pp, err 61 } 62 63 func getExecPathFromPID(pid uint32) (string, error) { 64 buf := make([]byte, 2048) 65 size := uint64(len(buf)) 66 // CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid 67 mib := [4]uint32{1, 14, 12, pid} 68 69 _, _, errno := syscall.Syscall6( 70 syscall.SYS___SYSCTL, 71 uintptr(unsafe.Pointer(&mib[0])), 72 uintptr(len(mib)), 73 uintptr(unsafe.Pointer(&buf[0])), 74 uintptr(unsafe.Pointer(&size)), 75 0, 76 0) 77 if errno != 0 || size == 0 { 78 return "", errno 79 } 80 81 return string(buf[:size-1]), nil 82 } 83 84 func readNativeUint32(b []byte) uint32 { 85 return *(*uint32)(unsafe.Pointer(&b[0])) 86 } 87 88 type searcher struct { 89 // sizeof(struct xinpgen) 90 headSize int 91 // sizeof(struct xtcpcb) 92 tcpItemSize int 93 // sizeof(struct xinpcb) 94 udpItemSize int 95 udpInpOffset int 96 port int 97 ip int 98 vflag int 99 socket int 100 101 // sizeof(struct xfile) 102 fileItemSize int 103 data int 104 pid int 105 } 106 107 func (s *searcher) Search(buf []byte, ip netip.Addr, port uint16, isTCP bool) (uint32, error) { 108 var itemSize int 109 var inpOffset int 110 111 if isTCP { 112 // struct xtcpcb 113 itemSize = s.tcpItemSize 114 inpOffset = 8 115 } else { 116 // struct xinpcb 117 itemSize = s.udpItemSize 118 inpOffset = s.udpInpOffset 119 } 120 121 isIPv4 := ip.Is4() 122 // skip the first xinpgen block 123 for i := s.headSize; i+itemSize <= len(buf); i += itemSize { 124 inp := i + inpOffset 125 126 srcPort := binary.BigEndian.Uint16(buf[inp+s.port : inp+s.port+2]) 127 128 if port != srcPort { 129 continue 130 } 131 132 // xinpcb.inp_vflag 133 flag := buf[inp+s.vflag] 134 135 var srcIP netip.Addr 136 switch { 137 case flag&0x1 > 0 && isIPv4: 138 // ipv4 139 srcIP = nnip.IpToAddr(buf[inp+s.ip : inp+s.ip+4]) 140 case flag&0x2 > 0 && !isIPv4: 141 // ipv6 142 srcIP = nnip.IpToAddr(buf[inp+s.ip-12 : inp+s.ip+4]) 143 default: 144 continue 145 } 146 147 if ip != srcIP { 148 continue 149 } 150 151 // xsocket.xso_so, interpreted as big endian anyway since it's only used for comparison 152 socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8]) 153 return s.searchSocketPid(socket) 154 } 155 return 0, ErrNotFound 156 } 157 158 func (s *searcher) searchSocketPid(socket uint64) (uint32, error) { 159 value, err := syscall.Sysctl("kern.file") 160 if err != nil { 161 return 0, err 162 } 163 164 buf := []byte(value) 165 166 // struct xfile 167 itemSize := s.fileItemSize 168 for i := 0; i+itemSize <= len(buf); i += itemSize { 169 // xfile.xf_data 170 data := binary.BigEndian.Uint64(buf[i+s.data : i+s.data+8]) 171 if data == socket { 172 // xfile.xf_pid 173 pid := readNativeUint32(buf[i+s.pid : i+s.pid+4]) 174 return pid, nil 175 } 176 } 177 return 0, ErrNotFound 178 } 179 180 func newSearcher(major int) *searcher { 181 var s *searcher 182 switch major { 183 case 11: 184 s = &searcher{ 185 headSize: 32, 186 tcpItemSize: 1304, 187 udpItemSize: 632, 188 port: 198, 189 ip: 228, 190 vflag: 116, 191 socket: 88, 192 fileItemSize: 80, 193 data: 56, 194 pid: 8, 195 udpInpOffset: 8, 196 } 197 case 12: 198 fallthrough 199 case 13: 200 s = &searcher{ 201 headSize: 64, 202 tcpItemSize: 744, 203 udpItemSize: 400, 204 port: 254, 205 ip: 284, 206 vflag: 392, 207 socket: 16, 208 fileItemSize: 128, 209 data: 56, 210 pid: 8, 211 } 212 } 213 return s 214 } 215 216 func initSearcher() error { 217 osRelease, err := syscall.Sysctl("kern.osrelease") 218 if err != nil { 219 return err 220 } 221 222 dot := strings.Index(osRelease, ".") 223 if dot != -1 { 224 osRelease = osRelease[:dot] 225 } 226 major, err := strconv.Atoi(osRelease) 227 if err != nil { 228 return err 229 } 230 defaultSearcher = newSearcher(major) 231 if defaultSearcher == nil { 232 return fmt.Errorf("unsupported freebsd version %d", major) 233 } 234 return nil 235 }