//go:build linux // +build linux package netlink import ( "bytes" "crypto/rand" "encoding/hex" "fmt" "io/ioutil" "log" "os" "os/exec" "runtime" "strings" "testing" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) type tearDownNetlinkTest func() func skipUnlessRoot(t testing.TB) { t.Helper() if os.Getuid() != 0 { t.Skip("Test requires root privileges.") } } func skipUnlessKModuleLoaded(t *testing.T, moduleNames ...string) { t.Helper() file, err := ioutil.ReadFile("/proc/modules") if err != nil { t.Fatal("Failed to open /proc/modules", err) } foundRequiredMods := make(map[string]bool) lines := strings.Split(string(file), "\n") for _, name := range moduleNames { foundRequiredMods[name] = false for _, line := range lines { n := strings.Split(line, " ")[0] if n == name { foundRequiredMods[name] = true break } } } failed := false for _, name := range moduleNames { if found, _ := foundRequiredMods[name]; !found { t.Logf("Test requires missing kmodule %q.", name) failed = true } } if failed { t.SkipNow() } } func setUpNetlinkTest(t testing.TB) tearDownNetlinkTest { skipUnlessRoot(t) // new temporary namespace so we don't pollute the host // lock thread since the namespace is thread local runtime.LockOSThread() var err error ns, err := netns.New() if err != nil { t.Fatal("Failed to create newns", ns) } return func() { ns.Close() runtime.UnlockOSThread() } } // setUpNamedNetlinkTest create a temporary named names space with a random name func setUpNamedNetlinkTest(t *testing.T) (string, tearDownNetlinkTest) { skipUnlessRoot(t) origNS, err := netns.Get() if err != nil { t.Fatal("Failed saving orig namespace") } // create a random name rnd := make([]byte, 4) if _, err := rand.Read(rnd); err != nil { t.Fatal("failed creating random ns name") } name := "netlinktest-" + hex.EncodeToString(rnd) ns, err := netns.NewNamed(name) if err != nil { t.Fatal("Failed to create new ns", err) } runtime.LockOSThread() cleanup := func() { ns.Close() netns.DeleteNamed(name) netns.Set(origNS) runtime.UnlockOSThread() } if err := netns.Set(ns); err != nil { cleanup() t.Fatal("Failed entering new namespace", err) } return name, cleanup } func setUpNetlinkTestWithLoopback(t *testing.T) tearDownNetlinkTest { skipUnlessRoot(t) runtime.LockOSThread() ns, err := netns.New() if err != nil { t.Fatal("Failed to create new netns", ns) } link, err := LinkByName("lo") if err != nil { t.Fatalf("Failed to find \"lo\" in new netns: %v", err) } if err := LinkSetUp(link); err != nil { t.Fatalf("Failed to bring up \"lo\" in new netns: %v", err) } return func() { ns.Close() runtime.UnlockOSThread() } } func setUpF(t *testing.T, path, value string) { file, err := os.Create(path) if err != nil { t.Fatalf("Failed to open %s: %s", path, err) } defer file.Close() file.WriteString(value) } func setUpMPLSNetlinkTest(t *testing.T) tearDownNetlinkTest { if _, err := os.Stat("/proc/sys/net/mpls/platform_labels"); err != nil { t.Skip("Test requires MPLS support.") } f := setUpNetlinkTest(t) setUpF(t, "/proc/sys/net/mpls/platform_labels", "1024") setUpF(t, "/proc/sys/net/mpls/conf/lo/input", "1") return f } func setUpSEG6NetlinkTest(t *testing.T) tearDownNetlinkTest { // check if SEG6 options are enabled in Kernel Config cmd := exec.Command("uname", "-r") var out bytes.Buffer cmd.Stdout = &out if err := cmd.Run(); err != nil { t.Fatal("Failed to run: uname -r") } s := []string{"/boot/config-", strings.TrimRight(out.String(), "\n")} filename := strings.Join(s, "") grepKey := func(key, fname string) (string, error) { cmd := exec.Command("grep", key, filename) var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() // "err != nil" if no line matched with grep return strings.TrimRight(out.String(), "\n"), err } key := string("CONFIG_IPV6_SEG6_LWTUNNEL=y") if _, err := grepKey(key, filename); err != nil { msg := "Skipped test because it requires SEG6_LWTUNNEL support." log.Println(msg) t.Skip(msg) } // Add CONFIG_IPV6_SEG6_HMAC to support seg6_hamc // key := string("CONFIG_IPV6_SEG6_HMAC=y") return setUpNetlinkTest(t) } func setUpNetlinkTestWithKModule(t *testing.T, moduleNames ...string) tearDownNetlinkTest { skipUnlessKModuleLoaded(t, moduleNames...) return setUpNetlinkTest(t) } func setUpNamedNetlinkTestWithKModule(t *testing.T, moduleNames ...string) (string, tearDownNetlinkTest) { file, err := ioutil.ReadFile("/proc/modules") if err != nil { t.Fatal("Failed to open /proc/modules", err) } foundRequiredMods := make(map[string]bool) lines := strings.Split(string(file), "\n") for _, name := range moduleNames { foundRequiredMods[name] = false for _, line := range lines { n := strings.Split(line, " ")[0] if n == name { foundRequiredMods[name] = true break } } } failed := false for _, name := range moduleNames { if found, _ := foundRequiredMods[name]; !found { t.Logf("Test requires missing kmodule %q.", name) failed = true } } if failed { t.SkipNow() } return setUpNamedNetlinkTest(t) } func remountSysfs() error { if err := unix.Mount("", "/", "none", unix.MS_SLAVE|unix.MS_REC, ""); err != nil { return err } if err := unix.Unmount("/sys", unix.MNT_DETACH); err != nil { return err } return unix.Mount("", "/sys", "sysfs", 0, "") } func minKernelRequired(t *testing.T, kernel, major int) { t.Helper() k, m, err := KernelVersion() if err != nil { t.Fatal(err) } if k < kernel || k == kernel && m < major { t.Skipf("Host Kernel (%d.%d) does not meet test's minimum required version: (%d.%d)", k, m, kernel, major) } } func KernelVersion() (kernel, major int, err error) { uts := unix.Utsname{} if err = unix.Uname(&uts); err != nil { return } ba := make([]byte, 0, len(uts.Release)) for _, b := range uts.Release { if b == 0 { break } ba = append(ba, byte(b)) } var rest string if n, _ := fmt.Sscanf(string(ba), "%d.%d%s", &kernel, &major, &rest); n < 2 { err = fmt.Errorf("can't parse kernel version in %q", string(ba)) } return } func TestMain(m *testing.M) { nl.EnableErrorMessageReporting = true os.Exit(m.Run()) }