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 }