diff --git a/link.go b/link.go index 37f5a5a4..e1695916 100644 --- a/link.go +++ b/link.go @@ -357,6 +357,46 @@ func (tuntap *Tuntap) Type() string { return "tuntap" } +type NetkitMode uint32 + +const ( + NETKIT_MODE_L2 NetkitMode = iota + NETKIT_MODE_L3 +) + +type NetkitPolicy int + +const ( + NETKIT_POLICY_FORWARD NetkitPolicy = 0 + NETKIT_POLICY_BLACKHOLE NetkitPolicy = 2 +) + +func (n *Netkit) IsPrimary() bool { + return n.isPrimary +} + +// SetPeerAttrs will not take effect if trying to modify an existing netkit device +func (n *Netkit) SetPeerAttrs(Attrs *LinkAttrs) { + n.peerLinkAttrs = *Attrs +} + +type Netkit struct { + LinkAttrs + Mode NetkitMode + Policy NetkitPolicy + PeerPolicy NetkitPolicy + isPrimary bool + peerLinkAttrs LinkAttrs +} + +func (n *Netkit) Attrs() *LinkAttrs { + return &n.LinkAttrs +} + +func (n *Netkit) Type() string { + return "netkit" +} + // Veth devices must specify PeerName on create type Veth struct { LinkAttrs diff --git a/link_linux.go b/link_linux.go index 202c2b74..96468c4f 100644 --- a/link_linux.go +++ b/link_linux.go @@ -1634,6 +1634,10 @@ func (h *Handle) linkModify(link Link, flags int) error { if link.VlanProtocol != VLAN_PROTOCOL_UNKNOWN { data.AddRtAttr(nl.IFLA_VLAN_PROTOCOL, htons(uint16(link.VlanProtocol))) } + case *Netkit: + if err := addNetkitAttrs(link, linkInfo, flags); err != nil { + return err + } case *Veth: data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) peer := data.AddRtAttr(nl.VETH_INFO_PEER, nil) @@ -1947,6 +1951,8 @@ func LinkDeserialize(hdr *unix.NlMsghdr, m []byte) (Link, error) { link = &Bridge{} case "vlan": link = &Vlan{} + case "netkit": + link = &Netkit{} case "veth": link = &Veth{} case "wireguard": @@ -2004,6 +2010,8 @@ func LinkDeserialize(hdr *unix.NlMsghdr, m []byte) (Link, error) { return nil, err } switch linkType { + case "netkit": + parseNetkitData(link, data) case "vlan": parseVlanData(link, data) case "vxlan": @@ -2544,6 +2552,80 @@ func (h *Handle) LinkSetGroup(link Link, group int) error { return err } +func addNetkitAttrs(nk *Netkit, linkInfo *nl.RtAttr, flag int) error { + if nk.peerLinkAttrs.HardwareAddr != nil || nk.HardwareAddr != nil { + return fmt.Errorf("netkit doesn't support setting Ethernet") + } + + data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) + // Kernel will return error if trying to change the mode of an existing netkit device + data.AddRtAttr(nl.IFLA_NETKIT_MODE, nl.Uint32Attr(uint32(nk.Mode))) + data.AddRtAttr(nl.IFLA_NETKIT_POLICY, nl.Uint32Attr(uint32(nk.Policy))) + data.AddRtAttr(nl.IFLA_NETKIT_PEER_POLICY, nl.Uint32Attr(uint32(nk.PeerPolicy))) + + if (flag & unix.NLM_F_EXCL) == 0 { + // Modifying peer link attributes will not take effect + return nil + } + + peer := data.AddRtAttr(nl.IFLA_NETKIT_PEER_INFO, nil) + msg := nl.NewIfInfomsg(unix.AF_UNSPEC) + if nk.peerLinkAttrs.Flags&net.FlagUp != 0 { + msg.Change = unix.IFF_UP + msg.Flags = unix.IFF_UP + } + if nk.peerLinkAttrs.Index != 0 { + msg.Index = int32(nk.peerLinkAttrs.Index) + } + peer.AddChild(msg) + if nk.peerLinkAttrs.Name != "" { + peer.AddRtAttr(unix.IFLA_IFNAME, nl.ZeroTerminated(nk.peerLinkAttrs.Name)) + } + if nk.peerLinkAttrs.MTU > 0 { + peer.AddRtAttr(unix.IFLA_MTU, nl.Uint32Attr(uint32(nk.peerLinkAttrs.MTU))) + } + if nk.peerLinkAttrs.GSOMaxSegs > 0 { + peer.AddRtAttr(unix.IFLA_GSO_MAX_SEGS, nl.Uint32Attr(nk.peerLinkAttrs.GSOMaxSegs)) + } + if nk.peerLinkAttrs.GSOMaxSize > 0 { + peer.AddRtAttr(unix.IFLA_GSO_MAX_SIZE, nl.Uint32Attr(nk.peerLinkAttrs.GSOMaxSize)) + } + if nk.peerLinkAttrs.GSOIPv4MaxSize > 0 { + peer.AddRtAttr(unix.IFLA_GSO_IPV4_MAX_SIZE, nl.Uint32Attr(nk.peerLinkAttrs.GSOIPv4MaxSize)) + } + if nk.peerLinkAttrs.GROIPv4MaxSize > 0 { + peer.AddRtAttr(unix.IFLA_GRO_IPV4_MAX_SIZE, nl.Uint32Attr(nk.peerLinkAttrs.GROIPv4MaxSize)) + } + if nk.peerLinkAttrs.Namespace != nil { + switch ns := nk.peerLinkAttrs.Namespace.(type) { + case NsPid: + peer.AddRtAttr(unix.IFLA_NET_NS_PID, nl.Uint32Attr(uint32(ns))) + case NsFd: + peer.AddRtAttr(unix.IFLA_NET_NS_FD, nl.Uint32Attr(uint32(ns))) + } + } + return nil +} + +func parseNetkitData(link Link, data []syscall.NetlinkRouteAttr) { + netkit := link.(*Netkit) + for _, datum := range data { + switch datum.Attr.Type { + case nl.IFLA_NETKIT_PRIMARY: + isPrimary := datum.Value[0:1][0] + if isPrimary != 0 { + netkit.isPrimary = true + } + case nl.IFLA_NETKIT_MODE: + netkit.Mode = NetkitMode(native.Uint32(datum.Value[0:4])) + case nl.IFLA_NETKIT_POLICY: + netkit.Policy = NetkitPolicy(native.Uint32(datum.Value[0:4])) + case nl.IFLA_NETKIT_PEER_POLICY: + netkit.PeerPolicy = NetkitPolicy(native.Uint32(datum.Value[0:4])) + } + } +} + func parseVlanData(link Link, data []syscall.NetlinkRouteAttr) { vlan := link.(*Vlan) for _, datum := range data { diff --git a/link_test.go b/link_test.go index c1d1255c..37b0cea6 100644 --- a/link_test.go +++ b/link_test.go @@ -67,6 +67,44 @@ func testLinkAddDel(t *testing.T, link Link) { } } + if resultPrimary, ok := result.(*Netkit); ok { + if inputPrimary, ok := link.(*Netkit); ok { + if resultPrimary.Policy != inputPrimary.Policy { + t.Fatalf("Policy is %d, should be %d", int(resultPrimary.Policy), int(inputPrimary.Policy)) + } + if resultPrimary.PeerPolicy != inputPrimary.PeerPolicy { + t.Fatalf("Peer Policy is %d, should be %d", int(resultPrimary.PeerPolicy), int(inputPrimary.PeerPolicy)) + } + if resultPrimary.Mode != inputPrimary.Mode { + t.Fatalf("Mode is %d, should be %d", int(resultPrimary.Mode), int(inputPrimary.Mode)) + } + + if inputPrimary.peerLinkAttrs.Name != "" { + var resultPeer *Netkit + pLink, err := LinkByName(inputPrimary.peerLinkAttrs.Name) + if err != nil { + t.Fatalf("Failed to get Peer netkit %s", inputPrimary.peerLinkAttrs.Name) + } + if resultPeer, ok = pLink.(*Netkit); !ok { + t.Fatalf("Peer %s is incorrect type", inputPrimary.peerLinkAttrs.Name) + } + if resultPrimary.PeerPolicy != resultPeer.Policy { + t.Fatalf("Peer Policy from primary is %d, should be %d", int(resultPrimary.PeerPolicy), int(resultPeer.Policy)) + } + if resultPeer.PeerPolicy != resultPrimary.Policy { + t.Fatalf("PeerPolicy from peer is %d, should be %d", int(resultPeer.PeerPolicy), int(resultPrimary.Policy)) + } + if resultPrimary.Mode != resultPeer.Mode { + t.Fatalf("Peer Mode from primary is %d, should be %d", int(resultPrimary.Mode), int(resultPeer.Mode)) + } + if resultPrimary.IsPrimary() == resultPeer.IsPrimary() { + t.Fatalf("Both primary and peer device has the same value in IsPrimary() %t", resultPrimary.IsPrimary()) + } + } + } + + } + if veth, ok := result.(*Veth); ok { if rBase.TxQLen != base.TxQLen { t.Fatalf("qlen is %d, should be %d", rBase.TxQLen, base.TxQLen) @@ -108,15 +146,19 @@ func testLinkAddDel(t *testing.T, link Link) { } } } - } else { - // recent kernels set the parent index for veths in the response - if rBase.ParentIndex == 0 && base.ParentIndex != 0 { - t.Fatalf("Created link doesn't have parent %d but it should", base.ParentIndex) - } else if rBase.ParentIndex != 0 && base.ParentIndex == 0 { - t.Fatalf("Created link has parent %d but it shouldn't", rBase.ParentIndex) - } else if rBase.ParentIndex != 0 && base.ParentIndex != 0 { - if rBase.ParentIndex != base.ParentIndex { - t.Fatalf("Link.ParentIndex doesn't match %d != %d", rBase.ParentIndex, base.ParentIndex) + } + + if _, ok := result.(*Veth); !ok { + if _, ok := result.(*Netkit); !ok { + // recent kernels set the parent index for veths/netkit in the response + if rBase.ParentIndex == 0 && base.ParentIndex != 0 { + t.Fatalf("Created link doesn't have parent %d but it should", base.ParentIndex) + } else if rBase.ParentIndex != 0 && base.ParentIndex == 0 { + t.Fatalf("Created link has parent %d but it shouldn't", rBase.ParentIndex) + } else if rBase.ParentIndex != 0 && base.ParentIndex != 0 { + if rBase.ParentIndex != base.ParentIndex { + t.Fatalf("Link.ParentIndex doesn't match %d != %d", rBase.ParentIndex, base.ParentIndex) + } } } } @@ -868,6 +910,99 @@ func TestLinkAddDelMacvtap(t *testing.T) { } } +func TestNetkitPeerNs(t *testing.T) { + minKernelRequired(t, 6, 7) + tearDown := setUpNetlinkTest(t) + defer tearDown() + + basens, err := netns.Get() + if err != nil { + t.Fatal("Failed to get basens") + } + defer basens.Close() + + nsOne, err := netns.New() + if err != nil { + t.Fatal("Failed to create nsOne") + } + defer nsOne.Close() + + nsTwo, err := netns.New() + if err != nil { + t.Fatal("Failed to create nsTwo") + } + defer nsTwo.Close() + + netkit := &Netkit{ + LinkAttrs: LinkAttrs{ + Name: "foo", + Namespace: NsFd(basens), + }, + Mode: NETKIT_MODE_L2, + Policy: NETKIT_POLICY_FORWARD, + PeerPolicy: NETKIT_POLICY_BLACKHOLE, + } + peerAttr := &LinkAttrs{ + Name: "bar", + Namespace: NsFd(nsOne), + } + netkit.SetPeerAttrs(peerAttr) + + if err := LinkAdd(netkit); err != nil { + t.Fatal(err) + } + + _, err = LinkByName("bar") + if err == nil { + t.Fatal("netkit link bar is in nsTwo") + } + + _, err = LinkByName("foo") + if err == nil { + t.Fatal("netkit link foo is in nsTwo") + } + + err = netns.Set(basens) + if err != nil { + t.Fatal("Failed to set basens") + } + + _, err = LinkByName("foo") + if err != nil { + t.Fatal("netkit link foo is not in basens") + } + + err = netns.Set(nsOne) + if err != nil { + t.Fatal("Failed to set nsOne") + } + + _, err = LinkByName("bar") + if err != nil { + t.Fatal("netkit link bar is not in nsOne") + } +} + +func TestLinkAddDelNetkit(t *testing.T) { + minKernelRequired(t, 6, 7) + tearDown := setUpNetlinkTest(t) + defer tearDown() + + netkit := &Netkit{ + LinkAttrs: LinkAttrs{ + Name: "foo", + }, + Mode: NETKIT_MODE_L2, + Policy: NETKIT_POLICY_FORWARD, + PeerPolicy: NETKIT_POLICY_BLACKHOLE, + } + peerAttr := &LinkAttrs{ + Name: "bar", + } + netkit.SetPeerAttrs(peerAttr) + testLinkAddDel(t, netkit) +} + func TestLinkAddDelVeth(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() diff --git a/nl/link_linux.go b/nl/link_linux.go index fc04e47f..6cb118fb 100644 --- a/nl/link_linux.go +++ b/nl/link_linux.go @@ -31,6 +31,16 @@ const ( IFLA_VLAN_MAX = IFLA_VLAN_PROTOCOL ) +const ( + IFLA_NETKIT_UNSPEC = iota + IFLA_NETKIT_PEER_INFO + IFLA_NETKIT_PRIMARY + IFLA_NETKIT_POLICY + IFLA_NETKIT_PEER_POLICY + IFLA_NETKIT_MODE + IFLA_NETKIT_MAX = IFLA_NETKIT_MODE +) + const ( VETH_INFO_UNSPEC = iota VETH_INFO_PEER