master
c 884 lines 27.1 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "libnetdata/libnetdata.h"
4
5 SPAWN_SERVER *spawn_server = NULL;
6
7 char env_netdata_host_prefix[FILENAME_MAX + 50] = "";
8 char env_netdata_log_method[FILENAME_MAX + 50] = "";
9 char env_netdata_log_format[FILENAME_MAX + 50] = "";
10 char env_netdata_log_level[FILENAME_MAX + 50] = "";
11 char *environment[] = {
12 "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin",
13 env_netdata_host_prefix,
14 env_netdata_log_method,
15 env_netdata_log_format,
16 env_netdata_log_level,
17 NULL
18 };
19
20 struct iface {
21 const char *device;
22 uint32_t hash;
23
24 unsigned int ifindex;
25 unsigned int iflink;
26
27 struct iface *next;
28 };
29
30 unsigned int calc_num_ifaces(struct iface *root) {
31 unsigned int num = 0;
32 for (struct iface *h = root; h; h = h->next) {
33 num++;
34 }
35 return num;
36 }
37
38 unsigned int read_iface_iflink(const char *prefix, const char *iface) {
39 if(!prefix) prefix = "";
40
41 char filename[FILENAME_MAX + 1];
42 snprintfz(filename, FILENAME_MAX, "%s/sys/class/net/%s/iflink", prefix, iface);
43
44 unsigned long long iflink = 0;
45 int ret = read_single_number_file(filename, &iflink);
46 if(ret) nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot read '%s'.", filename);
47
48 return (unsigned int)iflink;
49 }
50
51 unsigned int read_iface_ifindex(const char *prefix, const char *iface) {
52 if(!prefix) prefix = "";
53
54 char filename[FILENAME_MAX + 1];
55 snprintfz(filename, FILENAME_MAX, "%s/sys/class/net/%s/ifindex", prefix, iface);
56
57 unsigned long long ifindex = 0;
58 int ret = read_single_number_file(filename, &ifindex);
59 if(ret) nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot read '%s'.", filename);
60
61 return (unsigned int)ifindex;
62 }
63
64 struct iface *read_proc_net_dev(const char *scope __maybe_unused, const char *prefix) {
65 if(!prefix) prefix = "";
66
67 procfile *ff = NULL;
68 char filename[FILENAME_MAX + 1];
69
70 snprintfz(filename, FILENAME_MAX, "%s%s", prefix, (*prefix)?"/proc/1/net/dev":"/proc/net/dev");
71
72 ff = procfile_open(filename, " \t,:|", PROCFILE_FLAG_DEFAULT);
73 if(unlikely(!ff)) {
74 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open file '%s'", filename);
75 return NULL;
76 }
77
78 ff = procfile_readall(ff);
79 if(unlikely(!ff)) {
80 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot read file '%s'", filename);
81 return NULL;
82 }
83
84 size_t lines = procfile_lines(ff), l;
85 struct iface *root = NULL;
86 for(l = 2; l < lines ;l++) {
87 if (unlikely(procfile_linewords(ff, l) < 1)) continue;
88
89 struct iface *t = callocz(1, sizeof(struct iface));
90 t->device = strdupz(procfile_lineword(ff, l, 0));
91 t->hash = simple_hash(t->device);
92 t->ifindex = read_iface_ifindex(prefix, t->device);
93 t->iflink = read_iface_iflink(prefix, t->device);
94 t->next = root;
95 root = t;
96
97 nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "added %s interface '%s', ifindex %u, iflink %u", scope, t->device, t->ifindex, t->iflink);
98 }
99
100 procfile_close(ff);
101
102 return root;
103 }
104
105 void free_iface(struct iface *iface) {
106 freez((void *)iface->device);
107 freez(iface);
108 }
109
110 void free_host_ifaces(struct iface *iface) {
111 while(iface) {
112 struct iface *t = iface->next;
113 free_iface(iface);
114 iface = t;
115 }
116 }
117
118 int iface_is_eligible(struct iface *iface) {
119 if(iface->iflink != iface->ifindex)
120 return 1;
121
122 return 0;
123 }
124
125 int eligible_ifaces(struct iface *root) {
126 int eligible = 0;
127
128 struct iface *t;
129 for(t = root; t ; t = t->next)
130 if(iface_is_eligible(t))
131 eligible++;
132
133 return eligible;
134 }
135
136 static void continue_as_child(void) {
137 pid_t child = fork();
138 int status;
139 pid_t ret;
140
141 if (child < 0) {
142 nd_log(NDLS_COLLECTORS, NDLP_ERR, "fork() failed");
143 exit(1);
144 }
145
146 if (child == 0) {
147 // the child returns
148 gettid_uncached();
149 return;
150 }
151
152 // here is the parent
153 for (;;) {
154 ret = waitpid(child, &status, WUNTRACED);
155 if ((ret == child) && (WIFSTOPPED(status))) {
156 /* The child suspended so suspend us as well */
157 kill(getpid(), SIGSTOP);
158 kill(child, SIGCONT);
159 } else {
160 break;
161 }
162 tinysleep();
163 }
164
165 /* Return the child's exit code if possible */
166
167 #ifdef __SANITIZE_ADDRESS__
168 /*
169 * With sanitization, exiting leads to an infinite loop (100% cpu) here:
170 *
171 * #0 0x00007ffff690ea8b in sched_yield () from /usr/lib/libc.so.6
172 * #1 0x00007ffff792c4a6 in __sanitizer::StopTheWorld (callback=<optimized out>, argument=<optimized out>) at /usr/src/debug/gcc/gcc/libsanitizer/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cpp:457
173 * #2 0x00007ffff793f6f9 in __lsan::LockStuffAndStopTheWorldCallback (info=<optimized out>, size=<optimized out>, data=0x7fffffffde20) at /usr/src/debug/gcc/gcc/libsanitizer/lsan/lsan_common_linux.cpp:127
174 * #3 0x00007ffff6977909 in dl_iterate_phdr () from /usr/lib/libc.so.6
175 * #4 0x00007ffff793fb24 in __lsan::LockStuffAndStopTheWorld (callback=callback@entry=0x7ffff793d9d0 <__lsan::CheckForLeaksCallback(__sanitizer::SuspendedThreadsList const&, void*)>, argument=argument@entry=0x7fffffffdea0)
176 * at /usr/src/debug/gcc/gcc/libsanitizer/lsan/lsan_common_linux.cpp:142
177 * #5 0x00007ffff793c965 in __lsan::CheckForLeaks () at /usr/src/debug/gcc/gcc/libsanitizer/lsan/lsan_common.cpp:778
178 * #6 0x00007ffff793cc68 in __lsan::DoLeakCheck () at /usr/src/debug/gcc/gcc/libsanitizer/lsan/lsan_common.cpp:821
179 * #7 0x00007ffff684e340 in __cxa_finalize () from /usr/lib/libc.so.6
180 * #8 0x00007ffff7838c58 in __do_global_dtors_aux () from /usr/lib/libasan.so.8
181 * #9 0x00007fffffffdfe0 in ?? ()
182 *
183 * Probably is something related to switching name spaces.
184 * So, we kill -9 self.
185 *
186 */
187
188 nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "sanitizers detected, killing myself to avoid lockup");
189 kill(getpid(), SIGKILL);
190 #endif
191
192 if (WIFEXITED(status)) {
193 exit(WEXITSTATUS(status));
194 } else if (WIFSIGNALED(status)) {
195 kill(getpid(), WTERMSIG(status));
196 }
197
198 exit(EXIT_FAILURE);
199 }
200
201 int proc_pid_fd(const char *prefix, const char *ns, pid_t pid, ND_LOG_FIELD_PRIORITY priority) {
202 if(!prefix) prefix = "";
203
204 char filename[FILENAME_MAX + 1];
205 snprintfz(filename, FILENAME_MAX, "%s/proc/%d/%s", prefix, (int)pid, ns);
206 int fd = open(filename, O_RDONLY | O_CLOEXEC);
207
208 if(fd == -1)
209 nd_log(NDLS_COLLECTORS, priority, "Cannot open proc_pid_fd() file '%s'", filename);
210
211 return fd;
212 }
213
214 static struct ns {
215 int nstype;
216 int fd;
217 int status;
218 const char *name;
219 const char *path;
220 } all_ns[] = {
221 // { .nstype = CLONE_NEWUSER, .fd = -1, .status = -1, .name = "user", .path = "ns/user" },
222 // { .nstype = CLONE_NEWCGROUP, .fd = -1, .status = -1, .name = "cgroup", .path = "ns/cgroup" },
223 // { .nstype = CLONE_NEWIPC, .fd = -1, .status = -1, .name = "ipc", .path = "ns/ipc" },
224 // { .nstype = CLONE_NEWUTS, .fd = -1, .status = -1, .name = "uts", .path = "ns/uts" },
225 { .nstype = CLONE_NEWNET, .fd = -1, .status = -1, .name = "network", .path = "ns/net" },
226 { .nstype = CLONE_NEWPID, .fd = -1, .status = -1, .name = "pid", .path = "ns/pid" },
227 { .nstype = CLONE_NEWNS, .fd = -1, .status = -1, .name = "mount", .path = "ns/mnt" },
228
229 // terminator
230 { .nstype = 0, .fd = -1, .status = -1, .name = NULL, .path = NULL }
231 };
232
233 static int switch_namespace(const char *prefix, pid_t pid) {
234 #ifdef HAVE_SETNS
235 int i;
236 int root_fd = -1;
237
238 for(i = 0; all_ns[i].name ; i++) {
239 // Only network namespace is mandatory; optional namespaces log warnings
240 ND_LOG_FIELD_PRIORITY prio = (all_ns[i].nstype == CLONE_NEWNET) ? NDLP_ERR : NDLP_WARNING;
241 all_ns[i].fd = proc_pid_fd(prefix, all_ns[i].path, pid, prio);
242 }
243
244 root_fd = proc_pid_fd(prefix, "root", pid, NDLP_ERR);
245
246 // Verify we can access the network namespace fd
247 // This is the only namespace critical for correct interface detection
248 for(i = 0; all_ns[i].name ; i++) {
249 if(all_ns[i].nstype == CLONE_NEWNET) {
250 if(all_ns[i].fd == -1) {
251 // proc_pid_fd() already logs the open failure
252 goto cleanup_and_fail;
253 }
254 break;
255 }
256 }
257
258 setgroups(0, NULL);
259
260 // 2 passes - found it at nsenter source code
261 // this is related CLONE_NEWUSER functionality
262
263 // This code cannot switch user namespace (it can all the other namespaces)
264 // Fortunately, we don't need to switch user namespaces.
265
266 int pass;
267 for(pass = 0; pass < 2 ;pass++) {
268 for(i = 0; all_ns[i].name ; i++) {
269 if (all_ns[i].fd != -1 && all_ns[i].status == -1) {
270 if(setns(all_ns[i].fd, all_ns[i].nstype) == -1) {
271 if(pass == 1) {
272 all_ns[i].status = 0;
273 // Only log critical namespace failures here;
274 // non-critical failures are logged in the verification loop below
275 if(all_ns[i].nstype == CLONE_NEWNET) {
276 nd_log(NDLS_COLLECTORS, NDLP_ERR,
277 "Cannot switch to %s namespace of pid %d",
278 all_ns[i].name, (int) pid);
279 }
280 }
281 }
282 else
283 all_ns[i].status = 1;
284 }
285 }
286 }
287
288 // Verify critical namespaces were successfully switched
289 for(i = 0; all_ns[i].name ; i++) {
290 if(all_ns[i].fd != -1 && !all_ns[i].status) {
291 if(all_ns[i].nstype == CLONE_NEWNET) {
292 // Network namespace is mandatory for correct interface detection
293 nd_log(NDLS_COLLECTORS, NDLP_ERR,
294 "Failed to switch to %s namespace of pid %d",
295 all_ns[i].name, (int) pid);
296 goto cleanup_and_fail;
297 }
298 // Mount/PID namespace failure is non-critical for network detection
299 nd_log(NDLS_COLLECTORS, NDLP_WARNING,
300 "Failed to switch to %s namespace of pid %d (continuing)",
301 all_ns[i].name, (int) pid);
302 }
303 }
304
305 gettid_uncached();
306 setgroups(0, NULL);
307
308 if(root_fd != -1) {
309 if(fchdir(root_fd) < 0) {
310 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot fchdir() to pid %d root directory", (int)pid);
311 goto cleanup_and_fail;
312 }
313
314 if(chroot(".") < 0) {
315 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot chroot() to pid %d root directory", (int)pid);
316 goto cleanup_and_fail;
317 }
318
319 if(chdir("/") < 0) {
320 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot chdir() to / after chroot for pid %d", (int)pid);
321 goto cleanup_and_fail;
322 }
323
324 close(root_fd);
325 }
326
327 int do_fork = 0;
328 for(i = 0; all_ns[i].name ; i++)
329 if(all_ns[i].fd != -1) {
330
331 // CLONE_NEWPID requires a fork() to become effective
332 if(all_ns[i].nstype == CLONE_NEWPID && all_ns[i].status)
333 do_fork = 1;
334
335 close(all_ns[i].fd);
336 all_ns[i].fd = -1;
337 }
338
339 if(do_fork)
340 continue_as_child();
341
342 return 0;
343
344 cleanup_and_fail:
345 if(root_fd != -1) close(root_fd);
346 for(i = 0; all_ns[i].name ; i++) {
347 if(all_ns[i].fd != -1) {
348 close(all_ns[i].fd);
349 all_ns[i].fd = -1;
350 }
351 all_ns[i].status = -1;
352 }
353 return 1;
354
355 #else
356
357 errno = ENOSYS;
358 nd_log(NDLS_COLLECTORS, NDLP_ERR, "setns() is missing on this system.");
359 return 1;
360 #endif
361 }
362
363 pid_t read_pid_from_cgroup_file(const char *filename) {
364 int fd = open(filename, procfile_open_flags);
365 if(fd == -1) {
366 if (errno != ENOENT)
367 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open pid_from_cgroup() file '%s'.", filename);
368 return 0;
369 }
370
371 FILE *fp = fdopen(fd, "r");
372 if(!fp) {
373 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot upgrade fd to fp for file '%s'.", filename);
374 return 0;
375 }
376
377 char buffer[100 + 1];
378 pid_t pid = 0;
379 char *s;
380 while((s = fgets(buffer, 100, fp))) {
381 buffer[100] = '\0';
382 pid = atoi(s);
383 if(pid > 0) break;
384 }
385
386 fclose(fp);
387
388 if(pid > 0)
389 nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "found pid %d on file '%s'", pid, filename);
390
391 return pid;
392 }
393
394 pid_t read_pid_from_cgroup_files(const char *path) {
395 char filename[FILENAME_MAX + 1];
396
397 snprintfz(filename, FILENAME_MAX, "%s/cgroup.procs", path);
398 pid_t pid = read_pid_from_cgroup_file(filename);
399 if(pid > 0) return pid;
400
401 snprintfz(filename, FILENAME_MAX, "%s/tasks", path);
402 return read_pid_from_cgroup_file(filename);
403 }
404
405 pid_t read_pid_from_cgroup(const char *path) {
406 pid_t pid = read_pid_from_cgroup_files(path);
407 if (pid > 0) return pid;
408
409 DIR *dir = opendir(path);
410 if (!dir) {
411 nd_log(NDLS_COLLECTORS, NDLP_ERR, "cannot read directory '%s'", path);
412 return 0;
413 }
414
415 struct dirent *de = NULL;
416 while ((de = readdir(dir))) {
417 if (de->d_type == DT_DIR
418 && (
419 (de->d_name[0] == '.' && de->d_name[1] == '\0')
420 || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
421 ))
422 continue;
423
424 if (de->d_type == DT_DIR) {
425 char filename[FILENAME_MAX + 1];
426 snprintfz(filename, FILENAME_MAX, "%s/%s", path, de->d_name);
427 pid = read_pid_from_cgroup(filename);
428 if(pid > 0) break;
429 }
430 }
431 closedir(dir);
432 return pid;
433 }
434
435 // ----------------------------------------------------------------------------
436 // send the result to netdata
437
438 struct found_device {
439 const char *host_device;
440 const char *guest_device;
441
442 uint32_t host_device_hash;
443
444 struct found_device *next;
445 } *detected_devices = NULL;
446
447 void add_device(const char *host, const char *guest) {
448 errno_clear();
449 nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "adding device with host '%s', guest '%s'", host, guest);
450
451 uint32_t hash = simple_hash(host);
452
453 if(guest && (!*guest || strcmp(host, guest) == 0))
454 guest = NULL;
455
456 struct found_device *f;
457 for(f = detected_devices; f ; f = f->next) {
458 if(f->host_device_hash == hash && !strcmp(host, f->host_device)) {
459
460 if(guest && (!f->guest_device || !strcmp(f->host_device, f->guest_device))) {
461 if(f->guest_device) freez((void *)f->guest_device);
462 f->guest_device = strdupz(guest);
463 }
464
465 return;
466 }
467 }
468
469 f = mallocz(sizeof(struct found_device));
470 f->host_device = strdupz(host);
471 f->host_device_hash = hash;
472 f->guest_device = (guest)?strdupz(guest):NULL;
473 f->next = detected_devices;
474 detected_devices = f;
475 }
476
477 int send_devices(void) {
478 int found = 0;
479
480 struct found_device *f;
481 for(f = detected_devices; f ; f = f->next) {
482 found++;
483 printf("%s %s\n", f->host_device, (f->guest_device)?f->guest_device:f->host_device);
484 }
485
486 return found;
487 }
488
489 // ----------------------------------------------------------------------------
490 // this function should be called only **ONCE**
491 // also it has to be the **LAST** to be called
492 // since it switches namespaces, so after this call, everything is different!
493
494 void detect_veth_interfaces(pid_t pid) {
495 struct iface *cgroup = NULL;
496 struct iface *host, *h, *c;
497
498 host = read_proc_net_dev("host", netdata_configured_host_prefix);
499 if(!host) {
500 errno_clear();
501 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "no host interface list.");
502 goto cleanup;
503 }
504
505 if(!eligible_ifaces(host)) {
506 errno_clear();
507 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "no double-linked host interfaces available.");
508 goto cleanup;
509 }
510
511 if(switch_namespace(netdata_configured_host_prefix, pid)) {
512 errno_clear();
513 nd_log(NDLS_COLLECTORS, NDLP_ERR, "cannot switch to the namespace of pid %u", (unsigned int) pid);
514 goto cleanup;
515 }
516
517 nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "switched to namespaces of pid %d", pid);
518
519 cgroup = read_proc_net_dev("cgroup", NULL);
520 if(!cgroup) {
521 errno_clear();
522 nd_log(NDLS_COLLECTORS, NDLP_ERR, "cannot read cgroup interface list.");
523 goto cleanup;
524 }
525
526 if(!eligible_ifaces(cgroup)) {
527 errno_clear();
528 nd_log(NDLS_COLLECTORS, NDLP_ERR, "there are not double-linked cgroup interfaces available.");
529 goto cleanup;
530 }
531
532 unsigned int host_dev_num = calc_num_ifaces(host);
533 unsigned int cgroup_dev_num = calc_num_ifaces(cgroup);
534 // host ifaces == guest ifaces => we are still in the host namespace
535 // and we can't really identify which ifaces belong to the cgroup (e.g. Proxmox VM).
536 if (host_dev_num == cgroup_dev_num) {
537 unsigned int m = 0;
538 for (h = host; h; h = h->next) {
539 for (c = cgroup; c; c = c->next) {
540 if (h->ifindex == c->ifindex && h->iflink == c->iflink) {
541 m++;
542 break;
543 }
544 }
545 }
546 if (host_dev_num == m) {
547 goto cleanup;
548 }
549 }
550
551 for(h = host; h ; h = h->next) {
552 if(iface_is_eligible(h)) {
553 for (c = cgroup; c; c = c->next) {
554 if(iface_is_eligible(c) && h->ifindex == c->iflink && h->iflink == c->ifindex) {
555 printf("%s %s\n", h->device, c->device);
556 // add_device(h->device, c->device);
557 }
558 }
559 }
560 }
561
562 printf("EXIT DONE\n");
563 fflush(stdout);
564
565 cleanup:
566 free_host_ifaces(cgroup);
567 free_host_ifaces(host);
568 }
569
570 struct send_to_spawned_process {
571 pid_t pid;
572 char host_prefix[FILENAME_MAX];
573 };
574
575
576 static int spawn_callback(SPAWN_REQUEST *request) {
577 const struct send_to_spawned_process *d = request->data;
578 detect_veth_interfaces(d->pid);
579 return 0;
580 }
581
582 #define CGROUP_NETWORK_INTERFACE_MAX_LINE 2048
583 static void read_from_spawned(SPAWN_INSTANCE *si, const char *name __maybe_unused) {
584 char buffer[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];
585 char *s;
586 FILE *fp = fdopen(spawn_server_instance_read_fd(si), "r");
587 while((s = fgets(buffer, CGROUP_NETWORK_INTERFACE_MAX_LINE, fp))) {
588 trim(s);
589
590 if(*s && *s != '\n') {
591 char *t = s;
592 while(*t && *t != ' ') t++;
593 if(*t == ' ') {
594 *t = '\0';
595 t++;
596 }
597
598 if(strcmp(s, "EXIT") == 0)
599 break;
600
601 if(!*s || !*t) continue;
602 add_device(s, t);
603 }
604 }
605 fclose(fp);
606 spawn_server_instance_read_fd_unset(si);
607 spawn_server_exec_kill(spawn_server, si, 0);
608 }
609
610 void detect_veth_interfaces_spawn(pid_t pid) {
611 struct send_to_spawned_process d = {
612 .pid = pid,
613 };
614 strncpyz(d.host_prefix, netdata_configured_host_prefix, sizeof(d.host_prefix) - 1);
615 SPAWN_INSTANCE *si = spawn_server_exec(spawn_server, STDERR_FILENO, 0, NULL, &d, sizeof(d), SPAWN_INSTANCE_TYPE_CALLBACK);
616 if(si)
617 read_from_spawned(si, "switch namespace callback");
618 else
619 nd_log(NDLS_COLLECTORS, NDLP_ERR, "cgroup-network cannot spawn switch namespace callback");
620 }
621
622 // ----------------------------------------------------------------------------
623 // call the external helper
624
625 #define CGROUP_NETWORK_INTERFACE_MAX_LINE 2048
626 void call_the_helper(pid_t pid, const char *cgroup) {
627 char command[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];
628 if(cgroup)
629 snprintfz(command, CGROUP_NETWORK_INTERFACE_MAX_LINE, "exec " PLUGINS_DIR "/cgroup-network-helper.sh --cgroup '%s'", cgroup);
630 else
631 snprintfz(command, CGROUP_NETWORK_INTERFACE_MAX_LINE, "exec " PLUGINS_DIR "/cgroup-network-helper.sh --pid %d", pid);
632
633 nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "running: %s", command);
634
635 SPAWN_INSTANCE *si;
636
637 if(cgroup) {
638 const char *argv[] = {
639 PLUGINS_DIR "/cgroup-network-helper.sh",
640 "--cgroup",
641 cgroup,
642 NULL,
643 };
644 si = spawn_server_exec(spawn_server, nd_log_collectors_fd(), 0, argv, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
645 }
646 else {
647 char buffer[100];
648 snprintfz(buffer, sizeof(buffer) - 1, "%d", pid);
649 const char *argv[] = {
650 PLUGINS_DIR "/cgroup-network-helper.sh",
651 "--pid",
652 buffer,
653 NULL,
654 };
655 si = spawn_server_exec(spawn_server, nd_log_collectors_fd(), 0, argv, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
656 }
657
658 if(si)
659 read_from_spawned(si, command);
660 else
661 nd_log(NDLS_COLLECTORS, NDLP_ERR, "cannot execute cgroup-network helper script: %s", command);
662 }
663
664 int ishex(char c) {
665 return (c >= '0' && c <= '9') ||
666 (c >= 'a' && c <= 'f') ||
667 (c >= 'A' && c <= 'F');
668 }
669
670 int is_valid_hex_escape(const char *arg) {
671 fatal_assert(arg);
672
673 return (arg[0] == '\\') &&
674 (arg[1] == 'x') &&
675 ishex(arg[2]) &&
676 ishex(arg[3]);
677 }
678
679 int is_valid_path_symbol(char c) {
680 switch(c) {
681 case '/': // path separators
682 case ' ': // space
683 case '-': // hyphen
684 case '_': // underscore
685 case '.': // dot
686 case ',': // comma
687 case '@': // systemd unit template specifier (/sys/fs/cgroup/machines.slice/systemd-nspawn@NAME.service)
688 return 1;
689
690 default:
691 return 0;
692 }
693 }
694
695 // we will pass this path a shell script running as root
696 // so, we need to make sure the path will be valid
697 // and will not include anything that could allow
698 // the caller use shell expansion for gaining escalated
699 // privileges.
700 int verify_path(const char *path) {
701 struct stat sb;
702
703 fatal_assert(path);
704
705 const char *s = path;
706 while(*s != '\0') {
707 if (isalnum(*s) || is_valid_path_symbol(*s))
708 s += 1;
709 else if (*s == '\\' && is_valid_hex_escape(s))
710 s += 4;
711 else {
712 nd_log(NDLS_COLLECTORS, NDLP_ERR, "invalid character in path '%s'", path);
713 return -1;
714 }
715 }
716
717 if(strstr(path, "/../")) {
718 nd_log(NDLS_COLLECTORS, NDLP_ERR, "invalid parent path sequence detected in '%s'", path);
719 return 1;
720 }
721
722 if(path[0] != '/') {
723 nd_log(NDLS_COLLECTORS, NDLP_ERR, "only absolute path names are supported - invalid path '%s'", path);
724 return -1;
725 }
726
727 if (stat(path, &sb) == -1) {
728 nd_log(NDLS_COLLECTORS, NDLP_ERR, "cannot stat() path '%s'", path);
729 return -1;
730 }
731
732 if((sb.st_mode & S_IFMT) != S_IFDIR) {
733 nd_log(NDLS_COLLECTORS, NDLP_ERR, "path '%s' is not a directory", path);
734 return -1;
735 }
736
737 return 0;
738 }
739
740 /*
741 char *fix_path_variable(void) {
742 const char *path = getenv("PATH");
743 if(!path || !*path) return 0;
744
745 char *p = strdupz(path);
746 char *safe_path = callocz(1, strlen(p) + strlen("PATH=") + 1);
747 strcpy(safe_path, "PATH=");
748
749 int added = 0;
750 char *ptr = p;
751 while(ptr && *ptr) {
752 char *s = strsep(&ptr, ":");
753 if(s && *s) {
754 if(verify_path(s) == -1) {
755 nd_log(NDLS_COLLECTORS, NDLP_ERR, "the PATH variable includes an invalid path '%s' - removed it.", s);
756 }
757 else {
758 nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "the PATH variable includes a valid path '%s'.", s);
759 if(added) strcat(safe_path, ":");
760 strcat(safe_path, s);
761 added++;
762 }
763 }
764 }
765
766 nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "unsafe PATH: '%s'.", path);
767 nd_log(NDLS_COLLECTORS, NDLP_DEBUG, " safe PATH: '%s'.", safe_path);
768
769 freez(p);
770 return safe_path;
771 }
772 */
773
774 // ----------------------------------------------------------------------------
775 // main
776
777 static void cleanup_spawn_server_on_fatal(void) {
778 if(spawn_server) {
779 spawn_server_destroy(spawn_server);
780 spawn_server = NULL;
781 }
782 }
783
784 void usage(void) {
785 fprintf(stderr, "%s [ -p PID | --pid PID | --cgroup /path/to/cgroup ]\n", program_name);
786 exit(1);
787 }
788
789 int main(int argc, const char **argv) {
790 pid_t pid = 0;
791
792 if (setresuid(0, 0, 0) == -1)
793 collector_error("setresuid(0, 0, 0) failed.");
794
795 nd_log_initialize_for_external_plugins("cgroup-network");
796 spawn_server = spawn_server_create(SPAWN_SERVER_OPTION_EXEC | SPAWN_SERVER_OPTION_CALLBACK, NULL, spawn_callback, argc, argv);
797 nd_log_register_fatal_final_cb(cleanup_spawn_server_on_fatal);
798
799 // since cgroup-network runs as root, prevent it from opening symbolic links
800 procfile_open_flags = O_RDONLY|O_NOFOLLOW;
801
802 // ------------------------------------------------------------------------
803 // make sure NETDATA_HOST_PREFIX is safe
804
805 netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
806 if(verify_netdata_host_prefix(false) == -1) exit(1);
807
808 if(netdata_configured_host_prefix[0] != '\0' && verify_path(netdata_configured_host_prefix) == -1)
809 fatal("invalid NETDATA_HOST_PREFIX '%s'", netdata_configured_host_prefix);
810
811 // ------------------------------------------------------------------------
812 // build a safe environment for our script
813
814 // the first environment variable is a fixed PATH=
815 snprintfz(env_netdata_host_prefix, sizeof(env_netdata_host_prefix) - 1, "NETDATA_HOST_PREFIX=%s", netdata_configured_host_prefix);
816
817 char *s;
818
819 s = getenv("NETDATA_LOG_METHOD");
820 snprintfz(env_netdata_log_method, sizeof(env_netdata_log_method) - 1, "NETDATA_LOG_METHOD=%s", nd_log_method_for_external_plugins(s));
821
822 s = getenv("NETDATA_LOG_FORMAT");
823 if (s)
824 snprintfz(env_netdata_log_format, sizeof(env_netdata_log_format) - 1, "NETDATA_LOG_FORMAT=%s", s);
825
826 s = getenv("NETDATA_LOG_LEVEL");
827 if (s)
828 snprintfz(env_netdata_log_level, sizeof(env_netdata_log_level) - 1, "NETDATA_LOG_LEVEL=%s", s);
829
830 // ------------------------------------------------------------------------
831
832 if(argc == 2 && (!strcmp(argv[1], "version") || !strcmp(argv[1], "-version") || !strcmp(argv[1], "--version") || !strcmp(argv[1], "-v") || !strcmp(argv[1], "-V"))) {
833 fprintf(stderr, "cgroup-network %s\n", NETDATA_VERSION);
834 exit(0);
835 }
836
837 if(argc != 3)
838 usage();
839
840 int arg = 1;
841 int helper = 1;
842 if (getenv("KUBERNETES_SERVICE_HOST") != NULL && getenv("KUBERNETES_SERVICE_PORT") != NULL)
843 helper = 0;
844
845 if(!strcmp(argv[arg], "-p") || !strcmp(argv[arg], "--pid")) {
846 pid = atoi(argv[arg+1]);
847
848 if(pid <= 0) {
849 errno_clear();
850 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Invalid pid %d given", (int) pid);
851 return 2;
852 }
853
854 if(helper) call_the_helper(pid, NULL);
855 }
856 else if(!strcmp(argv[arg], "--cgroup")) {
857 const char *cgroup = argv[arg+1];
858 if(verify_path(cgroup) == -1) {
859 nd_log(NDLS_COLLECTORS, NDLP_ERR, "cgroup '%s' does not exist or is not valid.", cgroup);
860 return 1;
861 }
862
863 pid = read_pid_from_cgroup(cgroup);
864 if(helper) call_the_helper(pid, cgroup);
865
866 if(pid <= 0 && !detected_devices) {
867 errno_clear();
868 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot find a cgroup PID from cgroup '%s'", cgroup);
869 }
870 }
871 else
872 usage();
873
874 if(pid > 0)
875 detect_veth_interfaces_spawn(pid);
876
877 int found = send_devices();
878
879 spawn_server_destroy(spawn_server);
880 spawn_server = NULL;
881
882 if(found <= 0) return 1;
883 return 0;
884 }