@cryptotaxi247 / netdata-1 / commits / a65c0a00c

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