From 3e8eb11030a429bb78dc5d9b1273a1bcdfc8e791 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Sun, 28 Mar 2021 11:16:50 -0600 Subject: [PATCH] Support STP parameters in bridge Added support for some basic spanning tree protocol configuration parameters on the bridge device. * vlan protocol * stp state * forward delay * max age --- go.sum | 1 - link.go | 4 + link_linux.go | 76 ++++++++++ link_test.go | 387 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 467 insertions(+), 1 deletion(-) diff --git a/go.sum b/go.sum index ed5d3089..5946b7f7 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -golang.org/x/sys v0.0.0-20200217220822-9197077df867 h1:JoRuNIf+rpHl+VhScRQQvzbHed86tKkqwPMV34T8myw= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/link.go b/link.go index 32ca7cd6..22e94df8 100644 --- a/link.go +++ b/link.go @@ -262,6 +262,10 @@ type Bridge struct { AgeingTime *uint32 HelloTime *uint32 VlanFiltering *bool + StpState *uint32 + VlanProtocol *string + ForwardDelay *uint32 + MaxAge *uint32 } func (bridge *Bridge) Attrs() *LinkAttrs { diff --git a/link_linux.go b/link_linux.go index 3b959299..2f4f3c0d 100644 --- a/link_linux.go +++ b/link_linux.go @@ -303,6 +303,46 @@ func (h *Handle) BridgeSetVlanFiltering(link Link, on bool) error { return h.linkModify(bridge, unix.NLM_F_ACK) } +func BridgeSetVlanProtocol(link Link, protocol string) error { + return pkgHandle.BridgeSetVlanProtocol(link, protocol) +} + +func (h *Handle) BridgeSetVlanProtocol(link Link, protocol string) error { + bridge := link.(*Bridge) + bridge.VlanProtocol = &protocol + return h.linkModify(bridge, unix.NLM_F_ACK) +} + +func (h *Handle) BridgeSetStpState(link Link, state uint32) error { + bridge := link.(*Bridge) + bridge.StpState = &state + return h.linkModify(bridge, unix.NLM_F_ACK) +} + +func BridgeSetStpState(link Link, state uint32) error { + return pkgHandle.BridgeSetStpState(link, state) +} + +func (h *Handle) BridgeSetForwardDelay(link Link, delay uint32) error { + bridge := link.(*Bridge) + bridge.ForwardDelay = &delay + return h.linkModify(bridge, unix.NLM_F_ACK) +} + +func BridgeSetForwardDelay(link Link, delay uint32) error { + return pkgHandle.BridgeSetForwardDelay(link, delay) +} + +func (h *Handle) BridgeSetMaxAge(link Link, maxAge uint32) error { + bridge := link.(*Bridge) + bridge.MaxAge = &maxAge + return h.linkModify(bridge, unix.NLM_F_ACK) +} + +func BridgeSetMaxAge(link Link, maxAge uint32) error { + return pkgHandle.BridgeSetMaxAge(link, maxAge) +} + func SetPromiscOn(link Link) error { return pkgHandle.SetPromiscOn(link) } @@ -2985,6 +3025,23 @@ func addBridgeAttrs(bridge *Bridge, linkInfo *nl.RtAttr) { if bridge.VlanFiltering != nil { data.AddRtAttr(nl.IFLA_BR_VLAN_FILTERING, boolToByte(*bridge.VlanFiltering)) } + if bridge.StpState != nil { + data.AddRtAttr(nl.IFLA_BR_STP_STATE, nl.Uint32Attr(*bridge.StpState)) + } + if bridge.ForwardDelay != nil { + data.AddRtAttr(nl.IFLA_BR_FORWARD_DELAY, nl.Uint32Attr(*bridge.ForwardDelay)) + } + if bridge.MaxAge != nil { + data.AddRtAttr(nl.IFLA_BR_MAX_AGE, nl.Uint32Attr(*bridge.MaxAge)) + } + if bridge.VlanProtocol != nil { + switch *bridge.VlanProtocol { + case "802.1Q": + data.AddRtAttr(nl.IFLA_BR_VLAN_PROTOCOL, htons(uint16(unix.ETH_P_8021Q))) + case "802.1ad": + data.AddRtAttr(nl.IFLA_BR_VLAN_PROTOCOL, htons(uint16(unix.ETH_P_8021AD))) + } + } } func parseBridgeData(bridge Link, data []syscall.NetlinkRouteAttr) { @@ -3003,6 +3060,25 @@ func parseBridgeData(bridge Link, data []syscall.NetlinkRouteAttr) { case nl.IFLA_BR_VLAN_FILTERING: vlanFiltering := datum.Value[0] == 1 br.VlanFiltering = &vlanFiltering + case nl.IFLA_BR_VLAN_PROTOCOL: + vlanProtocolCode := native.Uint16(datum.Value[0:2]) + switch vlanProtocolCode { + case unix.ETH_P_8021Q: + vlanProtocol := "802.1Q" + br.VlanProtocol = &vlanProtocol + case unix.ETH_P_8021AD: + vlanProtocol := "802.1ad" + br.VlanProtocol = &vlanProtocol + } + case nl.IFLA_BR_STP_STATE: + stpState := native.Uint32(datum.Value[0:4]) + br.StpState = &stpState + case nl.IFLA_BR_FORWARD_DELAY: + forwardingDelay := native.Uint32(datum.Value[0:4]) + br.ForwardDelay = &forwardingDelay + case nl.IFLA_BR_MAX_AGE: + maxAge := native.Uint32(datum.Value[0:4]) + br.MaxAge = &maxAge } } } diff --git a/link_test.go b/link_test.go index b68dbb0a..ad9f246f 100644 --- a/link_test.go +++ b/link_test.go @@ -2035,6 +2035,184 @@ func expectMcastSnooping(t *testing.T, linkName string, expected bool) { } } +func TestBridgeSetVlanProtocol(t *testing.T) { + minKernelRequired(t, 4, 3) + + tearDown := setUpNetlinkTest(t) + defer tearDown() + + bridgeName := "foo" + bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}} + if err := LinkAdd(bridge); err != nil { + t.Fatal(err) + } + + expectVlanProtocol(t, bridgeName, "802.1Q") + + if err := BridgeSetVlanProtocol(bridge, "802.1ad"); err != nil { + t.Fatal(err) + } + + expectVlanProtocol(t, bridgeName, "802.1ad") + + if err := LinkDel(bridge); err != nil { + t.Fatal(err) + } +} + +func expectVlanProtocol(t *testing.T, linkName string, expected string) { + bridge, err := LinkByName(linkName) + if err != nil { + t.Fatal(err) + } + + if actual := bridge.(*Bridge).VlanProtocol; actual == nil || *actual != expected { + t.Fatal(err) + } +} + +func TestBridgeSetForwardDelay(t *testing.T) { + minKernelRequired(t, 3, 18) + + tearDown := setUpNetlinkTest(t) + defer tearDown() + + bridgeName := "foo" + bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}} + if err := LinkAdd(bridge); err != nil { + t.Fatal(err) + } + + var forwardDelay uint32 = 1200 + + if err := BridgeSetForwardDelay(bridge, forwardDelay); err != nil { + t.Fatal(err) + } + + expectForwardDelay(t, bridgeName, forwardDelay) + + forwardDelay = 400 + + if err := BridgeSetForwardDelay(bridge, forwardDelay); err != nil { + t.Fatal(err) + } + + expectForwardDelay(t, bridgeName, forwardDelay) + + if err := LinkDel(bridge); err != nil { + t.Fatal(err) + } +} + +func expectForwardDelay(t *testing.T, linkName string, expected uint32) { + bridge, err := LinkByName(linkName) + if err != nil { + t.Fatal(err) + } + + actual := bridge.(*Bridge).ForwardDelay + if actual == nil { + t.Fatalf("expected %d got %v", expected, nil) + } else if *actual != expected { + t.Fatalf("expected %d got %d", expected, *actual) + } +} + +func TestBridgeSetMaxAge(t *testing.T) { + minKernelRequired(t, 3, 18) + + tearDown := setUpNetlinkTest(t) + defer tearDown() + + bridgeName := "foo" + bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}} + if err := LinkAdd(bridge); err != nil { + t.Fatal(err) + } + + var maxAge uint32 = 1200 + + if err := BridgeSetMaxAge(bridge, maxAge); err != nil { + t.Fatal(err) + } + + expectMaxAge(t, bridgeName, maxAge) + + maxAge = 1900 + + if err := BridgeSetForwardDelay(bridge, maxAge); err != nil { + t.Fatal(err) + } + + expectMaxAge(t, bridgeName, maxAge) + + if err := LinkDel(bridge); err != nil { + t.Fatal(err) + } +} + +func expectMaxAge(t *testing.T, linkName string, expected uint32) { + bridge, err := LinkByName(linkName) + if err != nil { + t.Fatal(err) + } + + actual := bridge.(*Bridge).MaxAge + if actual == nil { + t.Fatalf("expected %d got %v", expected, nil) + } else if *actual != expected { + t.Fatalf("expected %d got %d", expected, *actual) + } +} + +func TestBridgeSetStpState(t *testing.T) { + minKernelRequired(t, 4, 1) + + tearDown := setUpNetlinkTest(t) + defer tearDown() + + bridgeName := "foo" + bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}} + if err := LinkAdd(bridge); err != nil { + t.Fatal(err) + } + + var stpState uint32 = 0 + + if err := BridgeSetStpState(bridge, stpState); err != nil { + t.Fatal(err) + } + + expectStpState(t, bridgeName, stpState) + + stpState = 1 + + if err := BridgeSetStpState(bridge, stpState); err != nil { + t.Fatal(err) + } + + expectStpState(t, bridgeName, stpState) + + if err := LinkDel(bridge); err != nil { + t.Fatal(err) + } +} + +func expectStpState(t *testing.T, linkName string, expected uint32) { + bridge, err := LinkByName(linkName) + if err != nil { + t.Fatal(err) + } + + actual := bridge.(*Bridge).StpState + if actual == nil { + t.Fatalf("expected %d got %v", expected, nil) + } else if *actual != expected { + t.Fatalf("expected %d got %d", expected, *actual) + } +} + + func TestBridgeSetVlanFiltering(t *testing.T) { minKernelRequired(t, 4, 4) @@ -2264,6 +2442,215 @@ func TestLinkSubscribeWithProtinfo(t *testing.T) { } } +func TestBridgeCreationWithStpState(t *testing.T) { + minKernelRequired(t, 4, 1) + + tearDown := setUpNetlinkTest(t) + defer tearDown() + + bridgeWithSpecifiedStpStateName := "foo" + stpState := uint32(1) + bridgeWithSpecifiedStpState := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithSpecifiedStpStateName}, StpState: &stpState} + if err := LinkAdd(bridgeWithSpecifiedStpState); err != nil { + t.Fatal(err) + } + + retrievedBridge, err := LinkByName(bridgeWithSpecifiedStpStateName) + if err != nil { + t.Fatal(err) + } + + if actualStpState := retrievedBridge.(*Bridge).StpState; actualStpState == nil || *actualStpState != stpState { + if actualStpState != nil { + t.Fatalf("expected %d got %d", stpState, *actualStpState) + } else { + t.Fatalf("expected %d got %v", stpState, nil) + } + } + if err := LinkDel(bridgeWithSpecifiedStpState); err != nil { + t.Fatal(err) + } + + bridgeWithDefaultStpStateName := "bar" + bridgeWithDefaultStpState := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithDefaultStpStateName}} + if err := LinkAdd(bridgeWithDefaultStpState); err != nil { + t.Fatal(err) + } + + retrievedBridge, err = LinkByName(bridgeWithDefaultStpStateName) + if err != nil { + t.Fatal(err) + } + + if actualStpState := retrievedBridge.(*Bridge).StpState; actualStpState == nil || *actualStpState != 0 { + if actualStpState != nil { + t.Fatalf("expected %d got %d", 0, *actualStpState) + } else { + t.Fatalf("expected %d got %v", 0, nil) + } + } + + if err := LinkDel(bridgeWithDefaultStpState); err != nil { + t.Fatal(err) + } +} + +func TestBridgeCreationWithVlanProtocol(t *testing.T) { + minKernelRequired(t, 4, 3) + + tearDown := setUpNetlinkTest(t) + defer tearDown() + + bridgeWithSpecifiedVlanProtocolName := "foo" + vlanProtocol := "802.1ad" + bridgeWithSpecifiedVlanProtocol := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithSpecifiedVlanProtocolName}, VlanProtocol: &vlanProtocol} + if err := LinkAdd(bridgeWithSpecifiedVlanProtocol); err != nil { + t.Fatal(err) + } + + retrievedBridge, err := LinkByName(bridgeWithSpecifiedVlanProtocolName) + if err != nil { + t.Fatal(err) + } + + if actualVlanProtocol := retrievedBridge.(*Bridge).VlanProtocol; actualVlanProtocol == nil || *actualVlanProtocol != vlanProtocol { + if actualVlanProtocol != nil { + t.Fatalf("expected %s got %s", vlanProtocol, *actualVlanProtocol) + } else { + t.Fatalf("expected %s got %v", vlanProtocol, nil) + } + } + if err := LinkDel(bridgeWithSpecifiedVlanProtocol); err != nil { + t.Fatal(err) + } + + bridgeWithDefaultVlanProtocolName := "bar" + bridgeWithDefaultVlanProtocol := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithDefaultVlanProtocolName}} + if err := LinkAdd(bridgeWithDefaultVlanProtocol); err != nil { + t.Fatal(err) + } + + retrievedBridge, err = LinkByName(bridgeWithDefaultVlanProtocolName) + if err != nil { + t.Fatal(err) + } + + if actualVlanProtocol := retrievedBridge.(*Bridge).VlanProtocol; actualVlanProtocol == nil || *actualVlanProtocol != "802.1Q" { + if actualVlanProtocol != nil { + t.Fatalf("expected 802.1Q got %s", *actualVlanProtocol) + } else { + t.Fatalf("expected 802.1Q got %v", nil) + } + } + if err := LinkDel(bridgeWithDefaultVlanProtocol); err != nil { + t.Fatal(err) + } +} + +func TestBridgeCreationWithForwardDelay(t *testing.T) { + minKernelRequired(t, 3, 18) + + tearDown := setUpNetlinkTest(t) + defer tearDown() + + bridgeWithSpecifiedForwardDelayName := "foo" + forwardDelay := uint32(400) + bridgeWithSpecifiedForwardDelay := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithSpecifiedForwardDelayName}, ForwardDelay: &forwardDelay} + if err := LinkAdd(bridgeWithSpecifiedForwardDelay); err != nil { + t.Fatal(err) + } + + retrievedBridge, err := LinkByName(bridgeWithSpecifiedForwardDelayName) + if err != nil { + t.Fatal(err) + } + + if actualForwardDelay := retrievedBridge.(*Bridge).ForwardDelay; actualForwardDelay == nil || *actualForwardDelay != forwardDelay { + if actualForwardDelay != nil { + t.Fatalf("expected %d got %d", forwardDelay, *actualForwardDelay) + } else { + t.Fatalf("expected %d got %v", forwardDelay, nil) + } + } + if err := LinkDel(bridgeWithSpecifiedForwardDelay); err != nil { + t.Fatal(err) + } + + bridgeWithDefaultForwardDelayName := "bar" + bridgeWithDefaultForwardDelay := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithDefaultForwardDelayName}} + if err := LinkAdd(bridgeWithDefaultForwardDelay); err != nil { + t.Fatal(err) + } + + retrievedBridge, err = LinkByName(bridgeWithDefaultForwardDelayName) + if err != nil { + t.Fatal(err) + } + + if actualForwardDelay := retrievedBridge.(*Bridge).ForwardDelay; actualForwardDelay == nil || *actualForwardDelay != 1500 { + if actualForwardDelay != nil { + t.Fatalf("expected %d got %d", 1500, *actualForwardDelay) + } else { + t.Fatalf("expected %d got %v", 1500, nil) + } + } + if err := LinkDel(bridgeWithDefaultForwardDelay); err != nil { + t.Fatal(err) + } +} + +func TestBridgeCreationWithMaxAge(t *testing.T) { + minKernelRequired(t, 3, 18) + + tearDown := setUpNetlinkTest(t) + defer tearDown() + + bridgeWithSpecifiedMaxAgeName := "foo" + maxAge := uint32(800) + bridgeWithSpecifiedMaxAge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithSpecifiedMaxAgeName}, MaxAge: &maxAge} + if err := LinkAdd(bridgeWithSpecifiedMaxAge); err != nil { + t.Fatal(err) + } + + retrievedBridge, err := LinkByName(bridgeWithSpecifiedMaxAgeName) + if err != nil { + t.Fatal(err) + } + + if actualMaxAge := retrievedBridge.(*Bridge).MaxAge; actualMaxAge == nil || *actualMaxAge != maxAge { + if actualMaxAge != nil { + t.Fatalf("expected %d got %d", maxAge, *actualMaxAge) + } else { + t.Fatalf("expected %d got %v", maxAge, nil) + } + } + if err := LinkDel(bridgeWithSpecifiedMaxAge); err != nil { + t.Fatal(err) + } + + bridgeWithDefaultMaxAgeName := "bar" + bridgeWithDefaultMaxAge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithDefaultMaxAgeName}} + if err := LinkAdd(bridgeWithDefaultMaxAge); err != nil { + t.Fatal(err) + } + + retrievedBridge, err = LinkByName(bridgeWithDefaultMaxAgeName) + if err != nil { + t.Fatal(err) + } + + if actualMaxAge := retrievedBridge.(*Bridge).MaxAge; actualMaxAge == nil || *actualMaxAge != 1999 { + if actualMaxAge != nil { + t.Fatalf("expected %d got %d", 1999, *actualMaxAge) + } else { + t.Fatalf("expected %d got %v", 1999, nil) + } + } + if err := LinkDel(bridgeWithDefaultMaxAge); err != nil { + t.Fatal(err) + } +} + func testGTPLink(t *testing.T) *GTP { conn1, err := net.ListenUDP("udp", &net.UDPAddr{ IP: net.ParseIP("0.0.0.0"),