diff --git a/link.go b/link.go index 5c34395..75fce01 100644 --- a/link.go +++ b/link.go @@ -1,8 +1,6 @@ package netlink -import ( - "net" -) +import "net" // Link represents a link device from netlink. Shared link attributes // like name may be retrieved using the Attrs() method. Unique data @@ -149,7 +147,29 @@ func (vxlan *Vxlan) Type() string { return "vxlan" } +type IPVlanMode uint16 + +const ( + IPVLAN_MODE_L2 IPVlanMode = iota + IPVLAN_MODE_L3 = iota + IPVLAN_MODE_MAX = iota +) + +type IPVlan struct { + LinkAttrs + Mode IPVlanMode +} + +func (ipvlan *IPVlan) Attrs() *LinkAttrs { + return &ipvlan.LinkAttrs +} + +func (ipvlan *IPVlan) Type() string { + return "ipvlan" +} + // iproute2 supported devices; // vlan | veth | vcan | dummy | ifb | macvlan | macvtap | -// can | bridge | bond | ipoib | ip6tnl | ipip | sit | -// vxlan | gre | gretap | ip6gre | ip6gretap | vti +// bridge | bond | ipoib | ip6tnl | ipip | sit | vxlan | +// gre | gretap | ip6gre | ip6gretap | vti | nlmon | +// bond_slave | ipvlan diff --git a/link_linux.go b/link_linux.go index 692fe7f..b037481 100644 --- a/link_linux.go +++ b/link_linux.go @@ -300,6 +300,8 @@ func LinkAdd(link Link) error { native.PutUint32(b, uint32(base.ParentIndex)) data := nl.NewRtAttr(syscall.IFLA_LINK, b) req.AddData(data) + } else if link.Type() == "ipvlan" { + return fmt.Errorf("Can't create ipvlan link without ParentIndex") } nameData := nl.NewRtAttr(syscall.IFLA_IFNAME, nl.ZeroTerminated(base.Name)) @@ -331,6 +333,9 @@ func LinkAdd(link Link) error { } } else if vxlan, ok := link.(*Vxlan); ok { addVxlanAttrs(vxlan, linkInfo) + } else if ipv, ok := link.(*IPVlan); ok { + data := nl.NewRtAttrChild(linkInfo, nl.IFLA_INFO_DATA, nil) + nl.NewRtAttrChild(data, nl.IFLA_IPVLAN_MODE, nl.Uint16Attr(uint16(ipv.Mode))) } req.AddData(linkInfo) @@ -476,6 +481,8 @@ func linkDeserialize(m []byte) (Link, error) { link = &Veth{} case "vxlan": link = &Vxlan{} + case "ipvlan": + link = &IPVlan{} default: link = &Generic{LinkType: linkType} } @@ -489,6 +496,8 @@ func linkDeserialize(m []byte) (Link, error) { parseVlanData(link, data) case "vxlan": parseVxlanData(link, data) + case "ipvlan": + parseIPVlanData(link, data) } } } @@ -609,6 +618,16 @@ func parseVxlanData(link Link, data []syscall.NetlinkRouteAttr) { } } +func parseIPVlanData(link Link, data []syscall.NetlinkRouteAttr) { + ipv := link.(*IPVlan) + for _, datum := range data { + if datum.Attr.Type == nl.IFLA_IPVLAN_MODE { + ipv.Mode = IPVlanMode(native.Uint32(datum.Value[0:4])) + return + } + } +} + // copied from pkg/net_linux.go func linkFlags(rawFlags uint32) net.Flags { var f net.Flags diff --git a/link_test.go b/link_test.go index 831684c..5752e82 100644 --- a/link_test.go +++ b/link_test.go @@ -81,6 +81,16 @@ func testLinkAddDel(t *testing.T, link Link) { compareVxlan(t, vxlan, other) } + if ipv, ok := link.(*IPVlan); ok { + other, ok := result.(*IPVlan) + if !ok { + t.Fatal("Result of create is not a vxlan") + } + if ipv.Mode != other.Mode { + t.Fatalf("Got unexpected mode: %d, expected: %d", other.Mode, ipv.Mode) + } + } + if err = LinkDel(link); err != nil { t.Fatal(err) } @@ -378,6 +388,63 @@ func TestLinkAddDelVxlan(t *testing.T) { } } +func TestLinkAddDelIPVlanL2(t *testing.T) { + tearDown := setUpNetlinkTest(t) + defer tearDown() + parent := &Dummy{LinkAttrs{Name: "foo"}} + if err := LinkAdd(parent); err != nil { + t.Fatal(err) + } + + ipv := IPVlan{ + LinkAttrs: LinkAttrs{ + Name: "bar", + ParentIndex: parent.Index, + }, + Mode: IPVLAN_MODE_L2, + } + + testLinkAddDel(t, &ipv) +} + +func TestLinkAddDelIPVlanL3(t *testing.T) { + tearDown := setUpNetlinkTest(t) + defer tearDown() + parent := &Dummy{LinkAttrs{Name: "foo"}} + if err := LinkAdd(parent); err != nil { + t.Fatal(err) + } + + ipv := IPVlan{ + LinkAttrs: LinkAttrs{ + Name: "bar", + ParentIndex: parent.Index, + }, + Mode: IPVLAN_MODE_L3, + } + + testLinkAddDel(t, &ipv) +} + +func TestLinkAddDelIPVlanNoParent(t *testing.T) { + tearDown := setUpNetlinkTest(t) + defer tearDown() + + ipv := IPVlan{ + LinkAttrs: LinkAttrs{ + Name: "bar", + }, + Mode: IPVLAN_MODE_L3, + } + err := LinkAdd(&ipv) + if err == nil { + t.Fatal("Add should fail if ipvlan creating without ParentIndex") + } + if err.Error() != "Can't create ipvlan link without ParentIndex" { + t.Fatalf("Error should be about missing ParentIndex, got %q", err) + } +} + func TestLinkByIndex(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() diff --git a/netlink_test.go b/netlink_test.go index 718448b..3292b75 100644 --- a/netlink_test.go +++ b/netlink_test.go @@ -1,11 +1,12 @@ package netlink import ( - "github.com/vishvananda/netns" "log" "os" "runtime" "testing" + + "github.com/vishvananda/netns" ) type tearDownNetlinkTest func() diff --git a/nl/link_linux.go b/nl/link_linux.go index 0217a49..6679b76 100644 --- a/nl/link_linux.go +++ b/nl/link_linux.go @@ -69,6 +69,12 @@ const ( IFLA_BRPORT_MAX = IFLA_BRPORT_UNICAST_FLOOD ) +const ( + IFLA_IPVLAN_UNSPEC = iota + IFLA_IPVLAN_MODE = iota + IFLA_IPVLAN_MAX = IFLA_IPVLAN_MODE +) + const ( // not defined in syscall IFLA_NET_NS_FD = 28