@cryptotaxi247 / netdata-1 / commits / 919dbe175

Network Viewer (local-sockets version) (#16872)

* network viewer based on local-sockets * added more fields for the UI * added socket state * added inodes to the lists

Costa Tsaousis committed Jan 30, 2024 at 11:28 UTC 919dbe175e0d811a2091992dfc24d88b8e91a944
11 files changed +652 -60
CMakeLists.txt
+19 -2
@@ -102,6 +102,7 @@ option(ENABLE_PLUGIN_CUPS "enable cups.plugin" True)
102 option(ENABLE_PLUGIN_CGROUP_NETWORK "enable cgroup-network plugin" True)
103 option(ENABLE_PLUGIN_EBPF "enable ebpf.plugin" True)
104 option(ENABLE_PLUGIN_LOCAL_LISTENERS "enable local-listeners" True)
105 +option(ENABLE_PLUGIN_NETWORK_VIEWER "enable network-viewer" True)
106 option(ENABLE_PLUGIN_SYSTEMD_JOURNAL "enable systemd-journal.plugin" True)
107 option(ENABLE_PLUGIN_LOGS_MANAGEMENT "enable logs-management.plugin" True)
108
@@ -1947,8 +1948,10 @@ if(ENABLE_PLUGIN_EBPF)
1948 endif()
1949
1950 if(ENABLE_PLUGIN_LOCAL_LISTENERS)
1950 - set(LOCAL_LISTENERS_FILES collectors/plugins.d/local_listeners.c
1951 - collectors/plugins.d/local-sockets.h)
1951 + set(LOCAL_LISTENERS_FILES
1952 + collectors/plugins.d/local_listeners.c
1953 + collectors/plugins.d/local-sockets.h
1954 + )
1955
1956 add_executable(local-listeners ${LOCAL_LISTENERS_FILES})
1957 target_link_libraries(local-listeners libnetdata)
@@ -1958,6 +1961,20 @@ if(ENABLE_PLUGIN_LOCAL_LISTENERS)
1961 DESTINATION usr/libexec/netdata/plugins.d)
1962 endif()
1963
1964 +if(ENABLE_PLUGIN_NETWORK_VIEWER)
1965 + set(NETWORK_VIEWER_FILES
1966 + collectors/plugins.d/local-sockets.h
1967 + collectors/network-viewer.plugin/network-viewer.c
1968 + )
1969 +
1970 + add_executable(network-viewer.plugin ${NETWORK_VIEWER_FILES})
1971 + target_link_libraries(network-viewer.plugin libnetdata)
1972 +
1973 + install(TARGETS network-viewer.plugin
1974 + COMPONENT network_viewer_plugin
1975 + DESTINATION usr/libexec/netdata/plugins.d)
1976 +endif()
1977 +
1978 #
1979 # exporters
1980 #
collectors/network-viewer.plugin/network-viewer.c new
+482
@@ -0,0 +1,482 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "collectors/all.h"
4 +#include "libnetdata/libnetdata.h"
5 +#include "libnetdata/required_dummies.h"
6 +#include "collectors/plugins.d/local-sockets.h"
7 +
8 +#define NETWORK_VIEWER_FUNCTION "network-viewer"
9 +#define NETWORK_VIEWER_HELP "Network dependencies (outbound connections)"
10 +
11 +netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
12 +static bool plugin_should_exit = false;
13 +
14 +ENUM_STR_MAP_DEFINE(SOCKET_DIRECTION) = {
15 + { .id = SOCKET_DIRECTION_LISTEN, .name = "listen" },
16 + { .id = SOCKET_DIRECTION_LOCAL, .name = "local" },
17 + { .id = SOCKET_DIRECTION_INBOUND, .name = "inbound" },
18 + { .id = SOCKET_DIRECTION_OUTBOUND, .name = "outbound" },
19 +
20 + // terminator
21 + { . id = 0, .name = NULL }
22 +};
23 +ENUM_STR_DEFINE_FUNCTIONS(SOCKET_DIRECTION, SOCKET_DIRECTION_LISTEN, "unknown");
24 +
25 +typedef int TCP_STATE;
26 +ENUM_STR_MAP_DEFINE(TCP_STATE) = {
27 + { .id = TCP_ESTABLISHED, .name = "established" },
28 + { .id = TCP_SYN_SENT, .name = "syn-sent" },
29 + { .id = TCP_SYN_RECV, .name = "syn-received" },
30 + { .id = TCP_FIN_WAIT1, .name = "fin1-wait1" },
31 + { .id = TCP_FIN_WAIT2, .name = "fin1-wait2" },
32 + { .id = TCP_TIME_WAIT, .name = "time-wait" },
33 + { .id = TCP_CLOSE, .name = "close" },
34 + { .id = TCP_CLOSE_WAIT, .name = "close-wait" },
35 + { .id = TCP_LAST_ACK, .name = "last-ack" },
36 + { .id = TCP_LISTEN, .name = "listen" },
37 + { .id = TCP_CLOSING, .name = "closing" },
38 +
39 + // terminator
40 + { . id = 0, .name = NULL }
41 +};
42 +ENUM_STR_DEFINE_FUNCTIONS(TCP_STATE, 0, "unknown");
43 +
44 +
45 +static void local_socket_to_array(struct local_socket_state *ls, struct local_socket *n, void *data) {
46 + BUFFER *wb = data;
47 +
48 + char local_address[INET6_ADDRSTRLEN];
49 + char remote_address[INET6_ADDRSTRLEN];
50 + char *protocol;
51 +
52 + if(n->local.family == AF_INET) {
53 + ipv4_address_to_txt(n->local.ip.ipv4, local_address);
54 + ipv4_address_to_txt(n->remote.ip.ipv4, remote_address);
55 + protocol = n->local.protocol == IPPROTO_TCP ? "tcp4" : "udp4";
56 + }
57 + else if(n->local.family == AF_INET6) {
58 + ipv6_address_to_txt(&n->local.ip.ipv6, local_address);
59 + ipv6_address_to_txt(&n->remote.ip.ipv6, remote_address);
60 + protocol = n->local.protocol == IPPROTO_TCP ? "tcp6" : "udp6";
61 + }
62 + else
63 + return;
64 +
65 + const char *type;
66 + if(n->net_ns_inode == ls->proc_self_net_ns_inode)
67 + type = "system";
68 + else
69 + type = "container";
70 +
71 + buffer_json_add_array_item_array(wb);
72 + {
73 + buffer_json_add_array_item_string(wb, SOCKET_DIRECTION_2str(n->direction));
74 + buffer_json_add_array_item_string(wb, protocol);
75 + buffer_json_add_array_item_string(wb, type); // system or container
76 + if(n->local.protocol == IPPROTO_TCP)
77 + buffer_json_add_array_item_string(wb, TCP_STATE_2str(n->state));
78 + else
79 + buffer_json_add_array_item_string(wb, "stateless");
80 + buffer_json_add_array_item_uint64(wb, n->pid);
81 + buffer_json_add_array_item_string(wb, n->comm);
82 + buffer_json_add_array_item_string(wb, n->cmdline);
83 + buffer_json_add_array_item_string(wb, local_address);
84 + buffer_json_add_array_item_uint64(wb, n->local.port);
85 + buffer_json_add_array_item_string(wb, local_sockets_address_space(&n->local));
86 + buffer_json_add_array_item_string(wb, remote_address);
87 + buffer_json_add_array_item_uint64(wb, n->remote.port);
88 + buffer_json_add_array_item_string(wb, local_sockets_address_space(&n->remote));
89 + buffer_json_add_array_item_uint64(wb, n->inode);
90 + buffer_json_add_array_item_uint64(wb, n->net_ns_inode);
91 + buffer_json_add_array_item_uint64(wb, 1); // count
92 + }
93 + buffer_json_array_close(wb);
94 +}
95 +
96 +void network_viewer_function(const char *transaction, char *function __maybe_unused, usec_t *stop_monotonic_ut __maybe_unused,
97 + bool *cancelled __maybe_unused, BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
98 + const char *source __maybe_unused, void *data __maybe_unused) {
99 +
100 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
101 + buffer_flush(wb);
102 + wb->content_type = CT_APPLICATION_JSON;
103 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
104 +
105 + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
106 + buffer_json_member_add_string(wb, "type", "table");
107 + buffer_json_member_add_time_t(wb, "update_every", 1);
108 + buffer_json_member_add_string(wb, "help", NETWORK_VIEWER_HELP);
109 + buffer_json_member_add_array(wb, "data");
110 +
111 + LS_STATE ls = {
112 + .config = {
113 + .listening = true,
114 + .inbound = true,
115 + .outbound = true,
116 + .local = true,
117 + .tcp4 = true,
118 + .tcp6 = true,
119 + .udp4 = true,
120 + .udp6 = true,
121 + .pid = true,
122 + .cmdline = true,
123 + .comm = true,
124 + .namespaces = true,
125 +
126 + .max_errors = 10,
127 +
128 + .cb = local_socket_to_array,
129 + .data = wb,
130 + },
131 + .stats = { 0 },
132 + .sockets_hashtable = { 0 },
133 + .local_ips_hashtable = { 0 },
134 + .listening_ports_hashtable = { 0 },
135 + };
136 +
137 + local_sockets_process(&ls);
138 +
139 + buffer_json_array_close(wb);
140 + buffer_json_member_add_object(wb, "columns");
141 + {
142 + size_t field_id = 0;
143 +
144 + // Direction
145 + buffer_rrdf_table_add_field(wb, field_id++, "Direction", "Socket Direction",
146 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
147 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
148 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
149 + RRDF_FIELD_OPTS_VISIBLE,
150 + NULL);
151 +
152 + // Protocol
153 + buffer_rrdf_table_add_field(wb, field_id++, "Protocol", "Socket Protocol",
154 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
155 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
156 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
157 + RRDF_FIELD_OPTS_VISIBLE,
158 + NULL);
159 +
160 + // Type
161 + buffer_rrdf_table_add_field(wb, field_id++, "Namespace", "Namespace",
162 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
163 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
164 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
165 + RRDF_FIELD_OPTS_VISIBLE,
166 + NULL);
167 +
168 + // State
169 + buffer_rrdf_table_add_field(wb, field_id++, "State", "Socket State",
170 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
171 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
172 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
173 + RRDF_FIELD_OPTS_VISIBLE,
174 + NULL);
175 +
176 + // Pid
177 + buffer_rrdf_table_add_field(wb, field_id++, "PID", "Process ID",
178 + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
179 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
180 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
181 + RRDF_FIELD_OPTS_VISIBLE,
182 + NULL);
183 +
184 + // Comm
185 + buffer_rrdf_table_add_field(wb, field_id++, "Process", "Process Name",
186 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
187 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
188 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
189 + RRDF_FIELD_OPTS_VISIBLE|RRDF_FIELD_OPTS_FULL_WIDTH,
190 + NULL);
191 +
192 + // Cmdline
193 + buffer_rrdf_table_add_field(wb, field_id++, "CommandLine", "Command Line",
194 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
195 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
196 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
197 + RRDF_FIELD_OPTS_NONE|RRDF_FIELD_OPTS_FULL_WIDTH,
198 + NULL);
199 +
200 + // Local Address
201 + buffer_rrdf_table_add_field(wb, field_id++, "LocalIP", "Local IP Address",
202 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
203 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
204 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
205 + RRDF_FIELD_OPTS_VISIBLE|RRDF_FIELD_OPTS_FULL_WIDTH,
206 + NULL);
207 +
208 + // Local Port
209 + buffer_rrdf_table_add_field(wb, field_id++, "LocalPort", "Local Port",
210 + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
211 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
212 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
213 + RRDF_FIELD_OPTS_VISIBLE,
214 + NULL);
215 +
216 + // Local Address Space
217 + buffer_rrdf_table_add_field(wb, field_id++, "LocalAddressSpace", "Local IP Address Space",
218 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
219 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
220 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
221 + RRDF_FIELD_OPTS_NONE,
222 + NULL);
223 +
224 + // Remote Address
225 + buffer_rrdf_table_add_field(wb, field_id++, "RemoteIP", "Remote IP Address",
226 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
227 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
228 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
229 + RRDF_FIELD_OPTS_VISIBLE|RRDF_FIELD_OPTS_FULL_WIDTH,
230 + NULL);
231 +
232 + // Remote Port
233 + buffer_rrdf_table_add_field(wb, field_id++, "RemotePort", "Remote Port",
234 + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
235 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
236 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
237 + RRDF_FIELD_OPTS_VISIBLE,
238 + NULL);
239 +
240 + // Remote Address Space
241 + buffer_rrdf_table_add_field(wb, field_id++, "RemoteAddressSpace", "Remote IP Address Space",
242 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
243 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
244 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
245 + RRDF_FIELD_OPTS_NONE,
246 + NULL);
247 +
248 + // inode
249 + buffer_rrdf_table_add_field(wb, field_id++, "Inode", "Socket Inode",
250 + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
251 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
252 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
253 + RRDF_FIELD_OPTS_NONE,
254 + NULL);
255 +
256 + // Namespace inode
257 + buffer_rrdf_table_add_field(wb, field_id++, "Namespace Inode", "Namespace Inode",
258 + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
259 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
260 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
261 + RRDF_FIELD_OPTS_NONE,
262 + NULL);
263 +
264 + // Count
265 + buffer_rrdf_table_add_field(wb, field_id++, "Count", "Count",
266 + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
267 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
268 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
269 + RRDF_FIELD_OPTS_NONE,
270 + NULL);
271 + }
272 + buffer_json_object_close(wb); // columns
273 + buffer_json_member_add_string(wb, "default_sort_column", "Direction");
274 +
275 + buffer_json_member_add_object(wb, "charts");
276 + {
277 + // Data Collection Age chart
278 + buffer_json_member_add_object(wb, "Count");
279 + {
280 + buffer_json_member_add_string(wb, "name", "Connections");
281 + buffer_json_member_add_string(wb, "type", "stacked-bar");
282 + buffer_json_member_add_array(wb, "columns");
283 + {
284 + buffer_json_add_array_item_string(wb, "Direction");
285 + }
286 + buffer_json_array_close(wb);
287 + }
288 + buffer_json_object_close(wb);
289 +
290 + // Streaming Age chart
291 + buffer_json_member_add_object(wb, "Count");
292 + {
293 + buffer_json_member_add_string(wb, "name", "Connections");
294 + buffer_json_member_add_string(wb, "type", "stacked-bar");
295 + buffer_json_member_add_array(wb, "columns");
296 + {
297 + buffer_json_add_array_item_string(wb, "Process");
298 + }
299 + buffer_json_array_close(wb);
300 + }
301 + buffer_json_object_close(wb);
302 +
303 + // DB Duration
304 + buffer_json_member_add_object(wb, "Count");
305 + {
306 + buffer_json_member_add_string(wb, "name", "Connections");
307 + buffer_json_member_add_string(wb, "type", "stacked-bar");
308 + buffer_json_member_add_array(wb, "columns");
309 + {
310 + buffer_json_add_array_item_string(wb, "Protocol");
311 + }
312 + buffer_json_array_close(wb);
313 + }
314 + buffer_json_object_close(wb);
315 + }
316 + buffer_json_object_close(wb); // charts
317 +
318 + buffer_json_member_add_array(wb, "default_charts");
319 + {
320 + buffer_json_add_array_item_array(wb);
321 + buffer_json_add_array_item_string(wb, "Count");
322 + buffer_json_add_array_item_string(wb, "Direction");
323 + buffer_json_array_close(wb);
324 +
325 + buffer_json_add_array_item_array(wb);
326 + buffer_json_add_array_item_string(wb, "Count");
327 + buffer_json_add_array_item_string(wb, "Process");
328 + buffer_json_array_close(wb);
329 + }
330 + buffer_json_array_close(wb);
331 +
332 + buffer_json_member_add_object(wb, "group_by");
333 + {
334 + buffer_json_member_add_object(wb, "Direction");
335 + {
336 + buffer_json_member_add_string(wb, "name", "Direction");
337 + buffer_json_member_add_array(wb, "columns");
338 + {
339 + buffer_json_add_array_item_string(wb, "Direction");
340 + }
341 + buffer_json_array_close(wb);
342 + }
343 + buffer_json_object_close(wb);
344 +
345 + buffer_json_member_add_object(wb, "Protocol");
346 + {
347 + buffer_json_member_add_string(wb, "name", "Protocol");
348 + buffer_json_member_add_array(wb, "columns");
349 + {
350 + buffer_json_add_array_item_string(wb, "Protocol");
351 + }
352 + buffer_json_array_close(wb);
353 + }
354 + buffer_json_object_close(wb);
355 +
356 + buffer_json_member_add_object(wb, "Namespace");
357 + {
358 + buffer_json_member_add_string(wb, "name", "Namespace");
359 + buffer_json_member_add_array(wb, "columns");
360 + {
361 + buffer_json_add_array_item_string(wb, "Namespace");
362 + }
363 + buffer_json_array_close(wb);
364 + }
365 + buffer_json_object_close(wb);
366 +
367 + buffer_json_member_add_object(wb, "Process");
368 + {
369 + buffer_json_member_add_string(wb, "name", "Process");
370 + buffer_json_member_add_array(wb, "columns");
371 + {
372 + buffer_json_add_array_item_string(wb, "Process");
373 + }
374 + buffer_json_array_close(wb);
375 + }
376 + buffer_json_object_close(wb);
377 +
378 + buffer_json_member_add_object(wb, "LocalIP");
379 + {
380 + buffer_json_member_add_string(wb, "name", "Local IP");
381 + buffer_json_member_add_array(wb, "columns");
382 + {
383 + buffer_json_add_array_item_string(wb, "LocalIP");
384 + }
385 + buffer_json_array_close(wb);
386 + }
387 + buffer_json_object_close(wb);
388 +
389 + buffer_json_member_add_object(wb, "LocalPort");
390 + {
391 + buffer_json_member_add_string(wb, "name", "Local Port");
392 + buffer_json_member_add_array(wb, "columns");
393 + {
394 + buffer_json_add_array_item_string(wb, "LocalPort");
395 + }
396 + buffer_json_array_close(wb);
397 + }
398 + buffer_json_object_close(wb);
399 +
400 + buffer_json_member_add_object(wb, "RemoteIP");
401 + {
402 + buffer_json_member_add_string(wb, "name", "Remote IP");
403 + buffer_json_member_add_array(wb, "columns");
404 + {
405 + buffer_json_add_array_item_string(wb, "RemoteIP");
406 + }
407 + buffer_json_array_close(wb);
408 + }
409 + buffer_json_object_close(wb);
410 +
411 + buffer_json_member_add_object(wb, "RemotePort");
412 + {
413 + buffer_json_member_add_string(wb, "name", "Remote Port");
414 + buffer_json_member_add_array(wb, "columns");
415 + {
416 + buffer_json_add_array_item_string(wb, "RemotePort");
417 + }
418 + buffer_json_array_close(wb);
419 + }
420 + buffer_json_object_close(wb);
421 + }
422 + buffer_json_object_close(wb); // group_by
423 +
424 +
425 + buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + 1);
426 + buffer_json_finalize(wb);
427 +
428 + netdata_mutex_lock(&stdout_mutex);
429 + pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "application/json", now_realtime_sec(), wb);
430 + netdata_mutex_unlock(&stdout_mutex);
431 +}
432 +
433 +// ----------------------------------------------------------------------------------------------------------------
434 +// main
435 +
436 +int main(int argc __maybe_unused, char **argv __maybe_unused) {
437 + clocks_init();
438 + netdata_thread_set_tag("NETWORK-VIEWER");
439 + nd_log_initialize_for_external_plugins("network-viewer.plugin");
440 +
441 + netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
442 + if(verify_netdata_host_prefix(true) == -1) exit(1);
443 +
444 + // ----------------------------------------------------------------------------------------------------------------
445 +
446 + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n",
447 + NETWORK_VIEWER_FUNCTION, 60, NETWORK_VIEWER_HELP,
448 + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA),
449 + RRDFUNCTIONS_PRIORITY_DEFAULT);
450 +
451 + // ----------------------------------------------------------------------------------------------------------------
452 +
453 + struct functions_evloop_globals *wg =
454 + functions_evloop_init(5, "Network-Viewer", &stdout_mutex, &plugin_should_exit);
455 +
456 + functions_evloop_add_function(wg,
457 + NETWORK_VIEWER_FUNCTION,
458 + network_viewer_function,
459 + PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT,
460 + NULL);
461 +
462 + // ----------------------------------------------------------------------------------------------------------------
463 +
464 + usec_t step_ut = 100 * USEC_PER_MS;
465 + usec_t send_newline_ut = 0;
466 + bool tty = isatty(fileno(stdout)) == 1;
467 +
468 + heartbeat_t hb;
469 + heartbeat_init(&hb);
470 + while(!plugin_should_exit) {
471 +
472 + usec_t dt_ut = heartbeat_next(&hb, step_ut);
473 + send_newline_ut += dt_ut;
474 +
475 + if(!tty && send_newline_ut > USEC_PER_SEC) {
476 + send_newline_and_flush(&stdout_mutex);
477 + send_newline_ut = 0;
478 + }
479 + }
480 +
481 + return 0;
482 +}
collectors/plugins.d/local-sockets.h
+119 -40
@@ -96,6 +96,7 @@ typedef struct local_socket_state {
96 // --------------------------------------------------------------------------------------------------------------------
97
98 typedef enum __attribute__((packed)) {
99 + SOCKET_DIRECTION_NONE = 0,
100 SOCKET_DIRECTION_LISTEN = (1 << 0), // a listening socket
101 SOCKET_DIRECTION_INBOUND = (1 << 1), // an inbound socket connecting a remote system to a local listening socket
102 SOCKET_DIRECTION_OUTBOUND = (1 << 2), // a socket initiated by this system, connecting to another system
@@ -114,11 +115,6 @@ struct pid_socket {
115 char comm[TASK_COMM_LEN];
116 };
117
117 -union ipv46 {
118 - uint32_t ipv4;
119 - struct in6_addr ipv6;
120 -};
121 -
118 struct local_port {
119 uint16_t protocol;
120 uint16_t family;
@@ -126,7 +122,14 @@ struct local_port {
122 uint64_t net_ns_inode;
123 };
124
125 +union ipv46 {
126 + uint32_t ipv4;
127 + struct in6_addr ipv6;
128 +};
129 +
130 struct socket_endpoint {
131 + uint16_t protocol;
132 + uint16_t family;
133 uint16_t port;
134 union ipv46 ip;
135 };
@@ -145,8 +148,6 @@ typedef struct local_socket {
148 uint64_t inode;
149 uint64_t net_ns_inode;
150
148 - uint16_t protocol;
149 - uint16_t family;
151 int state;
152 struct socket_endpoint local;
153 struct socket_endpoint remote;
@@ -218,7 +219,7 @@ static inline void local_sockets_fix_cmdline(char* str) {
219 }
220 }
221
221 -// ----------------------------------------------------------------------------
222 +// --------------------------------------------------------------------------------------------------------------------
223
224 static inline bool
225 local_sockets_read_proc_inode_link(LS_STATE *ls, const char *filename, uint64_t *inode, const char *type) {
@@ -368,7 +369,7 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch
369 return true;
370 }
371
371 -// ----------------------------------------------------------------------------
372 +// --------------------------------------------------------------------------------------------------------------------
373
374 static bool local_sockets_is_ipv4_mapped_ipv6_address(const struct in6_addr *addr) {
375 // An IPv4-mapped IPv6 address starts with 80 bits of zeros followed by 16 bits of ones
@@ -376,42 +377,117 @@ static bool local_sockets_is_ipv4_mapped_ipv6_address(const struct in6_addr *add
377 return memcmp(addr->s6_addr, ipv4_mapped_prefix, 12) == 0;
378 }
379
379 -static bool local_sockets_is_loopback_address(const void *ip, uint16_t family) {
380 - if (family == AF_INET) {
380 +static bool local_sockets_is_loopback_address(struct socket_endpoint *se) {
381 + if (se->family == AF_INET) {
382 // For IPv4, loopback addresses are in the 127.0.0.0/8 range
382 - const uint32_t addr = ntohl(*((const uint32_t *)ip)); // Convert to host byte order for comparison
383 - return (addr >> 24) == 127; // Check if the first byte is 127
384 - } else if (family == AF_INET6) {
383 + return (ntohl(se->ip.ipv4) >> 24) == 127; // Check if the first byte is 127
384 + } else if (se->family == AF_INET6) {
385 // Check if the address is an IPv4-mapped IPv6 address
386 - const struct in6_addr *ipv6_addr = (const struct in6_addr *)ip;
387 - if (local_sockets_is_ipv4_mapped_ipv6_address(ipv6_addr)) {
386 + if (local_sockets_is_ipv4_mapped_ipv6_address(&se->ip.ipv6)) {
387 // Extract the last 32 bits (IPv4 address) and check if it's in the 127.0.0.0/8 range
389 - const uint32_t ipv4_addr = ntohl(*((const uint32_t *)(ipv6_addr->s6_addr + 12)));
390 - return (ipv4_addr >> 24) == 127;
388 + uint8_t *ip6 = (uint8_t *)&se->ip.ipv6;
389 + const uint32_t ipv4_addr = *((const uint32_t *)(ip6 + 12));
390 + return (ntohl(ipv4_addr) >> 24) == 127;
391 }
392
393 // For IPv6, loopback address is ::1
394 - const struct in6_addr loopback_ipv6 = IN6ADDR_LOOPBACK_INIT;
395 - return memcmp(ipv6_addr, &loopback_ipv6, sizeof(struct in6_addr)) == 0;
394 + return memcmp(&se->ip.ipv6, &in6addr_loopback, sizeof(se->ip.ipv6)) == 0;
395 }
396
397 return false;
398 }
399
401 -static bool local_sockets_is_zero_address(const void *ip, uint16_t family) {
402 - if (family == AF_INET) {
403 - // For IPv4, check if the address is not 0.0.0.0
404 - const uint32_t zero_ipv4 = 0; // Zero address in network byte order
405 - return memcmp(ip, &zero_ipv4, sizeof(uint32_t)) == 0;
406 - } else if (family == AF_INET6) {
407 - // For IPv6, check if the address is not ::
408 - const struct in6_addr zero_ipv6 = IN6ADDR_ANY_INIT;
409 - return memcmp(ip, &zero_ipv6, sizeof(struct in6_addr)) == 0;
400 +static inline bool local_sockets_is_ipv4_reserved_address(uint32_t ip) {
401 + // Check for the reserved address ranges
402 + ip = ntohl(ip);
403 + return (
404 + (ip >> 24 == 10) || // Private IP range (A class)
405 + (ip >> 20 == (172 << 4) + 1) || // Private IP range (B class)
406 + (ip >> 16 == (192 << 8) + 168) || // Private IP range (C class)
407 + (ip >> 24 == 127) || // Loopback address (127.0.0.0)
408 + (ip >> 24 == 0) || // Reserved (0.0.0.0)
409 + (ip >> 24 == 169 && (ip >> 16) == 254) || // Link-local address (169.254.0.0)
410 + (ip >> 16 == (192 << 8) + 0) // Test-Net (192.0.0.0)
411 + );
412 +}
413 +
414 +static inline bool local_sockets_is_private_address(struct socket_endpoint *se) {
415 + if (se->family == AF_INET) {
416 + return local_sockets_is_ipv4_reserved_address(se->ip.ipv4);
417 + }
418 + else if (se->family == AF_INET6) {
419 + uint8_t *ip6 = (uint8_t *)&se->ip.ipv6;
420 +
421 + // Check if the address is an IPv4-mapped IPv6 address
422 + if (local_sockets_is_ipv4_mapped_ipv6_address(&se->ip.ipv6)) {
423 + // Extract the last 32 bits (IPv4 address) and check if it's in the 127.0.0.0/8 range
424 + const uint32_t ipv4_addr = *((const uint32_t *)(ip6 + 12));
425 + return local_sockets_is_ipv4_reserved_address(ipv4_addr);
426 + }
427 +
428 + // Check for link-local addresses (fe80::/10)
429 + if ((ip6[0] == 0xFE) && ((ip6[1] & 0xC0) == 0x80))
430 + return true;
431 +
432 + // Check for Unique Local Addresses (ULA) (fc00::/7)
433 + if ((ip6[0] & 0xFE) == 0xFC)
434 + return true;
435 +
436 + // Check for multicast addresses (ff00::/8)
437 + if (ip6[0] == 0xFF)
438 + return true;
439 +
440 + // For IPv6, loopback address is :: or ::1
441 + return memcmp(&se->ip.ipv6, &in6addr_any, sizeof(se->ip.ipv6)) == 0 ||
442 + memcmp(&se->ip.ipv6, &in6addr_loopback, sizeof(se->ip.ipv6)) == 0;
443 + }
444 +
445 + return false;
446 +}
447 +
448 +static bool local_sockets_is_multicast_address(struct socket_endpoint *se) {
449 + if (se->family == AF_INET) {
450 + // For IPv4, check if the address is 0.0.0.0
451 + uint32_t ip = htonl(se->ip.ipv4);
452 + return (ip >= 0xE0000000 && ip <= 0xEFFFFFFF); // Multicast address range (224.0.0.0/4)
453 + }
454 + else if (se->family == AF_INET6) {
455 + // For IPv6, check if the address is ff00::/8
456 + uint8_t *ip6 = (uint8_t *)&se->ip.ipv6;
457 + return ip6[0] == 0xff;
458 + }
459 +
460 + return false;
461 +}
462 +
463 +static bool local_sockets_is_zero_address(struct socket_endpoint *se) {
464 + if (se->family == AF_INET) {
465 + // For IPv4, check if the address is 0.0.0.0
466 + return se->ip.ipv4 == 0;
467 + }
468 + else if (se->family == AF_INET6) {
469 + // For IPv6, check if the address is ::
470 + return memcmp(&se->ip.ipv6, &in6addr_any, sizeof(se->ip.ipv6)) == 0;
471 }
472
473 return false;
474 }
475
476 +static inline const char *local_sockets_address_space(struct socket_endpoint *se) {
477 + if(local_sockets_is_zero_address(se))
478 + return "zero";
479 + else if(local_sockets_is_loopback_address(se))
480 + return "loopback";
481 + else if(local_sockets_is_multicast_address(se))
482 + return "multicast";
483 + else if(local_sockets_is_private_address(se))
484 + return "private";
485 + else
486 + return "public";
487 +}
488 +
489 +// --------------------------------------------------------------------------------------------------------------------
490 +
491 static inline void local_sockets_index_listening_port(LS_STATE *ls, LOCAL_SOCKET *n) {
492 if(n->direction & SOCKET_DIRECTION_LISTEN) {
493 // for the listening sockets, keep a hashtable with all the local ports
@@ -500,17 +576,20 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen
576 }
577
578 n->direction = 0;
503 - n->protocol = protocol;
504 - n->family = family;
579 n->state = (int)state;
580 n->inode = inode;
581 +
582 + n->local.family = family;
583 + n->local.protocol = protocol;
584 n->local.port = local_port;
585 +
586 + n->remote.family = family;
587 + n->remote.protocol = protocol;
588 n->remote.port = remote_port;
509 - n->protocol = protocol;
589
590 n->local_port_key.port = n->local.port;
512 - n->local_port_key.family = n->family;
513 - n->local_port_key.protocol = n->protocol;
591 + n->local_port_key.family = family;
592 + n->local_port_key.protocol = protocol;
593 n->local_port_key.net_ns_inode = ls->proc_self_net_ns_inode;
594
595 n->local_ip_hash = XXH3_64bits(&n->local.ip, sizeof(n->local.ip));
@@ -533,7 +612,7 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen
612
613 simple_hashtable_set_slot_LOCAL_SOCKET(&ls->sockets_hashtable, sl, inode, n);
614
536 - if(!local_sockets_is_zero_address(&n->local.ip, n->family)) {
615 + if(!local_sockets_is_zero_address(&n->local)) {
616 // put all the local IPs into the local_ips hashtable
617 // so, we learn all local IPs the system has
618
@@ -547,16 +626,16 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen
626
627 // --- 1st phase for direction detection ----------------------------------------------------------------------
628
550 - if((n->protocol == IPPROTO_TCP && n->state == TCP_LISTEN) ||
551 - local_sockets_is_zero_address(&n->local.ip, n->family) ||
552 - local_sockets_is_zero_address(&n->remote.ip, n->family)) {
629 + if((n->local.protocol == IPPROTO_TCP && n->state == TCP_LISTEN) ||
630 + local_sockets_is_zero_address(&n->local) ||
631 + local_sockets_is_zero_address(&n->remote)) {
632 // the socket is either in a TCP LISTEN, or
633 // the remote address is zero
634 n->direction |= SOCKET_DIRECTION_LISTEN;
635 }
636 else if(
558 - local_sockets_is_loopback_address(&n->local.ip, n->family) ||
559 - local_sockets_is_loopback_address(&n->remote.ip, n->family)) {
637 + local_sockets_is_loopback_address(&n->local) ||
638 + local_sockets_is_loopback_address(&n->remote)) {
639 // the local IP address is loopback
640 n->direction |= SOCKET_DIRECTION_LOCAL;
641 }
collectors/plugins.d/local_listeners.c
+10 -10
@@ -6,18 +6,18 @@
6 // --------------------------------------------------------------------------------------------------------------------
7
8 static const char *protocol_name(LOCAL_SOCKET *n) {
9 - if(n->family == AF_INET) {
10 - if(n->protocol == IPPROTO_TCP)
9 + if(n->local.family == AF_INET) {
10 + if(n->local.protocol == IPPROTO_TCP)
11 return "TCP";
12 - else if(n->protocol == IPPROTO_UDP)
12 + else if(n->local.protocol == IPPROTO_UDP)
13 return "UDP";
14 else
15 return "UNKNOWN_IPV4";
16 }
17 - else if(n->family == AF_INET6) {
18 - if (n->protocol == IPPROTO_TCP)
17 + else if(n->local.family == AF_INET6) {
18 + if (n->local.protocol == IPPROTO_TCP)
19 return "TCP6";
20 - else if(n->protocol == IPPROTO_UDP)
20 + else if(n->local.protocol == IPPROTO_UDP)
21 return "UDP6";
22 else
23 return "UNKNOWN_IPV6";
@@ -30,11 +30,11 @@ static void print_local_listeners(LS_STATE *ls __maybe_unused, LOCAL_SOCKET *n,
30 char local_address[INET6_ADDRSTRLEN];
31 char remote_address[INET6_ADDRSTRLEN];
32
33 - if(n->family == AF_INET) {
33 + if(n->local.family == AF_INET) {
34 ipv4_address_to_txt(n->local.ip.ipv4, local_address);
35 ipv4_address_to_txt(n->remote.ip.ipv4, remote_address);
36 }
37 - else if(n->family == AF_INET6) {
37 + else if(n->local.family == AF_INET6) {
38 ipv6_address_to_txt(&n->local.ip.ipv6, local_address);
39 ipv6_address_to_txt(&n->remote.ip.ipv6, remote_address);
40 }
@@ -46,11 +46,11 @@ static void print_local_listeners_debug(LS_STATE *ls __maybe_unused, LOCAL_SOCKE
46 char local_address[INET6_ADDRSTRLEN];
47 char remote_address[INET6_ADDRSTRLEN];
48
49 - if(n->family == AF_INET) {
49 + if(n->local.family == AF_INET) {
50 ipv4_address_to_txt(n->local.ip.ipv4, local_address);
51 ipv4_address_to_txt(n->remote.ip.ipv4, remote_address);
52 }
53 - else if(n->family == AF_INET6) {
53 + else if(n->local.family == AF_INET6) {
54 ipv6_address_to_txt(&n->local.ip.ipv6, local_address);
55 ipv6_address_to_txt(&n->remote.ip.ipv6, remote_address);
56 }
contrib/debian/netdata.postinst
+1
@@ -44,6 +44,7 @@ case "$1" in
44 chmod 4750 /usr/libexec/netdata/plugins.d/ndsudo
45 chmod 4750 /usr/libexec/netdata/plugins.d/cgroup-network
46 chmod 4750 /usr/libexec/netdata/plugins.d/local-listeners
47 + chmod 4750 /usr/libexec/netdata/plugins.d/network-viewer.plugin
48
49 # Workaround for other plugins not installed directly by this package
50 chmod -f 4750 /usr/libexec/netdata/plugins.d/ioping || true
contrib/debian/rules
+3
@@ -274,6 +274,9 @@ override_dh_fixperms:
274 # local-listeners
275 chmod 4750 $(TOP)/usr/libexec/netdata/plugins.d/local-listeners
276
277 + # network-viewer
278 + chmod 4750 $(TOP)/usr/libexec/netdata/plugins.d/network-viewer.plugin
279 +
280 # systemd-journal
281 chmod 4750 $(TOP)-plugin-systemd-journal/usr/libexec/netdata/plugins.d/systemd-journal.plugin
282
netdata-installer.sh
+5
@@ -1416,6 +1416,11 @@ if [ "$(id -u)" -eq 0 ]; then
1416 run chmod 4750 "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/local-listeners"
1417 fi
1418
1419 + if [ -f "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/network-viewer.plugin" ]; then
1420 + run chown "root:${NETDATA_GROUP}" "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/network-viewer.plugin"
1421 + run chmod 4750 "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/network-viewer.plugin"
1422 + fi
1423 +
1424 if [ -f "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/ndsudo" ]; then
1425 run chown "root:${NETDATA_GROUP}" "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/ndsudo"
1426 run chmod 4750 "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/ndsudo"
netdata.spec.in
+3
@@ -719,6 +719,9 @@ rm -rf "${RPM_BUILD_ROOT}"
719 # local-listeners detects the local processes that are listening for connections
720 %attr(4750,root,netdata) %{_libexecdir}/%{name}/plugins.d/local-listeners
721
722 +# network-viewer.plugin, detects all system sockets and classifies them
723 +%attr(4750,root,netdata) %{_libexecdir}/%{name}/plugins.d/network-viewer.plugin
724 +
725 # ndsudo a helper to run privileged commands
726 %attr(4750,root,netdata) %{_libexecdir}/%{name}/plugins.d/ndsudo
727
packaging/docker/Dockerfile
+1
@@ -124,6 +124,7 @@ RUN addgroup --gid ${NETDATA_GID} --system "${DOCKER_GRP}" && \
124 perf.plugin \
125 ndsudo \
126 slabinfo.plugin \
127 + network-viewer.plugin \
128 systemd-journal.plugin; do \
129 [ -f "/usr/libexec/netdata/plugins.d/$name" ] && chmod 4755 "/usr/libexec/netdata/plugins.d/$name"; \
130 done && \
packaging/docker/README.md
+7 -6
@@ -34,12 +34,13 @@ along with their descriptions.
34 <details open>
35 <summary>Privileges</summary>
36
37 -| Component | Privileges | Description |
38 -|:---------------:|:-----------------------------:|--------------------------------------------------------------------------------------------------------------------------|
39 -| cgroups.plugin | host PID mode, SYS_ADMIN | Container network interfaces monitoring. Map virtual interfaces in the system namespace to interfaces inside containers. |
40 -| proc.plugin | host network mode | Host system networking stack monitoring. |
41 -| go.d.plugin | host network mode | Monitoring applications running on the host and inside containers. |
42 -| local-listeners | host network mode, SYS_PTRACE | Discovering local services/applications. Map open (listening) ports to running services/applications. |
37 +| Component | Privileges | Description |
38 +|:---------------------:|:-----------------------------:|--------------------------------------------------------------------------------------------------------------------------|
39 +| cgroups.plugin | host PID mode, SYS_ADMIN | Container network interfaces monitoring. Map virtual interfaces in the system namespace to interfaces inside containers. |
40 +| proc.plugin | host network mode | Host system networking stack monitoring. |
41 +| go.d.plugin | host network mode | Monitoring applications running on the host and inside containers. |
42 +| local-listeners | host network mode, SYS_PTRACE | Discovering local services/applications. Map open (listening) ports to running services/applications. |
43 +| network-viewer.plugin | host network mode, SYS_ADMIN | Discovering all current network sockets and building a network-map. |
44
45 </details>
46
packaging/makeself/install-or-update.sh
+2 -2
@@ -172,7 +172,7 @@ fi
172
173 progress "changing plugins ownership and permissions"
174
175 -for x in ndsudo apps.plugin perf.plugin slabinfo.plugin debugfs.plugin freeipmi.plugin ioping cgroup-network local-listeners ebpf.plugin nfacct.plugin xenstat.plugin python.d.plugin charts.d.plugin go.d.plugin ioping.plugin cgroup-network-helper.sh; do
175 +for x in ndsudo apps.plugin perf.plugin slabinfo.plugin debugfs.plugin freeipmi.plugin ioping cgroup-network local-listeners network-viewer.plugin ebpf.plugin nfacct.plugin xenstat.plugin python.d.plugin charts.d.plugin go.d.plugin ioping.plugin cgroup-network-helper.sh; do
176 f="usr/libexec/netdata/plugins.d/${x}"
177 if [ -f "${f}" ]; then
178 run chown root:${NETDATA_GROUP} "${f}"
@@ -198,7 +198,7 @@ else
198 done
199 fi
200
201 -for x in freeipmi.plugin ioping cgroup-network local-listeners ebpf.plugin nfacct.plugin xenstat.plugin; do
201 +for x in freeipmi.plugin ioping cgroup-network local-listeners network-viewer.plugin ebpf.plugin nfacct.plugin xenstat.plugin; do
202 f="usr/libexec/netdata/plugins.d/${x}"
203
204 if [ -f "${f}" ]; then