github.com/kelleygo/clashcore@v1.0.2/component/process/process_linux.go (about) 1 package process 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "fmt" 7 "net/netip" 8 "os" 9 "path" 10 "path/filepath" 11 "runtime" 12 "strings" 13 "syscall" 14 "unicode" 15 "unsafe" 16 17 "github.com/mdlayher/netlink" 18 "golang.org/x/sys/unix" 19 ) 20 21 const ( 22 SOCK_DIAG_BY_FAMILY = 20 23 inetDiagRequestSize = int(unsafe.Sizeof(inetDiagRequest{})) 24 inetDiagResponseSize = int(unsafe.Sizeof(inetDiagResponse{})) 25 ) 26 27 type inetDiagRequest struct { 28 Family byte 29 Protocol byte 30 Ext byte 31 Pad byte 32 States uint32 33 34 SrcPort [2]byte 35 DstPort [2]byte 36 Src [16]byte 37 Dst [16]byte 38 If uint32 39 Cookie [2]uint32 40 } 41 42 type inetDiagResponse struct { 43 Family byte 44 State byte 45 Timer byte 46 ReTrans byte 47 48 SrcPort [2]byte 49 DstPort [2]byte 50 Src [16]byte 51 Dst [16]byte 52 If uint32 53 Cookie [2]uint32 54 55 Expires uint32 56 RQueue uint32 57 WQueue uint32 58 UID uint32 59 INode uint32 60 } 61 62 func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) { 63 uid, inode, err := resolveSocketByNetlink(network, ip, srcPort) 64 if err != nil { 65 return 0, "", err 66 } 67 68 pp, err := resolveProcessNameByProcSearch(inode, uid) 69 return uid, pp, err 70 } 71 72 func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) { 73 request := &inetDiagRequest{ 74 States: 0xffffffff, 75 Cookie: [2]uint32{0xffffffff, 0xffffffff}, 76 } 77 78 if ip.Is4() { 79 request.Family = unix.AF_INET 80 } else { 81 request.Family = unix.AF_INET6 82 } 83 84 if strings.HasPrefix(network, "tcp") { 85 request.Protocol = unix.IPPROTO_TCP 86 } else if strings.HasPrefix(network, "udp") { 87 request.Protocol = unix.IPPROTO_UDP 88 } else { 89 return 0, 0, ErrInvalidNetwork 90 } 91 92 copy(request.Src[:], ip.AsSlice()) 93 94 binary.BigEndian.PutUint16(request.SrcPort[:], uint16(srcPort)) 95 96 conn, err := netlink.Dial(unix.NETLINK_INET_DIAG, nil) 97 if err != nil { 98 return 0, 0, err 99 } 100 defer conn.Close() 101 102 message := netlink.Message{ 103 Header: netlink.Header{ 104 Type: SOCK_DIAG_BY_FAMILY, 105 Flags: netlink.Request | netlink.Dump, 106 }, 107 Data: (*(*[inetDiagRequestSize]byte)(unsafe.Pointer(request)))[:], 108 } 109 110 messages, err := conn.Execute(message) 111 if err != nil { 112 return 0, 0, err 113 } 114 115 for _, msg := range messages { 116 if len(msg.Data) < inetDiagResponseSize { 117 continue 118 } 119 120 response := (*inetDiagResponse)(unsafe.Pointer(&msg.Data[0])) 121 122 return response.UID, response.INode, nil 123 } 124 125 return 0, 0, ErrNotFound 126 } 127 128 func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) { 129 files, err := os.ReadDir("/proc") 130 if err != nil { 131 return "", err 132 } 133 134 buffer := make([]byte, unix.PathMax) 135 socket := fmt.Appendf(nil, "socket:[%d]", inode) 136 137 for _, f := range files { 138 if !f.IsDir() || !isPid(f.Name()) { 139 continue 140 } 141 142 info, err := f.Info() 143 if err != nil { 144 return "", err 145 } 146 if info.Sys().(*syscall.Stat_t).Uid != uid { 147 continue 148 } 149 150 processPath := filepath.Join("/proc", f.Name()) 151 fdPath := filepath.Join(processPath, "fd") 152 153 fds, err := os.ReadDir(fdPath) 154 if err != nil { 155 continue 156 } 157 158 for _, fd := range fds { 159 n, err := unix.Readlink(filepath.Join(fdPath, fd.Name()), buffer) 160 if err != nil { 161 continue 162 } 163 if runtime.GOOS == "android" { 164 if bytes.Equal(buffer[:n], socket) { 165 cmdline, err := os.ReadFile(path.Join(processPath, "cmdline")) 166 if err != nil { 167 return "", err 168 } 169 170 return splitCmdline(cmdline), nil 171 } 172 } else { 173 if bytes.Equal(buffer[:n], socket) { 174 return os.Readlink(filepath.Join(processPath, "exe")) 175 } 176 } 177 178 } 179 } 180 181 return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode) 182 } 183 184 func splitCmdline(cmdline []byte) string { 185 cmdline = bytes.Trim(cmdline, " ") 186 187 idx := bytes.IndexFunc(cmdline, func(r rune) bool { 188 return unicode.IsControl(r) || unicode.IsSpace(r) || r == ':' 189 }) 190 191 if idx == -1 { 192 return filepath.Base(string(cmdline)) 193 } 194 return filepath.Base(string(cmdline[:idx])) 195 } 196 197 func isPid(s string) bool { 198 return strings.IndexFunc(s, func(r rune) bool { 199 return !unicode.IsDigit(r) 200 }) == -1 201 }