diff --git a/bridge_linux.go b/bridge_linux.go index cb1dd98..3d59148 100644 --- a/bridge_linux.go +++ b/bridge_linux.go @@ -19,6 +19,29 @@ func BridgeVlanTunnelShow() ([]nl.TunnelInfo, error) { } func (h *Handle) BridgeVlanTunnelShow() ([]nl.TunnelInfo, error) { + // ifindex 0 means no filtering (return all devices) + return h.bridgeVlanTunnelShowBy(0) +} + +// BridgeVlanTunnelShowDev gets vlanid-tunnelid mapping for a specific device. +// Equivalent to: `bridge vlan tunnelshow dev DEV` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. +func BridgeVlanTunnelShowDev(link Link) ([]nl.TunnelInfo, error) { + return pkgHandle.BridgeVlanTunnelShowDev(link) +} + +func (h *Handle) BridgeVlanTunnelShowDev(link Link) ([]nl.TunnelInfo, error) { + base := link.Attrs() + h.ensureIndex(base) + return h.bridgeVlanTunnelShowBy(int32(base.Index)) +} + +// bridgeVlanTunnelShowBy performs a bridge VLAN tunnel dump and optionally +// filters by device ifindex. +// When ifindex is 0, all devices are returned. +func (h *Handle) bridgeVlanTunnelShowBy(ifindex int32) ([]nl.TunnelInfo, error) { req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) msg := nl.NewIfInfomsg(unix.AF_BRIDGE) req.AddData(msg) @@ -32,6 +55,10 @@ func (h *Handle) BridgeVlanTunnelShow() ([]nl.TunnelInfo, error) { for _, m := range msgs { msg := nl.DeserializeIfInfomsg(m) + if ifindex != 0 && msg.Index != ifindex { + continue + } + attrs, err := nl.ParseRouteAttr(m[msg.Len():]) if err != nil { return nil, err @@ -54,6 +81,11 @@ func (h *Handle) BridgeVlanTunnelShow() ([]nl.TunnelInfo, error) { } } } + // Each device has exactly one message in the dump; + // return early once we've processed the target device. + if ifindex != 0 { + return ret, executeErr + } } return ret, executeErr } diff --git a/bridge_linux_test.go b/bridge_linux_test.go index f2b1dd5..6b60a1d 100644 --- a/bridge_linux_test.go +++ b/bridge_linux_test.go @@ -4,6 +4,8 @@ import ( "fmt" "io/ioutil" "testing" + + "github.com/vishvananda/netlink/nl" ) func TestBridgeVlan(t *testing.T) { @@ -295,6 +297,133 @@ func TestBridgeVni(t *testing.T) { } } +func setupBridgeWithTwoVxlans(t *testing.T) (*Bridge, *Vxlan, *Vxlan) { + t.Helper() + + // ip link add br0 type bridge + bridge := &Bridge{LinkAttrs: LinkAttrs{Name: "br0"}} + if err := LinkAdd(bridge); err != nil { + t.Fatal(err) + } + + // ip link add vxlan0 type vxlan dstport 4789 nolearning external local 10.0.1.1 + vxlan0 := &Vxlan{ + LinkAttrs: LinkAttrs{Name: "vxlan0"}, + SrcAddr: []byte("10.0.1.1"), + Learning: false, + FlowBased: true, + Port: 4789, + } + if err := LinkAdd(vxlan0); err != nil { + t.Fatal(err) + } + + // ip link add vxlan1 type vxlan dstport 4790 nolearning external local 10.0.1.1 + vxlan1 := &Vxlan{ + LinkAttrs: LinkAttrs{Name: "vxlan1"}, + SrcAddr: []byte("10.0.1.1"), + Learning: false, + FlowBased: true, + Port: 4790, + } + if err := LinkAdd(vxlan1); err != nil { + t.Fatal(err) + } + + // ip link set dev vxlan0 master br0 + // ip link set dev vxlan1 master br0 + for _, vxlan := range []*Vxlan{vxlan0, vxlan1} { + if err := LinkSetMaster(vxlan, bridge); err != nil { + t.Fatal(err) + } + } + + // ip link set br0 type bridge vlan_filtering 1 + if err := BridgeSetVlanFiltering(bridge, true); err != nil { + t.Fatal(err) + } + + // bridge link set dev vxlan0 vlan_tunnel on + // bridge link set dev vxlan1 vlan_tunnel on + for _, vxlan := range []*Vxlan{vxlan0, vxlan1} { + if err := LinkSetVlanTunnel(vxlan, true); err != nil { + t.Fatal(err) + } + } + + return bridge, vxlan0, vxlan1 +} + +func TestBridgeVlanTunnelShowDev(t *testing.T) { + minKernelRequired(t, 4, 11) + t.Cleanup(setUpNetlinkTest(t)) + + if err := remountSysfs(); err != nil { + t.Fatal(err) + } + + bridge, vxlan0, vxlan1 := setupBridgeWithTwoVxlans(t) + + // bridge vlan add vid 10 dev vxlan0 + if err := BridgeVlanAdd(vxlan0, 10, false, false, false, false); err != nil { + t.Fatal(err) + } + // bridge vlan add dev vxlan0 vid 10 tunnel_info id 100 + if err := BridgeVlanAddTunnelInfo(vxlan0, 10, 100, false, false); err != nil { + t.Fatal(err) + } + // bridge vlan add vid 11 dev vxlan0 + if err := BridgeVlanAdd(vxlan0, 11, false, false, false, false); err != nil { + t.Fatal(err) + } + // bridge vlan add dev vxlan0 vid 11 tunnel_info id 101 + if err := BridgeVlanAddTunnelInfo(vxlan0, 11, 101, false, false); err != nil { + t.Fatal(err) + } + + // bridge vlan add vid 20 dev vxlan1 + if err := BridgeVlanAdd(vxlan1, 20, false, false, false, false); err != nil { + t.Fatal(err) + } + // bridge vlan add dev vxlan1 vid 20 tunnel_info id 200 + if err := BridgeVlanAddTunnelInfo(vxlan1, 20, 200, false, false); err != nil { + t.Fatal(err) + } + + // Verify vxlan0 returns only its tunnel infos + tis, err := BridgeVlanTunnelShowDev(vxlan0) + if err != nil { + t.Fatal(err) + } + if len(tis) != 2 { + t.Fatalf("vxlan0: expected 2 tunnel infos, got %d: %v", len(tis), tis) + } + if tis[0] != (nl.TunnelInfo{Vid: 10, TunId: 100}) || tis[1] != (nl.TunnelInfo{Vid: 11, TunId: 101}) { + t.Fatalf("vxlan0: unexpected tunnel infos: %+v", tis) + } + + // Verify vxlan1 returns only its tunnel infos + tis, err = BridgeVlanTunnelShowDev(vxlan1) + if err != nil { + t.Fatal(err) + } + if len(tis) != 1 { + t.Fatalf("vxlan1: expected 1 tunnel info, got %d: %v", len(tis), tis) + } + if tis[0] != (nl.TunnelInfo{Vid: 20, TunId: 200}) { + t.Fatalf("vxlan1: unexpected tunnel info: %+v", tis) + } + + // Verify bridge itself returns no tunnel infos + tis, err = BridgeVlanTunnelShowDev(bridge) + if err != nil { + t.Fatal(err) + } + if len(tis) != 0 { + t.Fatalf("bridge: expected 0 tunnel infos, got %d: %v", len(tis), tis) + } +} + func TestBridgeGroupFwdMask(t *testing.T) { minKernelRequired(t, 4, 15) //minimal release for per-port group_fwd_mask t.Cleanup(setUpNetlinkTest(t))