added cgroup-network that can find the network interface of a cgroup, given a pid in it
Costa Tsaousis (ktsaou) committed
Jul 30, 2017 at 19:29 UTC
012d456f385f1d629d9110bd0a0aa5bcd52c59e7
9 files changed
+364
-4
.gitignore
+2
@@ -109,3 +109,5 @@ diagrams/plantuml.jar
109
netdata.cppcheck
110
111
profile/statsd-stress
112
+src/cgroup-network
113
+vgcore.*
CMakeLists.txt
+19
@@ -188,6 +188,22 @@ set(FREEIPMI_PLUGIN_SOURCE_FILES
188
config.h
189
)
190
191
+set(CGROUP_NETWORK_SOURCE_FILES
192
+ src/cgroup-network.c
193
+ src/common.c
194
+ src/common.h
195
+ src/clocks.c
196
+ src/clocks.h
197
+ src/inlined.h
198
+ src/log.c
199
+ src/log.h
200
+ src/procfile.c
201
+ src/procfile.h
202
+ src/web_buffer.c
203
+ src/web_buffer.h
204
+ config.h
205
+ )
206
+
207
include_directories(AFTER .)
208
209
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})
216
217
add_executable(freeipmi.plugin ${FREEIPMI_PLUGIN_SOURCE_FILES})
218
target_link_libraries (freeipmi.plugin ipmimonitoring)
219
+
220
+add_executable(cgroup-network ${CGROUP_NETWORK_SOURCE_FILES})
221
+target_link_libraries (cgroup-network m ${CMAKE_THREAD_LIBS_INIT})
configure.ac
+15
-1
@@ -148,7 +148,6 @@ AC_HEADER_RESOLV
148
149
AC_CHECK_HEADERS_ONCE([sys/prctl.h])
150
151
-
151
# -----------------------------------------------------------------------------
152
# operating system detection
153
@@ -425,6 +424,21 @@ AC_MSG_RESULT([${enable_plugin_nfacct}])
424
AM_CONDITIONAL([ENABLE_PLUGIN_NFACCT], [test "${enable_plugin_nfacct}" = "yes"])
425
426
427
+# -----------------------------------------------------------------------------
428
+# check for setns() - cgroup-network
429
+
430
+AC_CHECK_FUNC([setns])
431
+AC_MSG_CHECKING([if cgroup-network can be enabled])
432
+if test "$ac_cv_func_setns" = "yes" ; then
433
+ have_setns="yes"
434
+ AC_DEFINE([HAVE_SETNS], [1], [Define 1 if you have setns() function])
435
+else
436
+ have_setns="no"
437
+fi
438
+AC_MSG_RESULT([${have_setns}])
439
+AM_CONDITIONAL([ENABLE_PLUGIN_CGROUP_NETWORK], [test "${have_setns}" = "yes"])
440
+
441
+
442
# -----------------------------------------------------------------------------
443
# Link-Time-Optimization
444
makeself/install-or-update.sh
+1
-1
@@ -138,7 +138,7 @@ run chown -R ${NETDATA_USER}:${NETDATA_GROUP} /opt/netdata
138
# -----------------------------------------------------------------------------
139
progress "fix plugin permissions"
140
141
-for x in apps.plugin freeipmi.plugin
141
+for x in apps.plugin freeipmi.plugin cgroup-network
142
do
143
f="usr/libexec/netdata/plugins.d/${x}"
144
netdata-installer.sh
+6
@@ -782,6 +782,12 @@ if [ ${UID} -eq 0 ]
782
run chmod 4755 "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/freeipmi.plugin"
783
fi
784
785
+ if [ -f "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/cgroup-network" ]
786
+ then
787
+ run chown root "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/cgroup-network"
788
+ run chmod 4755 "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/cgroup-network"
789
+ fi
790
+
791
else
792
run chown "${NETDATA_USER}:${NETDATA_USER}" "${NETDATA_LOG_DIR}"
793
run chown -R "${NETDATA_USER}:${NETDATA_USER}" "${NETDATA_PREFIX}/usr/libexec/netdata"
src/Makefile.am
+19
@@ -37,6 +37,10 @@ if ENABLE_PLUGIN_FREEIPMI
37
plugins_PROGRAMS += freeipmi.plugin
38
endif
39
40
+if ENABLE_PLUGIN_CGROUP_NETWORK
41
+plugins_PROGRAMS += cgroup-network
42
+endif
43
+
44
netdata_SOURCES = \
45
adaptive_resortable_list.c \
46
adaptive_resortable_list.h \
@@ -244,3 +248,18 @@ freeipmi_plugin_SOURCES = \
248
freeipmi_plugin_LDADD = \
249
$(OPTIONAL_IPMIMONITORING_LIBS) \
250
$(NULL)
251
+
252
+cgroup_network_SOURCES = \
253
+ cgroup-network.c \
254
+ clocks.c clocks.h \
255
+ common.c common.h \
256
+ inlined.h \
257
+ log.c log.h \
258
+ procfile.c procfile.h \
259
+ web_buffer.c web_buffer.h \
260
+ $(NULL)
261
+
262
+cgroup_network_LDADD = \
263
+ $(OPTIONAL_MATH_LIBS) \
264
+ $(OPTIONAL_LIBCAP_LIBS) \
265
+ $(NULL)
src/apps_plugin.c
+1
-1
@@ -3197,7 +3197,7 @@ static void parse_args(int argc, char **argv)
3197
}
3198
}
3199
3200
- if(strcmp("version", argv[i]) == 0 || strcmp("-v", argv[i]) == 0 || strcmp("-V", argv[i]) == 0) {
3200
+ 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) {
3201
printf("apps.plugin %s\n", VERSION);
3202
exit(0);
3203
}
src/cgroup-network.c
new
+300
@@ -0,0 +1,300 @@
1
+#include "common.h"
2
+
3
+#ifdef HAVE_SETNS
4
+#ifndef _GNU_SOURCE
5
+#define _GNU_SOURCE /* See feature_test_macros(7) */
6
+#endif
7
+#include <sched.h>
8
+#endif
9
+
10
+// ----------------------------------------------------------------------------
11
+// callback required by fatal()
12
+
13
+void netdata_cleanup_and_exit(int ret) {
14
+ exit(ret);
15
+}
16
+
17
+struct iface {
18
+ const char *device;
19
+ uint32_t hash;
20
+
21
+ unsigned int ifindex;
22
+ unsigned int iflink;
23
+
24
+ struct iface *next;
25
+};
26
+
27
+unsigned int read_iface_iflink(const char *prefix, const char *iface) {
28
+ char filename[FILENAME_MAX + 1];
29
+ snprintfz(filename, FILENAME_MAX, "%s/sys/class/net/%s/iflink", prefix?prefix:"", iface);
30
+
31
+ unsigned long long iflink = 0;
32
+ int ret = read_single_number_file(filename, &iflink);
33
+ if(ret) error("Cannot read '%s'.", filename);
34
+
35
+ return (unsigned int)iflink;
36
+}
37
+
38
+unsigned int read_iface_ifindex(const char *prefix, const char *iface) {
39
+ char filename[FILENAME_MAX + 1];
40
+ snprintfz(filename, FILENAME_MAX, "%s/sys/class/net/%s/ifindex", prefix?prefix:"", iface);
41
+
42
+ unsigned long long ifindex = 0;
43
+ int ret = read_single_number_file(filename, &ifindex);
44
+ if(ret) error("Cannot read '%s'.", filename);
45
+
46
+ return (unsigned int)ifindex;
47
+}
48
+
49
+struct iface *read_proc_net_dev(const char *prefix) {
50
+ procfile *ff = NULL;
51
+ char filename[FILENAME_MAX + 1];
52
+
53
+ snprintfz(filename, FILENAME_MAX, "%s%s", prefix?prefix:"", "/proc/net/dev");
54
+ ff = procfile_open(filename, " \t,:|", PROCFILE_FLAG_DEFAULT);
55
+ if(unlikely(!ff)) {
56
+ error("Cannot open file '%s'", filename);
57
+ return NULL;
58
+ }
59
+
60
+ ff = procfile_readall(ff);
61
+ if(unlikely(!ff)) {
62
+ error("Cannot read file '%s'", filename);
63
+ return NULL;
64
+ }
65
+
66
+ size_t lines = procfile_lines(ff), l;
67
+ struct iface *root = NULL;
68
+ for(l = 2; l < lines ;l++) {
69
+ if (unlikely(procfile_linewords(ff, l) < 1)) continue;
70
+
71
+ struct iface *t = callocz(1, sizeof(struct iface));
72
+ t->device = strdupz(procfile_lineword(ff, l, 0));
73
+ t->hash = simple_hash(t->device);
74
+ t->ifindex = read_iface_ifindex(prefix, t->device);
75
+ t->iflink = read_iface_iflink(prefix, t->device);
76
+ t->next = root;
77
+ root = t;
78
+ }
79
+
80
+ return root;
81
+}
82
+
83
+inline int iface_is_eligible(struct iface *iface) {
84
+ if(iface->iflink != iface->ifindex)
85
+ return 1;
86
+
87
+ return 0;
88
+}
89
+
90
+int eligible_ifaces(struct iface *root) {
91
+ int eligible = 0;
92
+
93
+ struct iface *t;
94
+ for(t = root; t ; t = t->next)
95
+ if(iface_is_eligible(t))
96
+ eligible++;
97
+
98
+ return eligible;
99
+}
100
+
101
+static void continue_as_child(void) {
102
+ pid_t child = fork();
103
+ int status;
104
+ pid_t ret;
105
+
106
+ if (child < 0)
107
+ error("fork() failed");
108
+
109
+ /* Only the child returns */
110
+ if (child == 0)
111
+ return;
112
+
113
+ for (;;) {
114
+ ret = waitpid(child, &status, WUNTRACED);
115
+ if ((ret == child) && (WIFSTOPPED(status))) {
116
+ /* The child suspended so suspend us as well */
117
+ kill(getpid(), SIGSTOP);
118
+ kill(child, SIGCONT);
119
+ } else {
120
+ break;
121
+ }
122
+ }
123
+
124
+ /* Return the child's exit code if possible */
125
+ if (WIFEXITED(status)) {
126
+ exit(WEXITSTATUS(status));
127
+ } else if (WIFSIGNALED(status)) {
128
+ kill(getpid(), WTERMSIG(status));
129
+ }
130
+
131
+ exit(EXIT_FAILURE);
132
+}
133
+
134
+int proc_pid_fd(const char *prefix, const char *ns, pid_t pid) {
135
+ char filename[FILENAME_MAX + 1];
136
+ snprintfz(filename, FILENAME_MAX, "%s/proc/%d/%s", prefix?prefix:"", (int)pid, ns);
137
+ int fd = open(filename, O_RDONLY);
138
+
139
+ if(fd == -1)
140
+ error("Cannot open file '%s'", filename);
141
+
142
+ return fd;
143
+}
144
+
145
+static struct ns {
146
+ int nstype;
147
+ int fd;
148
+ int status;
149
+ const char *name;
150
+ const char *path;
151
+} all_ns[] = {
152
+ // { .nstype = CLONE_NEWUSER, .fd = -1, .status = -1, .name = "user", .path = "ns/user" },
153
+ // { .nstype = CLONE_NEWCGROUP, .fd = -1, .status = -1, .name = "cgroup", .path = "ns/cgroup" },
154
+ // { .nstype = CLONE_NEWIPC, .fd = -1, .status = -1, .name = "ipc", .path = "ns/ipc" },
155
+ // { .nstype = CLONE_NEWUTS, .fd = -1, .status = -1, .name = "uts", .path = "ns/uts" },
156
+ { .nstype = CLONE_NEWNET, .fd = -1, .status = -1, .name = "network", .path = "ns/net" },
157
+ { .nstype = CLONE_NEWPID, .fd = -1, .status = -1, .name = "pid", .path = "ns/pid" },
158
+ { .nstype = CLONE_NEWNS, .fd = -1, .status = -1, .name = "mount", .path = "ns/mnt" },
159
+
160
+ // terminator
161
+ { .nstype = 0, .fd = -1, .status = -1, .name = NULL, .path = NULL }
162
+};
163
+
164
+int switch_namespace(const char *prefix, pid_t pid) {
165
+#ifdef HAVE_SETNS
166
+
167
+ int i;
168
+ for(i = 0; all_ns[i].name ; i++)
169
+ all_ns[i].fd = proc_pid_fd(prefix, all_ns[i].path, pid);
170
+
171
+ int root_fd = proc_pid_fd(prefix, "root", pid);
172
+ int cwd_fd = proc_pid_fd(prefix, "cwd", pid);
173
+
174
+ setgroups(0, NULL);
175
+
176
+ // 2 passes - found it at nsenter source code
177
+ // this is related CLONE_NEWUSER functionality
178
+
179
+ int pass, errors = 0;
180
+ for(pass = 0; pass < 2 ;pass++) {
181
+ for(i = 0; all_ns[i].name ; i++) {
182
+ if (all_ns[i].fd != -1 && all_ns[i].status == -1) {
183
+ if(setns(all_ns[i].fd, all_ns[i].nstype) == -1) {
184
+ if(pass == 1) {
185
+ all_ns[i].status = 0;
186
+ error("Cannot switch to %s namespace of pid %d", all_ns[i].name, (int) pid);
187
+ errors++;
188
+ }
189
+ }
190
+ else
191
+ all_ns[i].status = 1;
192
+ }
193
+ }
194
+ }
195
+
196
+ setgroups(0, NULL);
197
+
198
+ if(root_fd != -1) {
199
+ if(fchdir(root_fd) < 0)
200
+ error("Cannot fchdir() to pid %d root directory", (int)pid);
201
+
202
+ if(chroot(".") < 0)
203
+ error("Cannot chroot() to pid %d root directory", (int)pid);
204
+
205
+ close(root_fd);
206
+ }
207
+
208
+ if(cwd_fd != -1) {
209
+ if(fchdir(cwd_fd) < 0)
210
+ error("Cannot fchdir() to pid %d current working directory", (int)pid);
211
+
212
+ close(cwd_fd);
213
+ }
214
+
215
+ int do_fork = 0;
216
+ for(i = 0; all_ns[i].name ; i++)
217
+ if(all_ns[i].fd != -1) {
218
+
219
+ // CLONE_NEWPID requires a fork() to become effective
220
+ if(all_ns[i].nstype == CLONE_NEWPID && all_ns[i].status)
221
+ do_fork = 1;
222
+
223
+ close(all_ns[i].fd);
224
+ }
225
+
226
+ if(do_fork)
227
+ continue_as_child();
228
+
229
+ return 0;
230
+
231
+#else
232
+
233
+ errno = ENOSYS;
234
+ error("setns() is missing on this system.");
235
+ return 1;
236
+
237
+#endif
238
+}
239
+
240
+void usage(void) {
241
+ fprintf(stderr, "%s -p PID\n", program_name);
242
+ exit(1);
243
+}
244
+
245
+int main(int argc, char **argv) {
246
+ pid_t pid = 0;
247
+
248
+ program_name = argv[0];
249
+ program_version = VERSION;
250
+ error_log_syslog = 0;
251
+
252
+ if(argc == 2 && (!strcmp(argv[1], "version") || !strcmp(argv[1], "-version") || !strcmp(argv[1], "--version") || !strcmp(argv[1], "-v") || !strcmp(argv[1], "-V"))) {
253
+ fprintf(stderr, "cgroup-network %s\n", VERSION);
254
+ exit(0);
255
+ }
256
+
257
+ if(argc != 3 || strcmp(argv[1], "-p") != 0)
258
+ usage();
259
+
260
+ pid = atoi(argv[2]);
261
+ if(pid <= 0)
262
+ fatal("Invalid pid %d", (int)pid);
263
+
264
+ struct iface *host, *cgroup, *h, *c;
265
+ const char *prefix = getenv("NETDATA_HOST_PREFIX");
266
+
267
+ host = read_proc_net_dev(prefix);
268
+ if(!host)
269
+ fatal("cannot read host interface list.");
270
+
271
+ if(!eligible_ifaces(host))
272
+ fatal("there are no double-linked host interfaces available.");
273
+
274
+ if(switch_namespace(prefix, pid))
275
+ fatal("cannot switch to the namespace of pid %u", (unsigned int)pid);
276
+
277
+ cgroup = read_proc_net_dev(NULL);
278
+ if(!cgroup)
279
+ fatal("cannot read cgroup interface list.");
280
+
281
+ if(!eligible_ifaces(cgroup))
282
+ fatal("there are not double-linked cgroup interfaces available.");
283
+
284
+ int found = 0;
285
+ for(h = host; h ; h = h->next) {
286
+ if(iface_is_eligible(h)) {
287
+ for (c = cgroup; c; c = c->next) {
288
+ if(iface_is_eligible(c) && h->ifindex == c->iflink && h->iflink == c->ifindex) {
289
+ printf("%s\n", h->device);
290
+ found++;
291
+ }
292
+ }
293
+ }
294
+ }
295
+
296
+ if(!found)
297
+ return 1;
298
+
299
+ return 0;
300
+}
src/freeipmi_plugin.c
+1
-1
@@ -1433,7 +1433,7 @@ int main (int argc, char **argv) {
1433
continue;
1434
}
1435
}
1436
- else if(strcmp("version", argv[i]) == 0 || strcmp("-v", argv[i]) == 0 || strcmp("-V", argv[i]) == 0) {
1436
+ 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) {
1437
printf("freeipmi.plugin %s\n", VERSION);
1438
exit(0);
1439
}