summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rwxr-xr-xCMakeLists.txt19
-rw-r--r--configure.ac16
-rwxr-xr-xmakeself/install-or-update.sh2
-rwxr-xr-xnetdata-installer.sh6
-rw-r--r--src/Makefile.am19
-rw-r--r--src/apps_plugin.c2
-rw-r--r--src/cgroup-network.c300
-rw-r--r--src/freeipmi_plugin.c2
9 files changed, 364 insertions, 4 deletions
diff --git a/.gitignore b/.gitignore
index fc1e6ce5fd..40b6b1d3d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -109,3 +109,5 @@ diagrams/plantuml.jar
netdata.cppcheck
profile/statsd-stress
+src/cgroup-network
+vgcore.*
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8e59e094d8..0d926c189d 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -188,6 +188,22 @@ set(FREEIPMI_PLUGIN_SOURCE_FILES
config.h
)
+set(CGROUP_NETWORK_SOURCE_FILES
+ src/cgroup-network.c
+ src/common.c
+ src/common.h
+ src/clocks.c
+ src/clocks.h
+ src/inlined.h
+ src/log.c
+ src/log.h
+ src/procfile.c
+ src/procfile.h
+ src/web_buffer.c
+ src/web_buffer.h
+ config.h
+ )
+
include_directories(AFTER .)
add_definitions(-DHAVE_CONFIG_H -DCACHE_DIR="/var/cache/netdata" -DCONFIG_DIR="/etc/netdata" -DLOG_DIR="/var/log/netdata" -DPLUGINS_DIR="/usr/libexec/netdata" -DWEB_DIR="/usr/share/netdata" -DVARLIB_DIR="/var/lib/netdata")
@@ -200,3 +216,6 @@ target_link_libraries (apps.plugin m ${CMAKE_THREAD_LIBS_INIT})
add_executable(freeipmi.plugin ${FREEIPMI_PLUGIN_SOURCE_FILES})
target_link_libraries (freeipmi.plugin ipmimonitoring)
+
+add_executable(cgroup-network ${CGROUP_NETWORK_SOURCE_FILES})
+target_link_libraries (cgroup-network m ${CMAKE_THREAD_LIBS_INIT})
diff --git a/configure.ac b/configure.ac
index ce534e0ace..2ad8c3e180 100644
--- a/configure.ac
+++ b/configure.ac
@@ -148,7 +148,6 @@ AC_HEADER_RESOLV
AC_CHECK_HEADERS_ONCE([sys/prctl.h])
-
# -----------------------------------------------------------------------------
# operating system detection
@@ -426,6 +425,21 @@ AM_CONDITIONAL([ENABLE_PLUGIN_NFACCT], [test "${enable_plugin_nfacct}" = "yes"])
# -----------------------------------------------------------------------------
+# check for setns() - cgroup-network
+
+AC_CHECK_FUNC([setns])
+AC_MSG_CHECKING([if cgroup-network can be enabled])
+if test "$ac_cv_func_setns" = "yes" ; then
+ have_setns="yes"
+ AC_DEFINE([HAVE_SETNS], [1], [Define 1 if you have setns() function])
+else
+ have_setns="no"
+fi
+AC_MSG_RESULT([${have_setns}])
+AM_CONDITIONAL([ENABLE_PLUGIN_CGROUP_NETWORK], [test "${have_setns}" = "yes"])
+
+
+# -----------------------------------------------------------------------------
# Link-Time-Optimization
if test "${enable_lto}" != "no"; then
diff --git a/makeself/install-or-update.sh b/makeself/install-or-update.sh
index da63c64b6b..f822e7bcff 100755
--- a/makeself/install-or-update.sh
+++ b/makeself/install-or-update.sh
@@ -138,7 +138,7 @@ run chown -R ${NETDATA_USER}:${NETDATA_GROUP} /opt/netdata
# -----------------------------------------------------------------------------
progress "fix plugin permissions"
-for x in apps.plugin freeipmi.plugin
+for x in apps.plugin freeipmi.plugin cgroup-network
do
f="usr/libexec/netdata/plugins.d/${x}"
diff --git a/netdata-installer.sh b/netdata-installer.sh
index fbcd75d660..c986e5a898 100755
--- a/netdata-installer.sh
+++ b/netdata-installer.sh
@@ -782,6 +782,12 @@ if [ ${UID} -eq 0 ]
run chmod 4755 "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/freeipmi.plugin"
fi
+ if [ -f "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/cgroup-network" ]
+ then
+ run chown root "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/cgroup-network"
+ run chmod 4755 "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/cgroup-network"
+ fi
+
else
run chown "${NETDATA_USER}:${NETDATA_USER}" "${NETDATA_LOG_DIR}"
run chown -R "${NETDATA_USER}:${NETDATA_USER}" "${NETDATA_PREFIX}/usr/libexec/netdata"
diff --git a/src/Makefile.am b/src/Makefile.am
index 601d3204ff..637270d34f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -37,6 +37,10 @@ if ENABLE_PLUGIN_FREEIPMI
plugins_PROGRAMS += freeipmi.plugin
endif
+if ENABLE_PLUGIN_CGROUP_NETWORK
+plugins_PROGRAMS += cgroup-network
+endif
+
netdata_SOURCES = \
adaptive_resortable_list.c \
adaptive_resortable_list.h \
@@ -244,3 +248,18 @@ freeipmi_plugin_SOURCES = \
freeipmi_plugin_LDADD = \
$(OPTIONAL_IPMIMONITORING_LIBS) \
$(NULL)
+
+cgroup_network_SOURCES = \
+ cgroup-network.c \
+ clocks.c clocks.h \
+ common.c common.h \
+ inlined.h \
+ log.c log.h \
+ procfile.c procfile.h \
+ web_buffer.c web_buffer.h \
+ $(NULL)
+
+cgroup_network_LDADD = \
+ $(OPTIONAL_MATH_LIBS) \
+ $(OPTIONAL_LIBCAP_LIBS) \
+ $(NULL)
diff --git a/src/apps_plugin.c b/src/apps_plugin.c
index ecb6aaeacb..c0eb5c0838 100644
--- a/src/apps_plugin.c
+++ b/src/apps_plugin.c
@@ -3197,7 +3197,7 @@ static void parse_args(int argc, char **argv)
}
}
- if(strcmp("version", argv[i]) == 0 || strcmp("-v", argv[i]) == 0 || strcmp("-V", argv[i]) == 0) {
+ if(strcmp("version", argv[i]) == 0 || strcmp("-version", argv[i]) == 0 || strcmp("--version", argv[i]) == 0 || strcmp("-v", argv[i]) == 0 || strcmp("-V", argv[i]) == 0) {
printf("apps.plugin %s\n", VERSION);
exit(0);
}
diff --git a/src/cgroup-network.c b/src/cgroup-network.c
new file mode 100644
index 0000000000..7e04b6cf58
--- /dev/null
+++ b/src/cgroup-network.c
@@ -0,0 +1,300 @@
+#include "common.h"
+
+#ifdef HAVE_SETNS
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* See feature_test_macros(7) */
+#endif
+#include <sched.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// callback required by fatal()
+
+void netdata_cleanup_and_exit(int ret) {
+ exit(ret);
+}
+
+struct iface {
+ const char *device;
+ uint32_t hash;
+
+ unsigned int ifindex;
+ unsigned int iflink;
+
+ struct iface *next;
+};
+
+unsigned int read_iface_iflink(const char *prefix, const char *iface) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/sys/class/net/%s/iflink", prefix?prefix:"", iface);
+
+ unsigned long long iflink = 0;
+ int ret = read_single_number_file(filename, &iflink);
+ if(ret) error("Cannot read '%s'.", filename);
+
+ return (unsigned int)iflink;
+}
+
+unsigned int read_iface_ifindex(const char *prefix, const char *iface) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/sys/class/net/%s/ifindex", prefix?prefix:"", iface);
+
+ unsigned long long ifindex = 0;
+ int ret = read_single_number_file(filename, &ifindex);
+ if(ret) error("Cannot read '%s'.", filename);
+
+ return (unsigned int)ifindex;
+}
+
+struct iface *read_proc_net_dev(const char *prefix) {
+ procfile *ff = NULL;
+ char filename[FILENAME_MAX + 1];
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", prefix?prefix:"", "/proc/net/dev");
+ ff = procfile_open(filename, " \t,:|", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) {
+ error("Cannot open file '%s'", filename);
+ return NULL;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) {
+ error("Cannot read file '%s'", filename);
+ return NULL;
+ }
+
+ size_t lines = procfile_lines(ff), l;
+ struct iface *root = NULL;
+ for(l = 2; l < lines ;l++) {
+ if (unlikely(procfile_linewords(ff, l) < 1)) continue;
+
+ struct iface *t = callocz(1, sizeof(struct iface));
+ t->device = strdupz(procfile_lineword(ff, l, 0));
+ t->hash = simple_hash(t->device);
+ t->ifindex = read_iface_ifindex(prefix, t->device);
+ t->iflink = read_iface_iflink(prefix, t->device);
+ t->next = root;
+ root = t;
+ }
+
+ return root;
+}
+
+inline int iface_is_eligible(struct iface *iface) {
+ if(iface->iflink != iface->ifindex)
+ return 1;
+
+ return 0;
+}
+
+int eligible_ifaces(struct iface *root) {
+ int eligible = 0;
+
+ struct iface *t;
+ for(t = root; t ; t = t->next)
+ if(iface_is_eligible(t))
+ eligible++;
+
+ return eligible;
+}
+
+static void continue_as_child(void) {
+ pid_t child = fork();
+ int status;
+ pid_t ret;
+
+ if (child < 0)
+ error("fork() failed");
+
+ /* Only the child returns */
+ if (child == 0)
+ return;
+
+ for (;;) {
+ ret = waitpid(child, &status, WUNTRACED);
+ if ((ret == child) && (WIFSTOPPED(status))) {
+ /* The child suspended so suspend us as well */
+ kill(getpid(), SIGSTOP);
+ kill(child, SIGCONT);
+ } else {
+ break;
+ }
+ }
+
+ /* Return the child's exit code if possible */
+ if (WIFEXITED(status)) {
+ exit(WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ kill(getpid(), WTERMSIG(status));
+ }
+
+ exit(EXIT_FAILURE);
+}
+
+int proc_pid_fd(const char *prefix, const char *ns, pid_t pid) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/proc/%d/%s", prefix?prefix:"", (int)pid, ns);
+ int fd = open(filename, O_RDONLY);
+
+ if(fd == -1)
+ error("Cannot open file '%s'", filename);
+
+ return fd;
+}
+
+static struct ns {
+ int nstype;
+ int fd;
+ int status;
+ const char *name;
+ const char *path;
+} all_ns[] = {
+ // { .nstype = CLONE_NEWUSER, .fd = -1, .status = -1, .name = "user", .path = "ns/user" },
+ // { .nstype = CLONE_NEWCGROUP, .fd = -1, .status = -1, .name = "cgroup", .path = "ns/cgroup" },
+ // { .nstype = CLONE_NEWIPC, .fd = -1, .status = -1, .name = "ipc", .path = "ns/ipc" },
+ // { .nstype = CLONE_NEWUTS, .fd = -1, .status = -1, .name = "uts", .path = "ns/uts" },
+ { .nstype = CLONE_NEWNET, .fd = -1, .status = -1, .name = "network", .path = "ns/net" },
+ { .nstype = CLONE_NEWPID, .fd = -1, .status = -1, .name = "pid", .path = "ns/pid" },
+ { .nstype = CLONE_NEWNS, .fd = -1, .status = -1, .name = "mount", .path = "ns/mnt" },
+
+ // terminator
+ { .nstype = 0, .fd = -1, .status = -1, .name = NULL, .path = NULL }
+};
+
+int switch_namespace(const char *prefix, pid_t pid) {
+#ifdef HAVE_SETNS
+
+ int i;
+ for(i = 0; all_ns[i].name ; i++)
+ all_ns[i].fd = proc_pid_fd(prefix, all_ns[i].path, pid);
+
+ int root_fd = proc_pid_fd(prefix, "root", pid);
+ int cwd_fd = proc_pid_fd(prefix, "cwd", pid);
+
+ setgroups(0, NULL);
+
+ // 2 passes - found it at nsenter source code
+ // this is related CLONE_NEWUSER functionality
+
+ int pass, errors = 0;
+ for(pass = 0; pass < 2 ;pass++) {
+ for(i = 0; all_ns[i].name ; i++) {
+ if (all_ns[i].fd != -1 && all_ns[i].status == -1) {
+ if(setns(all_ns[i].fd, all_ns[i].nstype) == -1) {
+ if(pass == 1) {
+ all_ns[i].status = 0;
+ error("Cannot switch to %s namespace of pid %d", all_ns[i].name, (int) pid);
+ errors++;
+ }
+ }
+ else
+ all_ns[i].status = 1;
+ }
+ }
+ }
+
+ setgroups(0, NULL);
+
+ if(root_fd != -1) {
+ if(fchdir(root_fd) < 0)
+ error("Cannot fchdir() to pid %d root directory", (int)pid);
+
+ if(chroot(".") < 0)
+ error("Cannot chroot() to pid %d root directory", (int)pid);
+
+ close(root_fd);
+ }
+
+ if(cwd_fd != -1) {
+ if(fchdir(cwd_fd) < 0)
+ error("Cannot fchdir() to pid %d current working directory", (int)pid);
+
+ close(cwd_fd);
+ }
+
+ int do_fork = 0;
+ for(i = 0; all_ns[i].name ; i++)
+ if(all_ns[i].fd != -1) {
+
+ // CLONE_NEWPID requires a fork() to become effective
+ if(all_ns[i].nstype == CLONE_NEWPID && all_ns[i].status)
+ do_fork = 1;
+
+ close(all_ns[i].fd);
+ }
+
+ if(do_fork)
+ continue_as_child();
+
+ return 0;
+
+#else
+
+ errno = ENOSYS;
+ error("setns() is missing on this system.");
+ return 1;
+
+#endif
+}
+
+void usage(void) {
+ fprintf(stderr, "%s -p PID\n", program_name);
+ exit(1);
+}
+
+int main(int argc, char **argv) {
+ pid_t pid = 0;
+
+ program_name = argv[0];
+ program_version = VERSION;
+ error_log_syslog = 0;
+
+ if(argc == 2 && (!strcmp(argv[1], "version") || !strcmp(argv[1], "-version") || !strcmp(argv[1], "--version") || !strcmp(argv[1], "-v") || !strcmp(argv[1], "-V"))) {
+ fprintf(stderr, "cgroup-network %s\n", VERSION);
+ exit(0);
+ }
+
+ if(argc != 3 || strcmp(argv[1], "-p") != 0)
+ usage();
+
+ pid = atoi(argv[2]);
+ if(pid <= 0)
+ fatal("Invalid pid %d", (int)pid);
+
+ struct iface *host, *cgroup, *h, *c;
+ const char *prefix = getenv("NETDATA_HOST_PREFIX");
+
+ host = read_proc_net_dev(prefix);
+ if(!host)
+ fatal("cannot read host interface list.");
+
+ if(!eligible_ifaces(host))
+ fatal("there are no double-linked host interfaces available.");
+
+ if(switch_namespace(prefix, pid))
+ fatal("cannot switch to the namespace of pid %u", (unsigned int)pid);
+
+ cgroup = read_proc_net_dev(NULL);
+ if(!cgroup)
+ fatal("cannot read cgroup interface list.");
+
+ if(!eligible_ifaces(cgroup))
+ fatal("there are not double-linked cgroup interfaces available.");
+
+ int found = 0;
+ for(h = host; h ; h = h->next) {
+ if(iface_is_eligible(h)) {
+ for (c = cgroup; c; c = c->next) {
+ if(iface_is_eligible(c) && h->ifindex == c->iflink && h->iflink == c->ifindex) {
+ printf("%s\n", h->device);
+ found++;
+ }
+ }
+ }
+ }
+
+ if(!found)
+ return 1;
+
+ return 0;
+}
diff --git a/src/freeipmi_plugin.c b/src/freeipmi_plugin.c
index 146268a531..42a1ac01df 100644
--- a/src/freeipmi_plugin.c
+++ b/src/freeipmi_plugin.c
@@ -1433,7 +1433,7 @@ int main (int argc, char **argv) {
continue;
}
}
- else if(strcmp("version", argv[i]) == 0 || strcmp("-v", argv[i]) == 0 || strcmp("-V", argv[i]) == 0) {
+ else if(strcmp("version", argv[i]) == 0 || strcmp("-version", argv[i]) == 0 || strcmp("--version", argv[i]) == 0 || strcmp("-v", argv[i]) == 0 || strcmp("-V", argv[i]) == 0) {
printf("freeipmi.plugin %s\n", VERSION);
exit(0);
}