Given a process a different network namespace:
$ ps aux | grep '[s]leep'
root 716080 0.4 0.0 2292 748 ? Ss 19:09 0:00 sleep 24h
$ sudo ls -l /proc/$$/ns/net /proc/716080/ns/net
lrwxrwxrwx 1 user group 0 Jul 14 19:11 /proc/715845/ns/net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Jul 14 19:09 /proc/716080/ns/net -> 'net:[4026532200]'
Approach 1
I don't know what /proc/net/stats
is --- I don't have it --- so I'll substitute /proc/net/stat/nf_conntrack
for this example. If I look at that file in the default namespace, I see:
$ cat /proc/net/stat/nf_conntrack
entries searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error expect_new expect_create expect_delete search_restart
00000001 00000000 00000000 00000000 00000000 000000b8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000001 00000000 00000000 00000000 00000002 00000087 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
You can use the nsenter
command to run commands in different namespace. Here, you can run cat
in the target process' network namespace:
$ sudo nsenter --net=/proc/716080/ns/net cat /proc/net/stat/nf_conntrack
entries searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error expect_new expect_create expect_delete search_restart
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
If you look carefully, the values are different; the one using nsenter
results in the content of that proc file in the network namespace of the target process.
Approach 2
The OP is looking for a solution that works in the context of a metric-gathering application. I've put together the following sample application that will achieve the desired outcome:
#define _GNU_SOURCE
#include <fcntl.h>
#include <limits.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage: %s <pid>\n", argv[0]);
return 1;
}
int pipe_fds[2] = {};
if (pipe(pipe_fds) < 0) {
perror("pipe");
return 2;
}
const pid_t pid = fork();
if (pid < 0) {
perror("fork");
return 3;
}
if (pid == 0) { // child
char file_path[PATH_MAX];
if (dup2(pipe_fds[1], STDOUT_FILENO) < 0) {
perror("dup2");
return 4;
}
close(pipe_fds[0]);
close(pipe_fds[1]);
snprintf(file_path, sizeof(file_path) - 1, "/proc/%s/ns/net", argv[1]);
const int fd = open(file_path, O_RDONLY);
if (fd < 0) {
perror("open");
return 5;
}
if (setns(fd, CLONE_NEWNET) < 0) {
perror("setns");
return 6;
}
FILE* const proc_file = fopen("/proc/net/stat/nf_conntrack", "r");
if (proc_file == NULL) {
fprintf(stderr, "fopen failed\n");
return 7;
}
char* line = NULL;
size_t line_len = 0;
while (getline(&line, &line_len, proc_file) != -1) {
printf("%s", line);
}
free(line);
fclose(proc_file);
return 0; // Child process is done
}
// parent
if (dup2(pipe_fds[0], STDIN_FILENO) < 0) {
perror("dup2");
return 8;
}
close(pipe_fds[0]);
close(pipe_fds[1]);
char* line = NULL;
size_t line_len = 0;
while (getline(&line, &line_len, stdin) != -1) {
printf("%s", line);
}
free(line);
// Clean up our dead child
wait(NULL);
return 0;
}
The application starts in the default network namespace. It takes a single command line argument -- the PID of a process in the target network namespace.
The program creates a pipe, then forks. The child connects its standard output to the write-end of the pipe. The parent connects its standard input to the read-end of the pipe.
The child uses opens the network namespace proc file for the target process, then uses the setns()
system call to switch its network namespace to the network namespace of the target process. The child then opens the /proc
file, reads it line-by-line, and writes the result to standard output (here, the pipe).
The parent reads lines from standard input --- the pipe --- and writes those lines to standard output.
Here, the parent process can play the role of the metric-gathering app. It stays in the default network namespace, and it could connect to some remote network host in the context of the default network namespace in order to communicate the values read.
Note that the fork()
-based approach isn't the only option, it just happens to be the one I used.
A run of the program:
$ sudo ./a.out 716080
entries searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error expect_new expect_create expect_delete search_restart
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
$