diff --git a/bridge_linux.go b/bridge_linux.go index fa5766b..ec941e5 100644 --- a/bridge_linux.go +++ b/bridge_linux.go @@ -3,11 +3,102 @@ package netlink import ( "errors" "fmt" + "syscall" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) +// BridgeVlanTunnelShow gets vlanid-tunnelid mapping. +// Equivalent to: `bridge vlan tunnelshow` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. +func BridgeVlanTunnelShow() ([]nl.TunnelInfo, error) { + return pkgHandle.BridgeVlanTunnelShow() +} + +func (h *Handle) BridgeVlanTunnelShow() ([]nl.TunnelInfo, error) { + req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) + msg := nl.NewIfInfomsg(unix.AF_BRIDGE) + req.AddData(msg) + req.AddData(nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(uint32(nl.RTEXT_FILTER_BRVLAN)))) + + msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr + } + ret := make([]nl.TunnelInfo, 0) + for _, m := range msgs { + msg := nl.DeserializeIfInfomsg(m) + + attrs, err := nl.ParseRouteAttr(m[msg.Len():]) + if err != nil { + return nil, err + } + for _, attr := range attrs { + switch attr.Attr.Type { + case unix.IFLA_AF_SPEC: + nestedAttrs, err := nl.ParseRouteAttr(attr.Value) + if err != nil { + return nil, fmt.Errorf("failed to parse nested attr %v", err) + } + for _, nestAttr := range nestedAttrs { + switch nestAttr.Attr.Type { + case nl.IFLA_BRIDGE_VLAN_TUNNEL_INFO: + ret, err = parseTunnelInfo(&nestAttr, ret) + if err != nil { + return nil, fmt.Errorf("failed to parse tunnelinfo %v", err) + } + } + } + } + } + } + return ret, executeErr +} + +func parseTunnelInfo(nestAttr *syscall.NetlinkRouteAttr, results []nl.TunnelInfo) ([]nl.TunnelInfo, error) { + tunnelInfos, err := nl.ParseRouteAttr(nestAttr.Value) + if err != nil { + return nil, fmt.Errorf("failed to parse nested attr %v", err) + } + var tunnelId uint32 + var vid uint16 + var flag uint16 + for _, tunnelInfo := range tunnelInfos { + switch tunnelInfo.Attr.Type { + case nl.IFLA_BRIDGE_VLAN_TUNNEL_ID: + tunnelId = native.Uint32(tunnelInfo.Value) + case nl.IFLA_BRIDGE_VLAN_TUNNEL_VID: + vid = native.Uint16(tunnelInfo.Value) + case nl.IFLA_BRIDGE_VLAN_TUNNEL_FLAGS: + flag = native.Uint16(tunnelInfo.Value) + } + } + + if flag == nl.BRIDGE_VLAN_INFO_RANGE_END { + lastTi := results[len(results)-1] + vni := lastTi.TunId + 1 + for i := lastTi.Vid + 1; i < vid; i++ { + t := nl.TunnelInfo{ + TunId: vni, + Vid: i, + } + results = append(results, t) + vni++ + } + } + + t := nl.TunnelInfo{ + TunId: tunnelId, + Vid: vid, + } + + results = append(results, t) + return results, nil +} + // BridgeVlanList gets a map of device id to bridge vlan infos. // Equivalent to: `bridge vlan show` // @@ -61,6 +152,38 @@ func (h *Handle) BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) { return ret, executeErr } +// BridgeVlanAddTunnelInfo adds a new vlan filter entry +// Equivalent to: `bridge vlan add dev DEV vid VID tunnel_info id TUNID [ self ] [ master ]` +func BridgeVlanAddTunnelInfo(link Link, vid uint16, tunid uint32, self, master bool) error { + return pkgHandle.BridgeVlanAddTunnelInfo(link, vid, 0, tunid, 0, self, master) +} + +// BridgeVlanAddRangeTunnelInfoRange adds a new vlan filter entry +// Equivalent to: `bridge vlan add dev DEV vid VID-VIDEND tunnel_info id VIN-VINEND [ self ] [ master ]` +func BridgeVlanAddRangeTunnelInfoRange(link Link, vid, vidEnd uint16, tunid, tunidEnd uint32, self, master bool) error { + return pkgHandle.BridgeVlanAddTunnelInfo(link, vid, vidEnd, tunid, tunidEnd, self, master) +} + +func (h *Handle) BridgeVlanAddTunnelInfo(link Link, vid, vidEnd uint16, tunid, tunidEnd uint32, self, master bool) error { + return h.bridgeVlanModify(unix.RTM_SETLINK, link, vid, vidEnd, tunid, tunidEnd, false, false, self, master) +} + +// BridgeVlanDelTunnelInfo adds a new vlan filter entry +// Equivalent to: `bridge vlan del dev DEV vid VID tunnel_info id TUNID [ self ] [ master ]` +func BridgeVlanDelTunnelInfo(link Link, vid uint16, tunid uint32, self, master bool) error { + return pkgHandle.BridgeVlanDelTunnelInfo(link, vid, 0, tunid, 0, self, master) +} + +// BridgeVlanDelRangeTunnelInfoRange adds a new vlan filter entry +// Equivalent to: `bridge vlan del dev DEV vid VID-VIDEND tunnel_info id VIN-VINEND [ self ] [ master ]` +func BridgeVlanDelRangeTunnelInfoRange(link Link, vid, vidEnd uint16, tunid, tunidEnd uint32, self, master bool) error { + return pkgHandle.BridgeVlanDelTunnelInfo(link, vid, vidEnd, tunid, tunidEnd, self, master) +} + +func (h *Handle) BridgeVlanDelTunnelInfo(link Link, vid, vidEnd uint16, tunid, tunidEnd uint32, self, master bool) error { + return h.bridgeVlanModify(unix.RTM_DELLINK, link, vid, vidEnd, tunid, tunidEnd, false, false, self, master) +} + // BridgeVlanAdd adds a new vlan filter entry // Equivalent to: `bridge vlan add dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]` func BridgeVlanAdd(link Link, vid uint16, pvid, untagged, self, master bool) error { @@ -70,7 +193,7 @@ func BridgeVlanAdd(link Link, vid uint16, pvid, untagged, self, master bool) err // BridgeVlanAdd adds a new vlan filter entry // Equivalent to: `bridge vlan add dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]` func (h *Handle) BridgeVlanAdd(link Link, vid uint16, pvid, untagged, self, master bool) error { - return h.bridgeVlanModify(unix.RTM_SETLINK, link, vid, 0, pvid, untagged, self, master) + return h.bridgeVlanModify(unix.RTM_SETLINK, link, vid, 0, 0, 0, pvid, untagged, self, master) } // BridgeVlanAddRange adds a new vlan filter entry @@ -82,7 +205,7 @@ func BridgeVlanAddRange(link Link, vid, vidEnd uint16, pvid, untagged, self, mas // BridgeVlanAddRange adds a new vlan filter entry // Equivalent to: `bridge vlan add dev DEV vid VID-VIDEND [ pvid ] [ untagged ] [ self ] [ master ]` func (h *Handle) BridgeVlanAddRange(link Link, vid, vidEnd uint16, pvid, untagged, self, master bool) error { - return h.bridgeVlanModify(unix.RTM_SETLINK, link, vid, vidEnd, pvid, untagged, self, master) + return h.bridgeVlanModify(unix.RTM_SETLINK, link, vid, vidEnd, 0, 0, pvid, untagged, self, master) } // BridgeVlanDel adds a new vlan filter entry @@ -94,7 +217,7 @@ func BridgeVlanDel(link Link, vid uint16, pvid, untagged, self, master bool) err // BridgeVlanDel adds a new vlan filter entry // Equivalent to: `bridge vlan del dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]` func (h *Handle) BridgeVlanDel(link Link, vid uint16, pvid, untagged, self, master bool) error { - return h.bridgeVlanModify(unix.RTM_DELLINK, link, vid, 0, pvid, untagged, self, master) + return h.bridgeVlanModify(unix.RTM_DELLINK, link, vid, 0, 0, 0, pvid, untagged, self, master) } // BridgeVlanDelRange adds a new vlan filter entry @@ -106,10 +229,10 @@ func BridgeVlanDelRange(link Link, vid, vidEnd uint16, pvid, untagged, self, mas // BridgeVlanDelRange adds a new vlan filter entry // Equivalent to: `bridge vlan del dev DEV vid VID-VIDEND [ pvid ] [ untagged ] [ self ] [ master ]` func (h *Handle) BridgeVlanDelRange(link Link, vid, vidEnd uint16, pvid, untagged, self, master bool) error { - return h.bridgeVlanModify(unix.RTM_DELLINK, link, vid, vidEnd, pvid, untagged, self, master) + return h.bridgeVlanModify(unix.RTM_DELLINK, link, vid, vidEnd, 0, 0, pvid, untagged, self, master) } -func (h *Handle) bridgeVlanModify(cmd int, link Link, vid, vidEnd uint16, pvid, untagged, self, master bool) error { +func (h *Handle) bridgeVlanModify(cmd int, link Link, vid, vidEnd uint16, tunid, tunidEnd uint32, pvid, untagged, self, master bool) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(cmd, unix.NLM_F_ACK) @@ -129,25 +252,45 @@ func (h *Handle) bridgeVlanModify(cmd int, link Link, vid, vidEnd uint16, pvid, if flags > 0 { br.AddRtAttr(nl.IFLA_BRIDGE_FLAGS, nl.Uint16Attr(flags)) } - vlanInfo := &nl.BridgeVlanInfo{Vid: vid} - if pvid { - vlanInfo.Flags |= nl.BRIDGE_VLAN_INFO_PVID - } - if untagged { - vlanInfo.Flags |= nl.BRIDGE_VLAN_INFO_UNTAGGED - } - if vidEnd != 0 { - vlanEndInfo := &nl.BridgeVlanInfo{Vid: vidEnd} - vlanEndInfo.Flags = vlanInfo.Flags + if tunid != 0 { + if tunidEnd != 0 { + tiStart := br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_INFO, nil) + tiStart.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_ID, nl.Uint32Attr(tunid)) + tiStart.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_VID, nl.Uint16Attr(vid)) + tiStart.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, nl.Uint16Attr(nl.BRIDGE_VLAN_INFO_RANGE_BEGIN)) - vlanInfo.Flags |= nl.BRIDGE_VLAN_INFO_RANGE_BEGIN - br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_INFO, vlanInfo.Serialize()) + tiEnd := br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_INFO, nil) + tiEnd.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_ID, nl.Uint32Attr(tunidEnd)) + tiEnd.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_VID, nl.Uint16Attr(vidEnd)) + tiEnd.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, nl.Uint16Attr(nl.BRIDGE_VLAN_INFO_RANGE_END)) + } else { + ti := br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_INFO, nil) + ti.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_ID, nl.Uint32Attr(tunid)) + ti.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_VID, nl.Uint16Attr(vid)) + ti.AddRtAttr(nl.IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, nl.Uint16Attr(0)) + } + } else { + vlanInfo := &nl.BridgeVlanInfo{Vid: vid} + if pvid { + vlanInfo.Flags |= nl.BRIDGE_VLAN_INFO_PVID + } + if untagged { + vlanInfo.Flags |= nl.BRIDGE_VLAN_INFO_UNTAGGED + } - vlanEndInfo.Flags |= nl.BRIDGE_VLAN_INFO_RANGE_END - br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_INFO, vlanEndInfo.Serialize()) - } else { - br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_INFO, vlanInfo.Serialize()) + if vidEnd != 0 { + vlanEndInfo := &nl.BridgeVlanInfo{Vid: vidEnd} + vlanEndInfo.Flags = vlanInfo.Flags + + vlanInfo.Flags |= nl.BRIDGE_VLAN_INFO_RANGE_BEGIN + br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_INFO, vlanInfo.Serialize()) + + vlanEndInfo.Flags |= nl.BRIDGE_VLAN_INFO_RANGE_END + br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_INFO, vlanEndInfo.Serialize()) + } else { + br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_INFO, vlanInfo.Serialize()) + } } req.AddData(br) diff --git a/bridge_linux_test.go b/bridge_linux_test.go index 710a01b..d7aa596 100644 --- a/bridge_linux_test.go +++ b/bridge_linux_test.go @@ -79,6 +79,131 @@ func TestBridgeVlan(t *testing.T) { } } +func TestBridgeVlanTunnelInfo(t *testing.T) { + minKernelRequired(t, 4, 11) + tearDown := setUpNetlinkTest(t) + defer tearDown() + + if err := remountSysfs(); err != nil { + t.Fatal(err) + } + bridgeName := "br0" + vxlanName := "vxlan0" + + // ip link add br0 type bridge + bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}} + if err := LinkAdd(bridge); err != nil { + t.Fatal(err) + } + + // ip link add vxlan0 type vxlan dstport 4789 nolearning external local 10.0.1.1 + vxlan := &Vxlan{ + // local + SrcAddr: []byte("10.0.1.1"), + Learning: false, + // external + FlowBased: true, + // dstport + Port: 4789, + LinkAttrs: LinkAttrs{Name: vxlanName}, + } + if err := LinkAdd(vxlan); err != nil { + t.Fatal(err) + } + + // ip link set dev vxlan0 master br0 + 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 + if err := LinkSetVlanTunnel(vxlan, true); err != nil { + t.Fatal(err) + } + + p, err := LinkGetProtinfo(vxlan) + if err != nil { + t.Fatal(err) + } + if !p.VlanTunnel { + t.Fatal("vlan tunnel should be enabled on vxlan device") + } + + // bridge vlan add vid 10 dev vxlan0 + if err := BridgeVlanAdd(vxlan, 10, false, false, false, false); err != nil { + t.Fatal(err) + } + + // bridge vlan add vid 11 dev vxlan0 + if err := BridgeVlanAdd(vxlan, 11, false, false, false, false); err != nil { + t.Fatal(err) + } + + // bridge vlan add dev vxlan0 vid 10 tunnel_info id 20 + if err := BridgeVlanAddTunnelInfo(vxlan, 10, 20, false, false); err != nil { + t.Fatal(err) + } + + tis, err := BridgeVlanTunnelShow() + if err != nil { + t.Fatal(err) + } + + if len(tis) != 1 { + t.Fatal("only one tunnel info") + } + ti := tis[0] + if ti.TunId != 20 || ti.Vid != 10 { + t.Fatal("unexpected result") + } + + // bridge vlan del dev vxlan0 vid 10 tunnel_info id 20 + if err := BridgeVlanDelTunnelInfo(vxlan, 10, 20, false, false); err != nil { + t.Fatal(err) + } + + tis, err = BridgeVlanTunnelShow() + if err != nil { + t.Fatal(err) + } + + if len(tis) != 0 { + t.Fatal("tunnel info should have been deleted") + } + + // bridge vlan add dev vxlan0 vid 10-11 tunnel_info id 20-21 + if err := BridgeVlanAddRangeTunnelInfoRange(vxlan, 10, 11, 20, 21, false, false); err != nil { + t.Fatal(err) + } + + tis, err = BridgeVlanTunnelShow() + if err != nil { + t.Fatal(err) + } + if len(tis) != 2 { + t.Fatal("two tunnel info") + } + + // bridge vlan del dev vxlan0 vid 10-11 tunnel_info id 20-21 + if err := BridgeVlanDelRangeTunnelInfoRange(vxlan, 10, 11, 20, 21, false, false); err != nil { + t.Fatal(err) + } + + tis, err = BridgeVlanTunnelShow() + if err != nil { + t.Fatal(err) + } + + if len(tis) != 0 { + t.Fatal("tunnel info should have been deleted") + } +} + func TestBridgeGroupFwdMask(t *testing.T) { minKernelRequired(t, 4, 15) //minimal release for per-port group_fwd_mask tearDown := setUpNetlinkTest(t) diff --git a/link_linux.go b/link_linux.go index 9d7f44a..02b3462 100644 --- a/link_linux.go +++ b/link_linux.go @@ -2553,6 +2553,14 @@ func (h *Handle) LinkSetLearning(link Link, mode bool) error { return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_LEARNING) } +func LinkSetVlanTunnel(link Link, mode bool) error { + return pkgHandle.LinkSetVlanTunnel(link, mode) +} + +func (h *Handle) LinkSetVlanTunnel(link Link, mode bool) error { + return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_VLAN_TUNNEL) +} + func LinkSetRootBlock(link Link, mode bool) error { return pkgHandle.LinkSetRootBlock(link, mode) } diff --git a/nl/bridge_linux.go b/nl/bridge_linux.go index 34e78ba..2441d1c 100644 --- a/nl/bridge_linux.go +++ b/nl/bridge_linux.go @@ -26,6 +26,14 @@ const ( IFLA_BRIDGE_FLAGS = iota IFLA_BRIDGE_MODE IFLA_BRIDGE_VLAN_INFO + IFLA_BRIDGE_VLAN_TUNNEL_INFO +) + +const ( + IFLA_BRIDGE_VLAN_TUNNEL_UNSPEC = iota + IFLA_BRIDGE_VLAN_TUNNEL_ID + IFLA_BRIDGE_VLAN_TUNNEL_VID + IFLA_BRIDGE_VLAN_TUNNEL_FLAGS ) const ( @@ -41,6 +49,11 @@ const ( // __u16 vid; // }; +type TunnelInfo struct { + TunId uint32 + Vid uint16 +} + type BridgeVlanInfo struct { Flags uint16 Vid uint16 diff --git a/protinfo.go b/protinfo.go index 0163cba..02f066e 100644 --- a/protinfo.go +++ b/protinfo.go @@ -16,6 +16,7 @@ type Protinfo struct { ProxyArpWiFi bool Isolated bool NeighSuppress bool + VlanTunnel bool } // String returns a list of enabled flags @@ -55,6 +56,9 @@ func (prot *Protinfo) String() string { if prot.NeighSuppress { boolStrings = append(boolStrings, "NeighSuppress") } + if prot.VlanTunnel { + boolStrings = append(boolStrings, "VlanTunnel") + } return strings.Join(boolStrings, " ") } diff --git a/protinfo_linux.go b/protinfo_linux.go index aa51e3b..c7d7b56 100644 --- a/protinfo_linux.go +++ b/protinfo_linux.go @@ -77,7 +77,10 @@ func parseProtinfo(infos []syscall.NetlinkRouteAttr) (pi Protinfo) { pi.Isolated = byteToBool(info.Value[0]) case nl.IFLA_BRPORT_NEIGH_SUPPRESS: pi.NeighSuppress = byteToBool(info.Value[0]) + case nl.IFLA_BRPORT_VLAN_TUNNEL: + pi.VlanTunnel = byteToBool(info.Value[0]) } + } return }