Skip to content
This repository was archived by the owner on Apr 3, 2018. It is now read-only.
This repository was archived by the owner on Apr 3, 2018. It is now read-only.

namespaces: How to enter them in a foolproof way #644

@sboeuf

Description

@sboeuf

Following discussion on #613 and implementation #615, I will try to summarize what are the issues we're facing with Golang and what are the options here.
We would like to enter any namespaces from virtcontainers (or any code written in Go) and be able to execute some callbacks inside a set of defined namespaces. The reason of why we need to enter those namespaces does not need to be detailed here.

Now, let's say we want to execute some code in a set of namespaces, we can use the package nsenter implemented in the PR #615, which will basically save the current set of namespaces, then enter the targeted namespaces, execute the callback, and switch back to the saved(original) namespaces. But doing so, we have 2 drawbacks that could end up in unwanted behaviors:

  • If the callback defines a go routine, the code running into this routine will be running in a different thread (TID will be different), meaning it won't run in the expected set of namespaces.
  • If we're using a version < Go 1.10, we might hit a very unlikely issue (but still have a chance), which is inherent to Golang itself. If the code executed from the callback might take too much time, Golang might decide to create a new thread for executing this code, even if runtime.LockOSThread() has been previously called, leading our code to run in the wrong namespaces. This is obviously a Golang bug that has been fixed in Go 1.10.

Additionally to those potential issues, there is a main constraint that cannot be solved by Golang since this is multithreaded: the ability to enter mount or user namespaces. Following discussions here, here and here give some input.

So now, what should we do ? Well having C code is the solution to the multi-threading problem, and this will solve both the drawbacks and the limitations explained above. So why don't we get started ?
Well, it's not as easy as it seems, because we cannot only define a C function called from Go code, this is already too late, you're already multi-threaded at that time.

The solution would be to invoke a C constructor like this:

void __attribute__((constructor)) init_func(int argc, const char **argv)

from our Go code. This constructor is always running before any piece of Go code, ensuring about the single-threading here. In order to reach this code from a Go function, we need to re-execute ourselves with a snippet like this:

package main

/*
#cgo CFLAGS: -Wall
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

__attribute__((constructor)) static void constructor_map(int argc, const char **argv) {
	fprintf(stdout, "TEST 0\n");
	if (argc > 2) {
		fprintf(stdout, "TEST 1\n");
		exit(0);
	}
	fprintf(stdout, "TEST 2\n");
}
*/
import "C"
import "os"
import "os/exec"

func main() {
	cmd := exec.Command("/proc/self/exe", "init", "test")
	cmd.Stdout = os.Stdout
	cmd.Run()
}

From this code, if you replace the call to fprintf(stdout, "TEST 1\n"); with some code entering namespaces, you can enter any namespaces with the guarantee you won't end up in a non expected namespace. Basically, after entering the set of namespaces, we would need to execute our callback, assuring this would be running in the right namespaces. BUT, cause there is always one, I am not sure we can provide a Go callback through this so that it gets executed after the code entering the namespaces. I did some researches here and there and tried a few things that didn't work, so I am getting skeptical on this possibility. But I think this needs more investigation.
Based on libcontainer code, one way to do this would be to open a pipe between the Go code and the C code so that we can communicate what needs to be done, but again I am not sure about the ability to pass a function pointer here.

Now if we want to solve this more easily by saying that our package will spawn a new binary into the expected namespaces, things get easier. This means we would need to pass a bunch of const char[] info about the binary path and its argument through the parameter list of our re-execution. But notice that in this case, spawning our VM into the right network namespace would need to be implemented at the govmm level, that is the level where we deal with binary path and parameters.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions