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