Changeset View
Changeset View
Standalone View
Standalone View
plugins/process/network/helper/ConnectionMapping.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * This file is part of KSysGuard. | ||||
3 | * Copyright 2019 Arjen Hiemstra <ahiemstra@heimr.nl> | ||||
4 | * | ||||
5 | * This program is free software; you can redistribute it and/or | ||||
6 | * modify it under the terms of the GNU General Public License as | ||||
7 | * published by the Free Software Foundation; either version 2 of | ||||
8 | * the License or (at your option) version 3 or any later version | ||||
9 | * accepted by the membership of KDE e.V. (or its successor approved | ||||
10 | * by the membership of KDE e.V.), which shall act as a proxy | ||||
11 | * defined in Section 14 of version 3 of the license. | ||||
12 | * | ||||
13 | * This program is distributed in the hope that it will be useful, | ||||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
16 | * GNU General Public License for more details. | ||||
17 | * | ||||
18 | * You should have received a copy of the GNU General Public License | ||||
19 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
20 | */ | ||||
21 | | ||||
22 | #include "ConnectionMapping.h" | ||||
23 | | ||||
24 | #include <fstream> | ||||
25 | #include <iostream> | ||||
26 | | ||||
27 | #include <dirent.h> | ||||
28 | #include <errno.h> | ||||
29 | #include <unistd.h> | ||||
30 | | ||||
31 | using namespace std::string_literals; | ||||
32 | | ||||
33 | // Convert /proc/net/tcp's mangled big-endian notation to a host-endian int32' | ||||
34 | uint32_t tcpToInt(const std::string &part) | ||||
35 | { | ||||
36 | uint32_t result = 0; | ||||
37 | result |= std::stoi(part.substr(0, 2), 0, 16) << 24; | ||||
38 | result |= std::stoi(part.substr(2, 2), 0, 16) << 16; | ||||
39 | result |= std::stoi(part.substr(4, 2), 0, 16) << 8; | ||||
40 | result |= std::stoi(part.substr(6, 2), 0, 16) << 0; | ||||
41 | return result; | ||||
42 | } | ||||
43 | | ||||
44 | ConnectionMapping::ConnectionMapping() | ||||
45 | { | ||||
46 | m_socketFileMatch = | ||||
47 | // Format of /proc/net/tcp is: | ||||
48 | // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode | ||||
49 | // 0: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 31896 ... | ||||
50 | // Where local_address is a hex representation of the IP Address and port, in big endian notation. | ||||
51 | // Since we care only about local address, local port and inode we ignore the middle 70 characters. | ||||
52 | std::regex("\\s*\\d+: (?:(\\w{8})|(\\w{32})):([A-F0-9]{4}) (.{94}|.{70}) (\\d+) .*", std::regex::ECMAScript | std::regex::optimize); | ||||
53 | | ||||
54 | parseProc(); | ||||
55 | } | ||||
56 | | ||||
57 | ConnectionMapping::PacketResult ConnectionMapping::pidForPacket(const Packet &packet) | ||||
58 | { | ||||
59 | PacketResult result; | ||||
60 | | ||||
61 | auto sourceInode = m_localToINode.find(packet.sourceAddress()); | ||||
62 | auto destInode = m_localToINode.find(packet.destinationAddress()); | ||||
63 | | ||||
64 | if (sourceInode == m_localToINode.end() && destInode == m_localToINode.end()) { | ||||
65 | parseProc(); | ||||
66 | | ||||
67 | sourceInode = m_localToINode.find(packet.sourceAddress()); | ||||
68 | destInode = m_localToINode.find(packet.destinationAddress()); | ||||
69 | | ||||
70 | if (sourceInode == m_localToINode.end() && destInode == m_localToINode.end()) { | ||||
71 | return result; | ||||
72 | } | ||||
73 | } | ||||
74 | | ||||
75 | auto inode = m_localToINode.end(); | ||||
76 | if (sourceInode != m_localToINode.end()) { | ||||
77 | result.direction = Packet::Direction::Outbound; | ||||
78 | inode = sourceInode; | ||||
79 | } else { | ||||
80 | result.direction = Packet::Direction::Inbound; | ||||
81 | inode = destInode; | ||||
82 | } | ||||
83 | | ||||
84 | auto pid = m_inodeToPid.find((*inode).second); | ||||
85 | if (pid == m_inodeToPid.end()) { | ||||
86 | result.pid = -1; | ||||
87 | } else { | ||||
88 | result.pid = (*pid).second; | ||||
89 | } | ||||
90 | return result; | ||||
91 | } | ||||
92 | | ||||
93 | void ConnectionMapping::parseProc() | ||||
94 | { | ||||
95 | //TODO: Consider using INET_DIAG netlink protocol for retrieving socket information. | ||||
96 | if (parseSockets()) | ||||
97 | parsePid(); | ||||
98 | } | ||||
99 | | ||||
100 | bool ConnectionMapping::parseSockets() | ||||
101 | { | ||||
102 | auto oldInodes = m_inodes; | ||||
103 | | ||||
104 | m_inodes.clear(); | ||||
105 | m_localToINode.clear(); | ||||
106 | parseSocketFile("/proc/net/tcp"); | ||||
107 | parseSocketFile("/proc/net/udp"); | ||||
108 | parseSocketFile("/proc/net/tcp6"); | ||||
109 | parseSocketFile("/proc/net/udp6"); | ||||
110 | | ||||
111 | if (m_inodes == oldInodes) { | ||||
112 | return false; | ||||
113 | } | ||||
114 | | ||||
115 | return true; | ||||
116 | } | ||||
117 | | ||||
118 | void ConnectionMapping::parsePid() | ||||
119 | { | ||||
120 | std::unordered_set<int> pids; | ||||
121 | | ||||
122 | auto dir = opendir("/proc"); | ||||
123 | dirent *entry = nullptr; | ||||
124 | while ((entry = readdir(dir))) | ||||
125 | if (entry->d_name[0] >= '0' && entry->d_name[0] <= '9') | ||||
126 | pids.insert(std::stoi(entry->d_name)); | ||||
127 | closedir(dir); | ||||
128 | | ||||
129 | char buffer[100] = { "\0" }; | ||||
130 | m_inodeToPid.clear(); | ||||
131 | for (auto pid : pids) { | ||||
132 | auto fdPath = "/proc/%/fd"s.replace(6, 1, std::to_string(pid)); | ||||
133 | auto dir = opendir(fdPath.data()); | ||||
134 | if (dir == NULL) { | ||||
135 | continue; | ||||
136 | } | ||||
137 | | ||||
138 | dirent *fd = nullptr; | ||||
139 | while ((fd = readdir(dir))) { | ||||
140 | memset(buffer, 0, 100); | ||||
141 | readlinkat(dirfd(dir), fd->d_name, buffer, 100); | ||||
142 | auto target = std::string(buffer); | ||||
143 | if (target.find("socket:") == std::string::npos) | ||||
144 | continue; | ||||
145 | | ||||
146 | auto inode = std::stoi(target.substr(8)); | ||||
147 | m_inodeToPid.insert(std::make_pair(inode, pid)); | ||||
148 | } | ||||
149 | | ||||
150 | closedir(dir); | ||||
151 | } | ||||
152 | } | ||||
153 | | ||||
154 | void ConnectionMapping::parseSocketFile(const char *fileName) | ||||
155 | { | ||||
156 | std::ifstream file { fileName }; | ||||
157 | if (!file.is_open()) | ||||
158 | return; | ||||
159 | | ||||
160 | std::string data; | ||||
161 | while (std::getline(file, data)) { | ||||
162 | std::smatch match; | ||||
163 | if (!std::regex_match(data, match, m_socketFileMatch)) { | ||||
164 | continue; | ||||
165 | } | ||||
166 | | ||||
167 | Packet::Address localAddress; | ||||
168 | if (!match.str(1).empty()) { | ||||
169 | localAddress.address[3] = tcpToInt(match.str(1)); | ||||
170 | } else { | ||||
171 | auto ipv6 = match.str(2); | ||||
172 | if (ipv6.compare(0, 24, "0000000000000000FFFF0000")) { | ||||
173 | // Some applications (like Steam) use ipv6 sockets with ipv4. | ||||
174 | // This results in ipv4 addresses that end up in the tcp6 file. | ||||
175 | // They seem to start with 0000000000000000FFFF0000, so if we | ||||
176 | // detect that, assume it is ipv4-over-ipv6. | ||||
177 | localAddress.address[3] = tcpToInt(ipv6.substr(24,8)); | ||||
178 | } else { | ||||
179 | localAddress.address[0] = tcpToInt(ipv6.substr(0, 8)); | ||||
180 | localAddress.address[1] = tcpToInt(ipv6.substr(8, 8)); | ||||
181 | localAddress.address[2] = tcpToInt(ipv6.substr(16, 8)); | ||||
182 | localAddress.address[3] = tcpToInt(ipv6.substr(24, 8)); | ||||
183 | } | ||||
184 | } | ||||
185 | | ||||
186 | localAddress.port = std::stoi(match.str(3), 0, 16); | ||||
187 | auto inode = std::stoi(match.str(5)); | ||||
188 | m_localToINode.insert(std::make_pair(localAddress, inode)); | ||||
189 | m_inodes.insert(inode); | ||||
190 | } | ||||
191 | } |