github.com/chwjbn/xclash@v0.2.0/component/process/process_linux.go (about) 1 package process 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "fmt" 7 "io" 8 "net" 9 "os" 10 "path" 11 "path/filepath" 12 "syscall" 13 "unsafe" 14 15 "github.com/chwjbn/xclash/common/pool" 16 ) 17 18 // from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62 19 var nativeEndian = func() binary.ByteOrder { 20 var x uint32 = 0x01020304 21 if *(*byte)(unsafe.Pointer(&x)) == 0x01 { 22 return binary.BigEndian 23 } 24 25 return binary.LittleEndian 26 }() 27 28 type ( 29 SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error) 30 ProcessNameResolver func(inode, uid int) (name string, err error) 31 ) 32 33 // export for android 34 var ( 35 DefaultSocketResolver SocketResolver = resolveSocketByNetlink 36 DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch 37 ) 38 39 const ( 40 sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 41 socketDiagByFamily = 20 42 pathProc = "/proc" 43 ) 44 45 func findProcessName(network string, ip net.IP, srcPort int) (string, error) { 46 inode, uid, err := DefaultSocketResolver(network, ip, srcPort) 47 if err != nil { 48 return "", err 49 } 50 51 return DefaultProcessNameResolver(inode, uid) 52 } 53 54 func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, error) { 55 var family byte 56 var protocol byte 57 58 switch network { 59 case TCP: 60 protocol = syscall.IPPROTO_TCP 61 case UDP: 62 protocol = syscall.IPPROTO_UDP 63 default: 64 return 0, 0, ErrInvalidNetwork 65 } 66 67 if ip.To4() != nil { 68 family = syscall.AF_INET 69 } else { 70 family = syscall.AF_INET6 71 } 72 73 req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort)) 74 75 socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) 76 if err != nil { 77 return 0, 0, err 78 } 79 defer syscall.Close(socket) 80 81 syscall.SetNonblock(socket, true) 82 syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 50}) 83 syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 50}) 84 85 if err := syscall.Connect(socket, &syscall.SockaddrNetlink{ 86 Family: syscall.AF_NETLINK, 87 Pad: 0, 88 Pid: 0, 89 Groups: 0, 90 }); err != nil { 91 return 0, 0, err 92 } 93 94 if _, err := syscall.Write(socket, req); err != nil { 95 return 0, 0, err 96 } 97 98 rb := pool.Get(pool.RelayBufferSize) 99 defer pool.Put(rb) 100 101 n, err := syscall.Read(socket, rb) 102 if err != nil { 103 return 0, 0, err 104 } 105 106 messages, err := syscall.ParseNetlinkMessage(rb[:n]) 107 if err != nil { 108 return 0, 0, err 109 } else if len(messages) == 0 { 110 return 0, 0, io.ErrUnexpectedEOF 111 } 112 113 message := messages[0] 114 if message.Header.Type&syscall.NLMSG_ERROR != 0 { 115 return 0, 0, syscall.ESRCH 116 } 117 118 uid, inode := unpackSocketDiagResponse(&messages[0]) 119 120 return int(uid), int(inode), nil 121 } 122 123 func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte { 124 s := make([]byte, 16) 125 126 if v4 := source.To4(); v4 != nil { 127 copy(s, v4) 128 } else { 129 copy(s, source) 130 } 131 132 buf := make([]byte, sizeOfSocketDiagRequest) 133 134 nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest) 135 nativeEndian.PutUint16(buf[4:6], socketDiagByFamily) 136 nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP) 137 nativeEndian.PutUint32(buf[8:12], 0) 138 nativeEndian.PutUint32(buf[12:16], 0) 139 140 buf[16] = family 141 buf[17] = protocol 142 buf[18] = 0 143 buf[19] = 0 144 nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF) 145 146 binary.BigEndian.PutUint16(buf[24:26], sourcePort) 147 binary.BigEndian.PutUint16(buf[26:28], 0) 148 149 copy(buf[28:44], s) 150 copy(buf[44:60], net.IPv6zero) 151 152 nativeEndian.PutUint32(buf[60:64], 0) 153 nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF) 154 155 return buf 156 } 157 158 func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) { 159 if len(msg.Data) < 72 { 160 return 0, 0 161 } 162 163 data := msg.Data 164 165 uid = nativeEndian.Uint32(data[64:68]) 166 inode = nativeEndian.Uint32(data[68:72]) 167 168 return 169 } 170 171 func resolveProcessNameByProcSearch(inode, uid int) (string, error) { 172 files, err := os.ReadDir(pathProc) 173 if err != nil { 174 return "", err 175 } 176 177 buffer := make([]byte, syscall.PathMax) 178 socket := []byte(fmt.Sprintf("socket:[%d]", inode)) 179 180 for _, f := range files { 181 if !f.IsDir() || !isPid(f.Name()) { 182 continue 183 } 184 185 info, err := f.Info() 186 if err != nil { 187 return "", err 188 } 189 if info.Sys().(*syscall.Stat_t).Uid != uint32(uid) { 190 continue 191 } 192 193 processPath := path.Join(pathProc, f.Name()) 194 fdPath := path.Join(processPath, "fd") 195 196 fds, err := os.ReadDir(fdPath) 197 if err != nil { 198 continue 199 } 200 201 for _, fd := range fds { 202 n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer) 203 if err != nil { 204 continue 205 } 206 207 if bytes.Equal(buffer[:n], socket) { 208 cmdline, err := os.ReadFile(path.Join(processPath, "cmdline")) 209 if err != nil { 210 return "", err 211 } 212 213 return splitCmdline(cmdline), nil 214 } 215 } 216 } 217 218 return "", syscall.ESRCH 219 } 220 221 func splitCmdline(cmdline []byte) string { 222 indexOfEndOfString := len(cmdline) 223 224 for i, c := range cmdline { 225 if c == 0 { 226 indexOfEndOfString = i 227 break 228 } 229 } 230 231 return filepath.Base(string(cmdline[:indexOfEndOfString])) 232 } 233 234 func isPid(s string) bool { 235 for _, s := range s { 236 if s < '0' || s > '9' { 237 return false 238 } 239 } 240 241 return true 242 }