diff --git a/link_linux.go b/link_linux.go index 9d670f4c9..84640d7a9 100644 --- a/link_linux.go +++ b/link_linux.go @@ -2113,6 +2113,60 @@ func (h *Handle) LinkByIndex(index int) (Link, error) { return execGetLink(req) } +// LinkByIndexAndNsid finds a link by index in a different namespace and returns a pointer to the object +func LinkByIndexAndNsid(index int, nsid int) (Link, error) { + return pkgHandle.LinkByIndexAndNsid(index, nsid) +} + +// LinkByIndexAndNsid finds a link by index in a different namespace and returns a pointer to the object +func (h *Handle) LinkByIndexAndNsid(index int, nsid int) (Link, error) { + req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_ACK) + + msg := nl.NewIfInfomsg(unix.AF_UNSPEC) + msg.Index = int32(index) + req.AddData(msg) + + attr := nl.NewRtAttr(unix.IFLA_TARGET_NETNSID, nl.Uint32Attr(uint32(nsid))) + req.AddData(attr) + + if h.options.collectVFInfo { + attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)) + req.AddData(attr) + } + + return execGetLink(req) +} + +// LinkByNameAndNsid finds a link by name in a different namespace and returns a pointer to the object +func LinkByNameAndNsid(name string, nsid int) (Link, error) { + return pkgHandle.LinkByNameAndNsid(name, nsid) +} + +// LinkByNameAndNsid finds a link by name in a different namespace and returns a pointer to the object +func (h *Handle) LinkByNameAndNsid(name string, nsid int) (Link, error) { + req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_ACK) + + msg := nl.NewIfInfomsg(unix.AF_UNSPEC) + req.AddData(msg) + + attr := nl.NewRtAttr(unix.IFLA_TARGET_NETNSID, nl.Uint32Attr(uint32(nsid))) + req.AddData(attr) + + if h.options.collectVFInfo { + attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)) + req.AddData(attr) + } + + nameData := nl.NewRtAttr(unix.IFLA_IFNAME, nl.ZeroTerminated(name)) + if len(name) > 15 { + nameData = nl.NewRtAttr(unix.IFLA_ALT_IFNAME, nl.ZeroTerminated(name)) + } + req.AddData(nameData) + + link, err := execGetLink(req) + return link, err +} + func execGetLink(req *nl.NetlinkRequest) (Link, error) { msgs, err := req.Execute(unix.NETLINK_ROUTE, 0) if err != nil { diff --git a/link_test.go b/link_test.go index d00f85bc7..fcd0ab902 100644 --- a/link_test.go +++ b/link_test.go @@ -2220,6 +2220,38 @@ func TestLinkByIndex(t *testing.T) { } } +func TestLinkByIndexAndNsid(t *testing.T) { + minKernelRequired(t, 4, 15) + t.Cleanup(setUpNetlinkTest(t)) + nsId, linkIdx, linkName, cleanup := setUpNamespaceWithLink(t) + t.Cleanup(cleanup) + + link, err := LinkByIndexAndNsid(linkIdx, nsId) + if err != nil { + t.Fatal(err) + } + + if link.Attrs().Name != linkName { + t.Fatal("did not get the correct link") + } +} + +func TestLinkByNameAndNsid(t *testing.T) { + minKernelRequired(t, 4, 15) + t.Cleanup(setUpNetlinkTest(t)) + nsId, linkIdx, linkName, cleanup := setUpNamespaceWithLink(t) + t.Cleanup(cleanup) + + link, err := LinkByNameAndNsid(linkName, nsId) + if err != nil { + t.Fatal(err) + } + + if link.Attrs().Index != linkIdx { + t.Fatal("did not get the correct link") + } +} + func TestLinkSet(t *testing.T) { t.Cleanup(setUpNetlinkTest(t)) diff --git a/netlink_test.go b/netlink_test.go index 14e0f9cbb..792051855 100644 --- a/netlink_test.go +++ b/netlink_test.go @@ -7,6 +7,7 @@ import ( "bytes" "crypto/rand" "encoding/hex" + "encoding/json" "fmt" "io/ioutil" "log" @@ -264,6 +265,82 @@ func setUpNamedNetlinkTestWithKModule(t *testing.T, moduleNames ...string) (stri return setUpNamedNetlinkTest(t) } +func setUpNamespaceWithLink(t *testing.T) (nsId int, linkId int, linkName string, teardown tearDownNetlinkTest) { + skipUnlessRoot(t) + nsName, cleanupNs := setUpNamedNetlinkTest(t) + + ok := false + defer func() { + if !ok { + cleanupNs() + } + }() + + cmd := exec.Command("ip", "netns", "set", nsName, "auto") + var stderr bytes.Buffer + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + if strings.Contains(stderr.String(), "Invalid") { + t.Skip("iproute2 version is too old") + } + + t.Fatalf("Failed to run: ip netns set %s auto", nsName) + } + + // create a random name + rnd := make([]byte, 4) + if _, err := rand.Read(rnd); err != nil { + t.Fatal("failed creating random link name") + } + name := hex.EncodeToString(rnd) + + if err := exec.Command("ip", "netns", "exec", nsName, "ip", "link", "add", name, "type", "dummy").Run(); err != nil { + t.Fatalf("Failed to run: ip netns exec %s ip link add %s type dummy", nsName, name) + } + + cmd = exec.Command("ip", "netns", "exec", nsName, "ip", "--json", "link", "show", name) + var stdout bytes.Buffer + stderr.Reset() + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + if strings.Contains(stderr.String(), "unknown") { + t.Skip("iproute2 version is too old") + } + + t.Fatalf("Failed to run: %s", cmd) + } + + var ipOutput []struct { + Index int `json:"ifindex"` + } + + err := json.Unmarshal(stdout.Bytes(), &ipOutput) + if err != nil { + t.Fatalf("Failed to parse json") + } + + if len(ipOutput) != 1 { + t.Fatalf("failed to get created link") + } + + ns, err := netns.GetFromName(nsName) + if err != nil { + t.Fatalf("getting ns: %v", err) + } + + defer ns.Close() + + nsid, err := GetNetNsIdByFd(int(ns)) + if err != nil { + t.Fatalf("getting nsid: %v", err) + } + + ok = true + + return nsid, ipOutput[0].Index, name, cleanupNs +} + func remountSysfs() error { if err := unix.Mount("", "/", "none", unix.MS_SLAVE|unix.MS_REC, ""); err != nil { return err