0

I want to change namespace in go. When I'm compiling and running code in C it works fine, but in go I got errno 22 on netns syscall. Any Idea why this could occur?

go)

$ go build main.go ; ./main
setns mnt: Invalid argument
panic: -1

goroutine 1 [running]:
runtime.panic(0x423b80, 0xffffffffffffffff)
    /usr/local/go/src/pkg/runtime/panic.c:266 +0xb6
main.main()
    main.go:81 +0x86
$

c)

$ grep ^// main.go | sed 's/\/\///' | sed 's/__main/main/' > main.c; gcc main.c -o main; ./main
$

The code below:

package main

//
// #define _GNU_SOURCE
// #include <fcntl.h>
// #include <sched.h>
// #include <sys/syscall.h>
// #include <sys/param.h>
// #include <sys/mount.h>
// #include <stdio.h>
// #include <unistd.h>
//
// #define NETNS_RUN_DIR "/run/netns"
// #define MNTNS_RUN_DIR "/run/mntns"
//
// #ifndef HAVE_SETNS
//
// int
// setns(int fd, int nstype) {
// #ifdef __NR_setns
//   return syscall(__NR_setns, fd, nstype);
// #else
//   errno = ENOSYS;
//   return -1;
// #endif
// }
//
// #endif /* HAVE_SETNS */
//
//
// int
// ChangeNamespace(char *name)
// {
//   char net_path[MAXPATHLEN];
//   char mnt_path[MAXPATHLEN];
//   int fd;
//
//   snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, name);
//   snprintf(mnt_path, sizeof(mnt_path), "%s/%s", MNTNS_RUN_DIR, name);
//
//   fd = open(net_path, O_RDONLY);
//   if (fd < 0) {
//     perror("open net");
//     return -1;
//   }
//
//   if (setns(fd, 0) < 0) {
//     perror("setns net");
//     return -1;
//   }
//
//   fd = open(mnt_path, O_RDONLY);
//   if (fd < 0) {
//     perror("open mnt");
//     return -1;
//   }
//
//   if (setns(fd, 0) < 0) {
//     perror("setns mnt");
//     return -1;
//   }
//
//   return 0;
// }
//
// int
// __main(int argc, char *argv[]) {
//     ChangeNamespace("ns");
//     return 0;
// }
//
import "C"
import "unsafe"
func main() {
    name := C.CString("ns")
    defer C.free(unsafe.Pointer(name))
    i := int(C.ChangeNamespace(name))
    if i < 0 {
        panic(i)
    }
}
KrHubert
  • 1,030
  • 7
  • 17
  • Can you alter your `perror()` lines to have the strings say which call failed? Just "Invalid argument" isn't enough to explain what's going on. (I have a feeling this is a moving stacks issue...) – andlabs Sep 04 '14 at 15:23
  • You also need to call `runtime.LockOSThread` to ensure that setns is being called from the expected thread. – JimB Sep 04 '14 at 15:32
  • You may want to check out how docker handles this in [libcontainer/namespaces](https://github.com/libcontainer/namespaces). There's also at least 1 open issue about adding more support to the upcoming `go.sys` package: [#8447](https://code.google.com/p/go/issues/detail?id=8447) – JimB Sep 04 '14 at 15:34
  • @andlabs: done. Now perror have error msg – KrHubert Sep 04 '14 at 15:35
  • What happens if you move `name := C.CString("ns")` outside `main()`? – andlabs Sep 04 '14 at 16:14
  • 2
    Please see http://stackoverflow.com/questions/25704661/calling-setns-from-go-returns-einval-for-mnt-namespace for the answer I obtained from the Go project. – Iain Lowe Sep 07 '14 at 04:18

1 Answers1

5

You could use something like this skipping the cgo all together, I can't test it right now:

const (
    netNS = "/run/netns/"
    mntNS = "/run/mntns/"
)
func ChangeNamespace(name string) error {
    fd, err := syscall.Open(netNS+name, syscall.O_RDONLY, 0666)
    if err != nil {
        return err
    }
    defer syscall.Close(fd)
    if _, _, err := syscall.RawSyscall(syscall.SYS_SETNS, uintptr(fd), 0, 0); err != nil {
        return err
    }

    fd1, err := syscall.Open(mntNS+name, syscall.O_RDONLY, 0666)
    if err != nil {
        return err
    }
    defer syscall.Close(fd1)
    if _, _, err := syscall.RawSyscall(syscall.SYS_SETNS, uintptr(fd1), 0, 0); err != nil {
        return err
    }
    return nil
}
OneOfOne
  • 95,033
  • 20
  • 184
  • 185
  • I must be tired yesterday when testing it, but this dosent solve the problem. Still the same error. – KrHubert Sep 05 '14 at 09:04
  • @ixos were you running the c code as root by any chance? because I don't see how one syscall works fine and the other fails. – OneOfOne Sep 05 '14 at 13:07
  • I run both code as root, as you can see in first post in go) and c) section. Also I have rewritten this code to asm, perl, python and all of them are working. So for me it looks like go must compiling it different. I don't see any suspicious option in gcc (go build -n). Maybe 6c or 6g have impact on this syscall, but I'm not familiar whit those tools. – KrHubert Sep 05 '14 at 14:35
  • @ixos one last idea is to try gccgo, but regardless if the same code works in everything else, you should report a bug and link to this issue. https://code.google.com/p/go/issues/entry – OneOfOne Sep 05 '14 at 14:37
  • @ixos that's differently a bug then, please report it and add a link to the bug report in your original post. – OneOfOne Sep 05 '14 at 16:41
  • The mount namespace of a process can only be changed as long as it is single threaded. As the golang runtime has already spun up, there might be already multiple threads, so the setns syscall will fail. – TheDiveO Sep 27 '19 at 14:50