File indexing completed on 2024-06-23 05:27:53
0001 /* 0002 SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl> 0003 SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de> 0004 0005 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "ConnectionMapping.h" 0009 0010 #include <cstring> 0011 #include <charconv> 0012 #include <fstream> 0013 #include <iostream> 0014 0015 #include <dirent.h> 0016 #include <errno.h> 0017 #include <unistd.h> 0018 0019 #include <arpa/inet.h> 0020 #include <linux/inet_diag.h> 0021 #include <linux/sock_diag.h> 0022 0023 #include <netlink/msg.h> 0024 #include <netlink/netlink.h> 0025 0026 using namespace std::string_literals; 0027 0028 template<typename Key, typename Value> 0029 inline void cleanupOldEntries(const std::unordered_set<Key> &keys, std::unordered_map<Key, Value> &map) 0030 { 0031 for (auto itr = map.begin(); itr != map.end();) { 0032 if (keys.find(itr->first) == keys.end()) { 0033 itr = map.erase(itr); 0034 } else { 0035 itr++; 0036 } 0037 } 0038 } 0039 0040 ConnectionMapping::inode_t toInode(const std::string_view &view) 0041 { 0042 ConnectionMapping::inode_t value; 0043 if (auto status = std::from_chars(view.data(), view.data() + view.length(), value); status.ec == std::errc()) { 0044 return value; 0045 } 0046 return std::numeric_limits<ConnectionMapping::inode_t>::max(); 0047 } 0048 0049 int parseInetDiagMesg(struct nl_msg *msg, void *arg) 0050 { 0051 auto self = static_cast<ConnectionMapping *>(arg); 0052 struct nlmsghdr *nlh = nlmsg_hdr(msg); 0053 auto inetDiagMsg = static_cast<inet_diag_msg *>(nlmsg_data(nlh)); 0054 Packet::Address localAddress; 0055 if (inetDiagMsg->idiag_family == AF_INET) { 0056 // I expected to need ntohl calls here and bewlow for src but comparing to values gathered 0057 // by parsing proc they are not needed and even produce wrong results 0058 localAddress.address[3] = inetDiagMsg->id.idiag_src[0]; 0059 } else if (inetDiagMsg->id.idiag_src[0] == 0 && inetDiagMsg->id.idiag_src[1] == 0 && inetDiagMsg->id.idiag_src[2] == 0xffff0000) { 0060 // Some applications (like Steam) use ipv6 sockets with ipv4. 0061 // This results in ipv4 addresses that end up in the tcp6 file. 0062 // They seem to start with 0000000000000000FFFF0000, so if we 0063 // detect that, assume it is ipv4-over-ipv6. 0064 localAddress.address[3] = inetDiagMsg->id.idiag_src[3]; 0065 0066 } else { 0067 std::memcpy(localAddress.address.data(), inetDiagMsg->id.idiag_src, sizeof(localAddress.address)); 0068 } 0069 localAddress.port = ntohs(inetDiagMsg->id.idiag_sport); 0070 0071 if (self->m_newState.addressToInode.find(localAddress) == self->m_newState.addressToInode.end()) { 0072 // new localAddress is found for which no socket inode is known 0073 // will trigger pid parsing 0074 self->m_newState.addressToInode.emplace(localAddress, inetDiagMsg->idiag_inode); 0075 self->m_newInode = true; 0076 } 0077 0078 self->m_seenAddresses.insert(localAddress); 0079 self->m_seenInodes.insert(inetDiagMsg->idiag_inode); 0080 0081 return NL_OK; 0082 } 0083 0084 ConnectionMapping::ConnectionMapping() 0085 : m_running(true) 0086 { 0087 m_thread = std::thread(&ConnectionMapping::loop, this); 0088 } 0089 0090 ConnectionMapping::~ConnectionMapping() 0091 { 0092 m_running = false; 0093 if (m_thread.joinable()) { 0094 m_thread.join(); 0095 } 0096 } 0097 0098 0099 ConnectionMapping::PacketResult ConnectionMapping::pidForPacket(const Packet &packet) 0100 { 0101 std::lock_guard<std::mutex> lock{m_mutex}; 0102 0103 PacketResult result; 0104 0105 auto sourceInode = m_oldState.addressToInode.find(packet.sourceAddress()); 0106 auto destInode = m_oldState.addressToInode.find(packet.destinationAddress()); 0107 0108 if (sourceInode == m_oldState.addressToInode.end() && destInode == m_oldState.addressToInode.end()) { 0109 return result; 0110 } 0111 0112 auto inode = m_oldState.addressToInode.end(); 0113 if (sourceInode != m_oldState.addressToInode.end()) { 0114 result.direction = Packet::Direction::Outbound; 0115 inode = sourceInode; 0116 } else { 0117 result.direction = Packet::Direction::Inbound; 0118 inode = destInode; 0119 } 0120 0121 auto pid = m_oldState.inodeToPid.find((*inode).second); 0122 if (pid == m_oldState.inodeToPid.end()) { 0123 result.pid = -1; 0124 } else { 0125 result.pid = (*pid).second; 0126 } 0127 return result; 0128 } 0129 0130 void ConnectionMapping::loop() 0131 { 0132 std::unique_ptr<nl_sock, decltype(&nl_socket_free)> socket{nl_socket_alloc(), nl_socket_free}; 0133 0134 nl_connect(socket.get(), NETLINK_SOCK_DIAG); 0135 nl_socket_modify_cb(socket.get(), NL_CB_VALID, NL_CB_CUSTOM, &parseInetDiagMesg, this); 0136 0137 while (m_running) { 0138 m_seenAddresses.clear(); 0139 m_seenInodes.clear(); 0140 0141 dumpSockets(socket.get()); 0142 0143 if (m_newInode) { 0144 parsePid(); 0145 m_newInode = false; 0146 } 0147 0148 cleanupOldEntries(m_seenAddresses, m_newState.addressToInode); 0149 cleanupOldEntries(m_seenInodes, m_newState.inodeToPid); 0150 0151 { 0152 std::lock_guard<std::mutex> lock{m_mutex}; 0153 m_oldState = m_newState; 0154 } 0155 0156 std::this_thread::sleep_for(std::chrono::milliseconds(500)); 0157 } 0158 } 0159 0160 bool ConnectionMapping::dumpSockets(nl_sock *socket) 0161 { 0162 for (auto family : {AF_INET, AF_INET6}) { 0163 for (auto protocol : {IPPROTO_TCP, IPPROTO_UDP}) { 0164 if (!dumpSockets(socket, family, protocol)) { 0165 return false; 0166 } 0167 } 0168 } 0169 return true; 0170 } 0171 0172 bool ConnectionMapping::dumpSockets(nl_sock *socket, int inet_family, int protocol) 0173 { 0174 inet_diag_req_v2 inet_request; 0175 inet_request.id = {}; 0176 inet_request.sdiag_family = inet_family; 0177 inet_request.sdiag_protocol = protocol; 0178 inet_request.idiag_states = -1; 0179 if (nl_send_simple(socket, SOCK_DIAG_BY_FAMILY, NLM_F_DUMP | NLM_F_REQUEST, &inet_request, sizeof(inet_diag_req_v2)) < 0) { 0180 return false; 0181 } 0182 if (nl_recvmsgs_default(socket) != 0) { 0183 return false; 0184 } 0185 return true; 0186 } 0187 0188 void ConnectionMapping::parsePid() 0189 { 0190 auto dir = opendir("/proc"); 0191 0192 std::array<char, 100> buffer; 0193 0194 auto fdPath = "/proc/%/fd"s; 0195 // Ensure the string has enough space to accommodate large PIDs 0196 fdPath.reserve(30); 0197 0198 // The only way to get a list of PIDs is to list the contents of /proc. 0199 // Any directory with a numeric name corresponds to a process and its PID. 0200 dirent *entry = nullptr; 0201 while ((entry = readdir(dir))) { 0202 if (entry->d_type != DT_DIR) { 0203 continue; 0204 } 0205 0206 if (entry->d_name[0] < '0' || entry->d_name[0] > '9') { 0207 continue; 0208 } 0209 0210 // We need to list the contents of a subdirectory of the PID directory. 0211 // To avoid multiple allocations we reserve the string above and reuse 0212 // it here. 0213 fdPath.replace(6, fdPath.find_last_of('/') - 6, entry->d_name); 0214 0215 auto fdDir = opendir(fdPath.data()); 0216 if (fdDir == NULL) { 0217 continue; 0218 } 0219 0220 dirent *fd = nullptr; 0221 while ((fd = readdir(fdDir))) { 0222 if (fd->d_type != DT_LNK) { 0223 continue; 0224 } 0225 0226 // /proc/PID/fd contains symlinks for each open fd in the process. 0227 // The symlink target contains information about what the fd is about. 0228 auto size = readlinkat(dirfd(fdDir), fd->d_name, buffer.data(), 99); 0229 if (size < 0) { 0230 continue; 0231 } 0232 buffer[size] = '\0'; 0233 0234 auto view = std::string_view(buffer.data(), 100); 0235 0236 // In this case, we are only interested in sockets, for which the 0237 // symlink target starts with 'socket:', followed by the inode 0238 // number in square brackets. 0239 if (view.compare(0, 7, "socket:") != 0) { 0240 continue; 0241 } 0242 0243 // Strip off the leading "socket:" part and the opening bracket, 0244 // then convert that to an inode number. 0245 auto inode = toInode(view.substr(8)); 0246 if (inode != std::numeric_limits<inode_t>::max()) { 0247 m_newState.inodeToPid[inode] = std::stoi(entry->d_name); 0248 } 0249 } 0250 0251 closedir(fdDir); 0252 } 0253 0254 closedir(dir); 0255 }