所以我最近一直在玩网络命名空间。我整理了一个简单的代码,构建它并注意到发生了一些非常奇怪的事情。
代码如下:
package main
import (
"fmt"
"log"
"net"
"os"
"path"
"syscall"
)
const (
NsRunDir = "/var/run/netns"
SelfNetNs = "/proc/self/ns/net"
)
func main() {
netNsPath := path.Join(NsRunDir, "myns")
os.Mkdir(NsRunDir, 0755)
if err := syscall.Mount(NsRunDir, NsRunDir, "none", syscall.MS_BIND, ""); err != nil {
log.Fatalf("Could not create Network namespace: %s", err)
}
fd, err := syscall.Open(netNsPath, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_EXCL, 0)
if err != nil {
log.Fatalf("Could not create Network namespace: %s", err)
}
syscall.Close(fd)
if err := syscall.Unshare(syscall.CLONE_NEWNET); err != nil {
log.Fatalf("Could not clone new Network namespace: %s", err)
}
if err := syscall.Mount(SelfNetNs, netNsPath, "none", syscall.MS_BIND, ""); err != nil {
log.Fatalf("Could not Mount Network namespace: %s", err)
}
if err := syscall.Unmount(netNsPath, syscall.MNT_DETACH); err != nil {
log.Fatalf("Could not Unmount new Network namespace: %s", err)
}
if err := syscall.Unlink(netNsPath); err != nil {
log.Fatalf("Could not Unlink new Network namespace: %s", err)
}
ifcs, _ := net.Interfaces()
for _, ifc := range ifcs {
fmt.Printf("%#v\n", ifc)
}
}
现在,当您在 Trusty 14.04 上运行此代码时,您会看到一些奇怪的事情发生。当您连续多次运行二进制文件时会发生这种情况。
有时它会打印出所有主机的接口,有时它只是打印出一个环回接口,这意味着程序末尾的范围循环似乎在命名空间仍然附加时执行一次,有时当它已经被分离时。
我完全困惑为什么会发生这种情况,但我认为这要么是我的代码,要么我只是在程序执行或一些内核内容方面遗漏了一些东西。
任何帮助将不胜感激。
谢谢
更新 1: 所以看起来“奇怪”的行为与 golang 如何跨 OS 线程调度 goroutines 有关。所以你需要确保你处理好运行时。我的意思是,如果您将代码执行锁定到一个 OS 线程,您将获得一致的结果。您可以通过添加以下运行时包语句来做到这一点:
runtime.LockOSThread()
然而这仍然不能解决我的问题,但现在我认为这一切都归结为对命名空间的理解。我需要对此进行更多研究。
更新2: 为了让您了解为什么在运行一堆系统调用时应该使用上述操作系统线程锁并体验到正确行为的类似“奇怪”,请阅读这篇博文。它描述了运行时和 go 调度程序。它是为 go 1.1 编写的,但它仍然提供了很好的概述。