github.com/igoogolx/clash@v1.19.8/component/process/process_linux.go (about) 1 package process 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "fmt" 7 "net" 8 "net/netip" 9 "os" 10 "unsafe" 11 12 "github.com/igoogolx/clash/common/pool" 13 14 "github.com/mdlayher/netlink" 15 "golang.org/x/sys/unix" 16 ) 17 18 type inetDiagRequest struct { 19 Family byte 20 Protocol byte 21 Ext byte 22 Pad byte 23 States uint32 24 25 SrcPort [2]byte 26 DstPort [2]byte 27 Src [16]byte 28 Dst [16]byte 29 If uint32 30 Cookie [2]uint32 31 } 32 33 type inetDiagResponse struct { 34 Family byte 35 State byte 36 Timer byte 37 ReTrans byte 38 39 SrcPort [2]byte 40 DstPort [2]byte 41 Src [16]byte 42 Dst [16]byte 43 If uint32 44 Cookie [2]uint32 45 46 Expires uint32 47 RQueue uint32 48 WQueue uint32 49 UID uint32 50 INode uint32 51 } 52 53 func findProcessPath(network string, from netip.AddrPort, to netip.AddrPort) (string, error) { 54 inode, uid, err := resolveSocketByNetlink(network, from, to) 55 if err != nil { 56 return "", err 57 } 58 59 return resolveProcessPathByProcSearch(inode, uid) 60 } 61 62 func resolveSocketByNetlink(network string, from netip.AddrPort, to netip.AddrPort) (inode uint32, uid uint32, err error) { 63 var families []byte 64 if from.Addr().Unmap().Is4() { 65 families = []byte{unix.AF_INET, unix.AF_INET6} 66 } else { 67 families = []byte{unix.AF_INET6, unix.AF_INET} 68 } 69 70 var protocol byte 71 switch network { 72 case TCP: 73 protocol = unix.IPPROTO_TCP 74 case UDP: 75 protocol = unix.IPPROTO_UDP 76 default: 77 return 0, 0, ErrInvalidNetwork 78 } 79 80 if protocol == unix.IPPROTO_UDP { 81 // Swap from & to for udp 82 // See also https://www.mail-archive.com/netdev@vger.kernel.org/msg248638.html 83 from, to = to, from 84 } 85 86 for _, family := range families { 87 inode, uid, err = resolveSocketByNetlinkExact(family, protocol, from, to, netlink.Request) 88 if err == nil { 89 return inode, uid, err 90 } 91 } 92 93 return 0, 0, ErrNotFound 94 } 95 96 func resolveSocketByNetlinkExact(family byte, protocol byte, from netip.AddrPort, to netip.AddrPort, flags netlink.HeaderFlags) (inode uint32, uid uint32, err error) { 97 request := &inetDiagRequest{ 98 Family: family, 99 Protocol: protocol, 100 States: 0xffffffff, 101 Cookie: [2]uint32{0xffffffff, 0xffffffff}, 102 } 103 104 var ( 105 fromAddr []byte 106 toAddr []byte 107 ) 108 if family == unix.AF_INET { 109 fromAddr = net.IP(from.Addr().AsSlice()).To4() 110 toAddr = net.IP(to.Addr().AsSlice()).To4() 111 } else { 112 fromAddr = net.IP(from.Addr().AsSlice()).To16() 113 toAddr = net.IP(to.Addr().AsSlice()).To16() 114 } 115 116 copy(request.Src[:], fromAddr) 117 copy(request.Dst[:], toAddr) 118 119 binary.BigEndian.PutUint16(request.SrcPort[:], from.Port()) 120 binary.BigEndian.PutUint16(request.DstPort[:], to.Port()) 121 122 conn, err := netlink.Dial(unix.NETLINK_INET_DIAG, nil) 123 if err != nil { 124 return 0, 0, err 125 } 126 defer conn.Close() 127 128 message := netlink.Message{ 129 Header: netlink.Header{ 130 Type: 20, // SOCK_DIAG_BY_FAMILY 131 Flags: flags, 132 }, 133 Data: (*(*[unsafe.Sizeof(*request)]byte)(unsafe.Pointer(request)))[:], 134 } 135 136 messages, err := conn.Execute(message) 137 if err != nil { 138 return 0, 0, err 139 } 140 141 for _, msg := range messages { 142 if len(msg.Data) < int(unsafe.Sizeof(inetDiagResponse{})) { 143 continue 144 } 145 146 response := (*inetDiagResponse)(unsafe.Pointer(&msg.Data[0])) 147 148 return response.INode, response.UID, nil 149 } 150 151 return 0, 0, ErrNotFound 152 } 153 154 func resolveProcessPathByProcSearch(inode, uid uint32) (string, error) { 155 procDir, err := os.Open("/proc") 156 if err != nil { 157 return "", err 158 } 159 defer procDir.Close() 160 161 pids, err := procDir.Readdirnames(-1) 162 if err != nil { 163 return "", err 164 } 165 166 expectedSocketName := fmt.Appendf(nil, "socket:[%d]", inode) 167 168 pathBuffer := pool.Get(64) 169 defer pool.Put(pathBuffer) 170 171 readlinkBuffer := pool.Get(32) 172 defer pool.Put(readlinkBuffer) 173 174 copy(pathBuffer, "/proc/") 175 176 for _, pid := range pids { 177 if !isPid(pid) { 178 continue 179 } 180 181 pathBuffer = append(pathBuffer[:len("/proc/")], pid...) 182 183 stat := &unix.Stat_t{} 184 err = unix.Stat(string(pathBuffer), stat) 185 if err != nil { 186 continue 187 } else if stat.Uid != uid { 188 continue 189 } 190 191 pathBuffer = append(pathBuffer, "/fd/"...) 192 fdsPrefixLength := len(pathBuffer) 193 194 fdDir, err := os.Open(string(pathBuffer)) 195 if err != nil { 196 continue 197 } 198 199 fds, err := fdDir.Readdirnames(-1) 200 fdDir.Close() 201 if err != nil { 202 continue 203 } 204 205 for _, fd := range fds { 206 pathBuffer = pathBuffer[:fdsPrefixLength] 207 208 pathBuffer = append(pathBuffer, fd...) 209 210 n, err := unix.Readlink(string(pathBuffer), readlinkBuffer) 211 if err != nil { 212 continue 213 } 214 215 if bytes.Equal(readlinkBuffer[:n], expectedSocketName) { 216 return os.Readlink("/proc/" + pid + "/exe") 217 } 218 } 219 } 220 221 return "", fmt.Errorf("inode %d of uid %d not found", inode, uid) 222 } 223 224 func isPid(name string) bool { 225 for _, c := range name { 226 if c < '0' || c > '9' { 227 return false 228 } 229 } 230 231 return true 232 }