| 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 | } |