2

Is there a way that an application can access the /proc/net/stat from another namespace?

The obvious thing to do would be to put the application, i.e, process in that netns, however, this application I have in mind should monitor the stats in /proc/net/stat and should forward them to a database over the network, however the database is only rout-able from the default namespace.

I was hoping there would be some path like /proc/namespace-root/foo/net/stat but afaics there is not.

Also I was hoping that I would not have to rely on random PIDs in the other namespaces and poll that through /proc/$somePID/ns/net.

1 Answer 1

2

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
$
6
  • You are right, I meant './stat', singular that is.
    – hbogert
    Jul 15, 2020 at 6:52
  • But this means that my metric gathering application still needs to be in the namespace, but in my case that means it can no longer reach the database to send its metrics to. Of course I could make veths, but that would just introduce more dependencies like a dns server on the host, to forward the database fqdn to, (it's a private DNS server/zone, which is reachable only in the default namespace), and probably some NAT rules. Ergo, all kinds of attack vectors which imo are not needed if the default namespace could just reach this other namespace's proc through the virtual filesystem.
    – hbogert
    Jul 15, 2020 at 7:05
  • I see, the approach is solid, but it does imply the application has to be namespace aware. The metric gathering application would be something in Golang, which doesn't have a stellar record to doing namespace operations. See weave.works/blog/linux-namespaces-golang-followup (even now those mentioned issues are not fundamentally solved afaict), so I wanted to see if we could not make the application NS aware; thus leaving it up to the OS to make a /proc of an other namespace visible. I'll do some more investigation and otherwise mark this as the answer in due time.
    – hbogert
    Jul 16, 2020 at 7:18
  • @hbogert if you want to do namespace-aware metric collection, then your collector needs to be namespace aware; I don't see any way around that. Jul 16, 2020 at 14:33
  • Well that depends on what the definition of aware is, but if Linux could mount other namespaces' proc in another namespace, then the metric gathering application would only have to know about some filepaths, and be agnostic to any clone(), setns(),etc; kind of functionality.
    – hbogert
    Jul 16, 2020 at 14:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.