diff --git a/link.go b/link.go index 611b4d2..a123349 100644 --- a/link.go +++ b/link.go @@ -289,6 +289,21 @@ func (bridge *Bridge) Type() string { return "bridge" } +// OpenvSwitch links are Open vSwitch bridge devices. +// Note: their lifecycle is typically managed by OVS (OVSDB/ovs-vsctl), +// while netlink is used to query/link them. +type OpenvSwitch struct { + LinkAttrs +} + +func (ovs *OpenvSwitch) Attrs() *LinkAttrs { + return &ovs.LinkAttrs +} + +func (ovs *OpenvSwitch) Type() string { + return "openvswitch" +} + // Vlan links have ParentIndex set in their Attrs() type Vlan struct { LinkAttrs @@ -1066,6 +1081,12 @@ func (v *VrfSlave) SlaveType() string { return "vrf" } +type OpenvSwitchSlave struct{} + +func (o *OpenvSwitchSlave) SlaveType() string { + return "openvswitch" +} + // Geneve devices must specify RemoteIP and ID (VNI) on create // https://github.com/torvalds/linux/blob/47ec5303d73ea344e84f46660fff693c57641386/drivers/net/geneve.c#L1209-L1223 type Geneve struct { diff --git a/link_linux.go b/link_linux.go index 01ec962..119b064 100644 --- a/link_linux.go +++ b/link_linux.go @@ -2188,6 +2188,8 @@ func LinkDeserialize(hdr *unix.NlMsghdr, m []byte) (Link, error) { link = &Ifb{} case "bridge": link = &Bridge{} + case "openvswitch": + link = &OpenvSwitch{} case "vlan": link = &Vlan{} case "netkit": @@ -2308,6 +2310,8 @@ func LinkDeserialize(hdr *unix.NlMsghdr, m []byte) (Link, error) { linkSlave = &BondSlave{} case "vrf": linkSlave = &VrfSlave{} + case "openvswitch": + linkSlave = &OpenvSwitchSlave{} } case nl.IFLA_INFO_SLAVE_DATA: diff --git a/link_test.go b/link_test.go index 595f64e..a2ebcad 100644 --- a/link_test.go +++ b/link_test.go @@ -2240,6 +2240,49 @@ func TestLinkByIndex(t *testing.T) { } } +func TestOpenvSwitch(t *testing.T) { + skipUnlessRoot(t) + skipUnlessKModuleLoaded(t, "openvswitch") + + // This test validates host OVS state, so it must not create an isolated netns + // via setUpNetlinkTest(), otherwise host OVS links will be invisible. + link, err := LinkByName("ovs-system") + if err != nil { + if _, ok := err.(LinkNotFoundError); ok { + t.Skip("ovs-system interface not found") + } + t.Fatal("Failed getting link: ", err) + } + + ovsSystem, ok := link.(*OpenvSwitch) + if !ok { + t.Fatalf("unexpected link type: %T", link) + } + + links, err := LinkList() + if err != nil { + t.Fatal(err) + } + + for _, l := range links { + if l.Attrs().Name == ovsSystem.Name { + if _, ok := l.(*OpenvSwitch); !ok { + t.Fatalf("unexpected link type: %T", l) + } + } + + if l.Attrs().Slave != nil && l.Attrs().Slave.SlaveType() == "openvswitch" { + if _, ok := l.Attrs().Slave.(*OpenvSwitchSlave); !ok { + t.Fatalf("unexpected slave type: %T", l.Attrs().Slave) + } + + if l.Attrs().MasterIndex != ovsSystem.Index { + t.Fatalf("Got unexpected master index: %d, expected: %d", l.Attrs().MasterIndex, ovsSystem.Index) + } + } + } +} + func TestLinkSet(t *testing.T) { t.Cleanup(setUpNetlinkTest(t))