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