cgroup-network can now detect libvirt tun/tap interfaces and move them to cgroup section
Costa Tsaousis (ktsaou) committed
Oct 31, 2017 at 01:17 UTC
a65c0a00ca40815e9b001dadfef9878ecd54594a
5 files changed
+403
-30
plugins.d/Makefile.am
+1
@@ -12,6 +12,7 @@ dist_plugins_SCRIPTS = \
12
alarm-notify.sh \
13
alarm-test.sh \
14
cgroup-name.sh \
15
+ cgroup-network-helper.sh \
16
charts.d.dryrun-helper.sh \
17
charts.d.plugin \
18
fping.plugin \
plugins.d/cgroup-network-helper.sh
new
+252
@@ -0,0 +1,252 @@
1
+#!/usr/bin/env bash
2
+
3
+# cgroup-network-helper.sh
4
+# detect container and virtual machine interfaces
5
+#
6
+# (C) 2017 Costa Tsaousis
7
+# GPL v3+
8
+#
9
+# This script is called as root (by cgroup-network), with either a pid, or a cgroup path.
10
+# It tries to find all the network interfaces that belong to the same cgroup.
11
+#
12
+# It supports several method for this detection:
13
+#
14
+# 1. cgroup-network (the binary father of this script) detects veth network interfaces,
15
+# by examining iflink and ifindex IDs and switching namespaces
16
+# (it also detects the interface name as it is used by the container).
17
+#
18
+# 2. this script, uses /proc/PID/fdinfo to find tun/tap network interfaces.
19
+#
20
+# 3. this script, calls virsh to find libvirt network interfaces.
21
+#
22
+
23
+# -----------------------------------------------------------------------------
24
+
25
+PROGRAM_NAME="$(basename "${0}")"
26
+
27
+logdate() {
28
+ date "+%Y-%m-%d %H:%M:%S"
29
+}
30
+
31
+log() {
32
+ local status="${1}"
33
+ shift
34
+
35
+ echo >&2 "$(logdate): ${PROGRAM_NAME}: ${status}: ${*}"
36
+
37
+}
38
+
39
+warning() {
40
+ log WARNING "${@}"
41
+}
42
+
43
+error() {
44
+ log ERROR "${@}"
45
+}
46
+
47
+info() {
48
+ log INFO "${@}"
49
+}
50
+
51
+fatal() {
52
+ log FATAL "${@}"
53
+ exit 1
54
+}
55
+
56
+debug=0
57
+debug() {
58
+ [ "${debug}" = "1" ] && log DEBUG "${@}"
59
+}
60
+
61
+# -----------------------------------------------------------------------------
62
+# check for BASH v4+ (required for associative arrays)
63
+
64
+[ $(( ${BASH_VERSINFO[0]} )) -lt 4 ] && \
65
+ fatal "BASH version 4 or later is required (this is ${BASH_VERSION})."
66
+
67
+# -----------------------------------------------------------------------------
68
+# defaults to allow running this script by hand
69
+
70
+[ -z "${NETDATA_PLUGINS_DIR}" ] && NETDATA_PLUGINS_DIR="$(dirname "${0}")"
71
+[ -z "${NETDATA_CONFIG_DIR}" ] && NETDATA_CONFIG_DIR="$(dirname "${0}")/../../../../etc/netdata"
72
+[ -z "${NETDATA_CACHE_DIR}" ] && NETDATA_CACHE_DIR="$(dirname "${0}")/../../../../var/cache/netdata"
73
+
74
+# -----------------------------------------------------------------------------
75
+# parse the arguments
76
+
77
+pid=
78
+cgroup=
79
+while [ ! -z "${1}" ]
80
+do
81
+ case "${1}" in
82
+ --cgroup) cgroup="${2}"; shift 1;;
83
+ --pid|-p) pid="${2}"; shift 1;;
84
+ --debug|debug) debug=1;;
85
+ *) fatal "Cannot understand argument '${1}'";;
86
+ esac
87
+
88
+ shift
89
+done
90
+
91
+if [ -z "${pid}" -a -z "${cgroup}" ]
92
+then
93
+ fatal "Either --pid or --cgroup is required"
94
+fi
95
+
96
+# -----------------------------------------------------------------------------
97
+
98
+set_source() {
99
+ [ ${debug} -eq 1 ] && echo "SRC ${*}"
100
+}
101
+
102
+
103
+# -----------------------------------------------------------------------------
104
+# veth interfaces via cgroup
105
+
106
+# cgroup-network can detect veth interfaces by itself (written in C).
107
+# If you seek for a shell version of what it does, check this:
108
+# https://github.com/firehol/netdata/issues/474#issuecomment-317866709
109
+
110
+
111
+# -----------------------------------------------------------------------------
112
+# tun/tap interfaces via /proc/PID/fdinfo
113
+
114
+# find any tun/tap devices linked to a pid
115
+proc_pid_fdinfo_iff() {
116
+ local p="${1}" # the pid
117
+
118
+ debug "Searching for tun/tap interfaces for pid ${p}..."
119
+ set_source "fdinfo"
120
+ grep ^iff:.* "${NETDATA_HOST_PREFIX}/proc/${p}/fdinfo"/* 2>/dev/null | cut -f 2
121
+}
122
+
123
+find_tun_tap_interfaces_for_cgroup() {
124
+ local c="${1}" # the cgroup path
125
+
126
+ # for each pid of the cgroup
127
+ # find any tun/tap devices linked to the pid
128
+ if [ -f "${c}/emulator/cgroup.procs" ]
129
+ then
130
+ local p
131
+ for p in $(< "${c}/emulator/cgroup.procs" )
132
+ do
133
+ proc_pid_fdinfo_iff ${p}
134
+ done
135
+ fi
136
+}
137
+
138
+
139
+# -----------------------------------------------------------------------------
140
+# virsh domain network interfaces
141
+
142
+virsh_cgroup_to_domain_name() {
143
+ local c="${1}" # the cgroup path
144
+
145
+ debug "extracting a possible virsh domain from cgroup ${c}..."
146
+
147
+ # extract for the cgroup path
148
+ # \1 = domain id
149
+ # \2 = domain name
150
+
151
+ echo "${c}" |\
152
+ sed "s|.*/machine-qemu\\\\x2d\([0-9]\+\)\\\\x2d\(.*\)\.scope$|virsh_domain_found \2|" |\
153
+ grep ^virsh_domain_found |\
154
+ cut -d ' ' -f 2
155
+}
156
+
157
+virsh_find_all_interfaces_for_cgroup() {
158
+ local c="${1}" # the cgroup path
159
+
160
+ # the virsh command
161
+ local virsh="$(which virsh 2>/dev/null || command -v virsh 2>/dev/null)"
162
+
163
+ if [ ! -z "${virsh}" ]
164
+ then
165
+ local d="$(virsh_cgroup_to_domain_name "${c}")"
166
+
167
+ if [ ! -z "${d}" ]
168
+ then
169
+ debug "running: virsh domiflist ${d}; to find the network interfaces"
170
+
171
+ set_source "virsh"
172
+ "${virsh}" domiflist ${d} |\
173
+ grep -Ev "^(Interface|----)" |\
174
+ cut -d ' ' -f 1
175
+ else
176
+ debug "no virsh domain extracted from cgroup ${c}"
177
+ fi
178
+ else
179
+ debug "virsh command is not available"
180
+ fi
181
+}
182
+
183
+# -----------------------------------------------------------------------------
184
+
185
+find_all_interfaces_of_pid_or_cgroup() {
186
+ local p="${1}" c="${2}" # the pid and the cgroup path
187
+
188
+ if [ ! -z "${pid}" ]
189
+ then
190
+ # we have been called with a pid
191
+
192
+ proc_pid_fdinfo_iff ${p}
193
+
194
+ elif [ ! -z "${c}" ]
195
+ then
196
+ # we have been called with a cgroup
197
+
198
+ info "searching for network interfaces of cgroup '${c}'"
199
+
200
+ find_tun_tap_interfaces_for_cgroup "${c}"
201
+ virsh_find_all_interfaces_for_cgroup "${c}"
202
+
203
+ else
204
+
205
+ error "Either a pid or a cgroup path is needed"
206
+ return 1
207
+
208
+ fi
209
+
210
+ return 0
211
+}
212
+
213
+# -----------------------------------------------------------------------------
214
+
215
+# an associative array to store the interfaces
216
+# the index is the interface name as seen by the host
217
+# the value is the interface name as seen by the guest / container
218
+declare -A devs=()
219
+
220
+# store all interfaces found in the associative array
221
+# this will also give the unique devices, as seen by the host
222
+last_src=
223
+while read host_device guest_device
224
+do
225
+ [ -z "${host_device}" ] && continue
226
+
227
+ [ "${host_device}" = "SRC" ] && last_src="${guest_device}" && continue
228
+
229
+ # the default guest_device is the host_device
230
+ [ -z "${guest_device}" ] && guest_device="${host_device}"
231
+
232
+ # when we run in debug, show the source
233
+ debug "Found host device '${host_device}', guest device '${guest_device}', detected via '${last_src}'"
234
+
235
+ [ -z "${devs[${host_device}]}" -o "${devs[${host_device}]}" = "${host_device}" ] && \
236
+ devs[${host_device}]="${guest_device}"
237
+
238
+done < <( find_all_interfaces_of_pid_or_cgroup "${pid}" "${cgroup}" )
239
+
240
+# print the interfaces found, in the format netdata expects them
241
+found=0
242
+for x in "${!devs[@]}"
243
+do
244
+ found=$((found + 1))
245
+ echo "${x} ${devs[${x}]}"
246
+done
247
+
248
+debug "found ${found} network interfaces for pid '${pid}', cgroup '${cgroup}', run as ${USER}, ${UID}"
249
+
250
+# let netdata know if we found any
251
+[ ${found} -eq 0 ] && exit 1
252
+exit 0
src/Makefile.am
+2
@@ -260,6 +260,8 @@ cgroup_network_SOURCES = \
260
inlined.h \
261
log.c log.h \
262
procfile.c procfile.h \
263
+ popen.c popen.h \
264
+ signals.c signals.h \
265
$(NULL)
266
267
cgroup_network_LDADD = \
src/cgroup-network.c
+147
-29
@@ -1,4 +1,5 @@
1
#include "common.h"
2
+#include <libgen.h>
3
4
#ifdef HAVE_SETNS
5
#ifndef _GNU_SOURCE
@@ -269,38 +270,62 @@ pid_t read_pid_from_cgroup(const char *path) {
270
return pid;
271
}
272
272
-void usage(void) {
273
- fprintf(stderr, "%s [ -p PID | --pid PID | --cgroup /path/to/cgroup ]\n", program_name);
274
- exit(1);
275
-}
273
277
-int main(int argc, char **argv) {
278
- pid_t pid = 0;
274
+// ----------------------------------------------------------------------------
275
+// send the result to netdata
276
280
- program_name = argv[0];
281
- program_version = VERSION;
282
- error_log_syslog = 0;
277
+struct found_device {
278
+ const char *host_device;
279
+ const char *guest_device;
280
284
- if(argc == 2 && (!strcmp(argv[1], "version") || !strcmp(argv[1], "-version") || !strcmp(argv[1], "--version") || !strcmp(argv[1], "-v") || !strcmp(argv[1], "-V"))) {
285
- fprintf(stderr, "cgroup-network %s\n", VERSION);
286
- exit(0);
287
- }
281
+ uint32_t host_device_hash;
282
289
- if(argc != 3)
290
- usage();
283
+ struct found_device *next;
284
+} *detected_devices = NULL;
285
292
- if(!strcmp(argv[1], "-p") || !strcmp(argv[1], "--pid")) {
293
- pid = atoi(argv[2]);
286
+void add_device(const char *host, const char *guest) {
287
+ uint32_t hash = simple_hash(host);
288
+
289
+ if(guest && (!*guest || strcmp(host, guest) == 0))
290
+ guest = NULL;
291
+
292
+ struct found_device *f;
293
+ for(f = detected_devices; f ; f = f->next) {
294
+ if(f->host_device_hash == hash && strcmp(host, f->host_device) == 0) {
295
+
296
+ if(guest && !f->guest_device)
297
+ f->guest_device = strdup(guest);
298
+
299
+ return;
300
+ }
301
}
295
- else if(!strcmp(argv[1], "--cgroup")) {
296
- pid = read_pid_from_cgroup(argv[2]);
302
+
303
+ f = mallocz(sizeof(struct found_device));
304
+ f->host_device = strdupz(host);
305
+ f->host_device_hash = hash;
306
+ f->guest_device = (guest)?strdupz(guest):NULL;
307
+ f->next = detected_devices;
308
+ detected_devices = f;
309
+}
310
+
311
+int send_devices(void) {
312
+ int found = 0;
313
+
314
+ struct found_device *f;
315
+ for(f = detected_devices; f ; f = f->next) {
316
+ found++;
317
+ printf("%s %s\n", f->host_device, (f->guest_device)?f->guest_device:f->host_device);
318
}
298
- else
299
- usage();
319
301
- if(pid <= 0)
302
- fatal("Invalid pid %d", (int)pid);
320
+ return found;
321
+}
322
+
323
+// ----------------------------------------------------------------------------
324
+// this function should be called only **ONCE**
325
+// also it has to be the **LAST** to be called
326
+// since it switches namespaces, so after this call, everything is different!
327
328
+void detect_veth_interfaces(pid_t pid) {
329
struct iface *host, *cgroup, *h, *c;
330
const char *prefix = getenv("NETDATA_HOST_PREFIX");
331
@@ -321,20 +346,113 @@ int main(int argc, char **argv) {
346
if(!eligible_ifaces(cgroup))
347
fatal("there are not double-linked cgroup interfaces available.");
348
324
- int found = 0;
349
for(h = host; h ; h = h->next) {
350
if(iface_is_eligible(h)) {
351
for (c = cgroup; c; c = c->next) {
352
if(iface_is_eligible(c) && h->ifindex == c->iflink && h->iflink == c->ifindex) {
329
- printf("%s %s\n", h->device, c->device);
330
- found++;
353
+ add_device(h->device, c->device);
354
}
355
}
356
}
357
}
358
+}
359
336
- if(!found)
337
- return 1;
360
+// ----------------------------------------------------------------------------
361
+// call the external helper
362
+
363
+#define CGROUP_NETWORK_INTERFACE_MAX_LINE 2048
364
+void call_the_helper(const char *me, pid_t pid, const char *cgroup) {
365
+ const char *pluginsdir = getenv("NETDATA_PLUGINS_DIR");
366
+ if(!pluginsdir || !*pluginsdir) {
367
+ char *m = strdupz(me);
368
+ pluginsdir = dirname(m);
369
+ }
370
339
- return 0;
371
+ if(setresuid(0, 0, 0) == -1)
372
+ error("setresuid(0, 0, 0) failed.");
373
+
374
+ char buffer[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];
375
+ if(cgroup)
376
+ snprintfz(buffer, CGROUP_NETWORK_INTERFACE_MAX_LINE, "exec %s/cgroup-network-helper.sh --cgroup '%s'", pluginsdir, cgroup);
377
+ else
378
+ snprintfz(buffer, CGROUP_NETWORK_INTERFACE_MAX_LINE, "exec %s/cgroup-network-helper.sh --pid %d", pluginsdir, pid);
379
+
380
+ info("running: %s", buffer);
381
+
382
+ pid_t cgroup_pid;
383
+ FILE *fp = mypopen(buffer, &cgroup_pid);
384
+ if(fp) {
385
+ char *s;
386
+ while((s = fgets(buffer, CGROUP_NETWORK_INTERFACE_MAX_LINE, fp))) {
387
+ trim(s);
388
+
389
+ if(*s && *s != '\n') {
390
+ char *t = s;
391
+ while(*t && *t != ' ') t++;
392
+ if(*t == ' ') {
393
+ *t = '\0';
394
+ t++;
395
+ }
396
+
397
+ if(!*s || !*t) continue;
398
+ add_device(s, t);
399
+ }
400
+ }
401
+
402
+ mypclose(fp, cgroup_pid);
403
+ }
404
+ else
405
+ error("cannot execute cgroup-network helper script: %s", buffer);
406
+}
407
+
408
+
409
+// ----------------------------------------------------------------------------
410
+// main
411
+
412
+void usage(void) {
413
+ fprintf(stderr, "%s [ -p PID | --pid PID | --cgroup /path/to/cgroup ]\n", program_name);
414
+ exit(1);
415
+}
416
+
417
+int main(int argc, char **argv) {
418
+ pid_t pid = 0;
419
+
420
+ program_name = argv[0];
421
+ program_version = VERSION;
422
+ error_log_syslog = 0;
423
+
424
+ if(argc == 2 && (!strcmp(argv[1], "version") || !strcmp(argv[1], "-version") || !strcmp(argv[1], "--version") || !strcmp(argv[1], "-v") || !strcmp(argv[1], "-V"))) {
425
+ fprintf(stderr, "cgroup-network %s\n", VERSION);
426
+ exit(0);
427
+ }
428
+
429
+ if(argc != 3)
430
+ usage();
431
+
432
+ if(!strcmp(argv[1], "-p") || !strcmp(argv[1], "--pid")) {
433
+ pid = atoi(argv[2]);
434
+
435
+ if(pid <= 0) {
436
+ errno = 0;
437
+ fatal("Invalid pid %d given", (int) pid);
438
+ }
439
+
440
+ call_the_helper(argv[0], pid, NULL);
441
+ }
442
+ else if(!strcmp(argv[1], "--cgroup")) {
443
+ pid = read_pid_from_cgroup(argv[2]);
444
+ call_the_helper(argv[0], pid, argv[2]);
445
+
446
+ if(pid <= 0 && !detected_devices) {
447
+ errno = 0;
448
+ fatal("Invalid pid %d read from cgroup '%s'", (int) pid, argv[2]);
449
+ }
450
+ }
451
+ else
452
+ usage();
453
+
454
+ if(pid > 0)
455
+ detect_veth_interfaces(pid);
456
+
457
+ return send_devices();
458
}
src/sys_fs_cgroup.c
+1
-1
@@ -762,7 +762,7 @@ static inline void read_cgroup_network_interfaces(struct cgroup *cg) {
762
}
763
764
if(!*t) {
765
- error("CGROUP: empty container interface returned by script");
765
+ error("CGROUP: empty guest interface returned by script");
766
continue;
767
}
768