Add bridge vlan support

This commit is contained in:
Chun Chen 2017-06-06 17:09:16 +08:00 committed by Vish (Ishaya) Abrams
parent bd6d5de5cc
commit 7593cff56f
5 changed files with 315 additions and 0 deletions

115
bridge_linux.go Normal file
View File

@ -0,0 +1,115 @@
package netlink
import (
"fmt"
"syscall"
"github.com/vishvananda/netlink/nl"
)
// BridgeVlanList gets a map of device id to bridge vlan infos.
// Equivalent to: `bridge vlan show`
func BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) {
return pkgHandle.BridgeVlanList()
}
// BridgeVlanList gets a map of device id to bridge vlan infos.
// Equivalent to: `bridge vlan show`
func (h *Handle) BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) {
req := h.newNetlinkRequest(syscall.RTM_GETLINK, syscall.NLM_F_DUMP)
msg := nl.NewIfInfomsg(syscall.AF_BRIDGE)
req.AddData(msg)
req.AddData(nl.NewRtAttr(nl.IFLA_EXT_MASK, nl.Uint32Attr(uint32(nl.RTEXT_FILTER_BRVLAN))))
msgs, err := req.Execute(syscall.NETLINK_ROUTE, syscall.RTM_NEWLINK)
if err != nil {
return nil, err
}
ret := make(map[int32][]*nl.BridgeVlanInfo)
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 nl.IFLA_AF_SPEC:
//nested attr
nestAttrs, err := nl.ParseRouteAttr(attr.Value)
if err != nil {
return nil, fmt.Errorf("failed to parse nested attr %v", err)
}
for _, nestAttr := range nestAttrs {
switch nestAttr.Attr.Type {
case nl.IFLA_BRIDGE_VLAN_INFO:
vlanInfo := nl.DeserializeBridgeVlanInfo(nestAttr.Value)
ret[msg.Index] = append(ret[msg.Index], vlanInfo)
}
}
}
}
}
return ret, nil
}
// 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 {
return pkgHandle.BridgeVlanAdd(link, vid, pvid, untagged, self, master)
}
// 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(syscall.RTM_SETLINK, link, vid, pvid, untagged, self, master)
}
// BridgeVlanDel adds a new vlan filter entry
// Equivalent to: `bridge vlan del dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]`
func BridgeVlanDel(link Link, vid uint16, pvid, untagged, self, master bool) error {
return pkgHandle.BridgeVlanDel(link, vid, pvid, untagged, self, master)
}
// 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(syscall.RTM_DELLINK, link, vid, pvid, untagged, self, master)
}
func (h *Handle) bridgeVlanModify(cmd int, link Link, vid uint16, pvid, untagged, self, master bool) error {
base := link.Attrs()
h.ensureIndex(base)
req := h.newNetlinkRequest(cmd, syscall.NLM_F_ACK)
msg := nl.NewIfInfomsg(syscall.AF_BRIDGE)
msg.Index = int32(base.Index)
req.AddData(msg)
br := nl.NewRtAttr(nl.IFLA_AF_SPEC, nil)
var flags uint16
if self {
flags |= nl.BRIDGE_FLAGS_SELF
}
if master {
flags |= nl.BRIDGE_FLAGS_MASTER
}
if flags > 0 {
nl.NewRtAttrChild(br, 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
}
nl.NewRtAttrChild(br, nl.IFLA_BRIDGE_VLAN_INFO, vlanInfo.Serialize())
req.AddData(br)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
if err != nil {
return err
}
return nil
}

80
bridge_linux_test.go Normal file
View File

@ -0,0 +1,80 @@
package netlink
import (
"fmt"
"io/ioutil"
"os"
"testing"
)
func TestBridgeVlan(t *testing.T) {
if os.Getenv("TRAVIS_BUILD_DIR") != "" {
t.Skipf("Travis CI worker Linux kernel version (3.13) is too old for this test")
}
tearDown := setUpNetlinkTest(t)
defer tearDown()
if err := remountSysfs(); err != nil {
t.Fatal(err)
}
bridgeName := "foo"
bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}}
if err := LinkAdd(bridge); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(fmt.Sprintf("/sys/devices/virtual/net/%s/bridge/vlan_filtering", bridgeName), []byte("1"), 0644); err != nil {
t.Fatal(err)
}
if vlanMap, err := BridgeVlanList(); err != nil {
t.Fatal(err)
} else {
if len(vlanMap) != 1 {
t.Fatal()
}
if vInfo, ok := vlanMap[int32(bridge.Index)]; !ok {
t.Fatal("vlanMap should include foo port vlan info")
} else {
if len(vInfo) != 1 {
t.Fatal()
} else {
if !vInfo[0].EngressUntag() || !vInfo[0].PortVID() || vInfo[0].Vid != 1 {
t.Fatal("bridge vlan show get wrong return %s", vInfo[0].String())
}
}
}
}
dummy := &Dummy{LinkAttrs: LinkAttrs{Name: "dum1"}}
if err := LinkAdd(dummy); err != nil {
t.Fatal(err)
}
if err := LinkSetMaster(dummy, bridge); err != nil {
t.Fatal(err)
}
if err := BridgeVlanAdd(dummy, 2, false, false, false, false); err != nil {
t.Fatal(err)
}
if err := BridgeVlanAdd(dummy, 3, true, true, false, false); err != nil {
t.Fatal(err)
}
if vlanMap, err := BridgeVlanList(); err != nil {
t.Fatal(err)
} else {
if len(vlanMap) != 2 {
t.Fatal()
}
if vInfo, ok := vlanMap[int32(bridge.Index)]; !ok {
t.Fatal("vlanMap should include foo port vlan info")
} else {
if "[{Flags:6 Vid:1}]" != fmt.Sprintf("%v", vInfo) {
t.Fatalf("unexpected result %v", vInfo)
}
}
if vInfo, ok := vlanMap[int32(dummy.Index)]; !ok {
t.Fatal("vlanMap should include dum1 port vlan info")
} else {
if "[{Flags:4 Vid:1} {Flags:0 Vid:2} {Flags:6 Vid:3}]" != fmt.Sprintf("%v", vInfo) {
t.Fatalf("unexpected result %v", vInfo)
}
}
}
}

View File

@ -7,6 +7,7 @@ import (
"os"
"runtime"
"strings"
"syscall"
"testing"
"github.com/vishvananda/netns"
@ -81,3 +82,13 @@ func setUpNetlinkTestWithKModule(t *testing.T, name string) tearDownNetlinkTest
}
return setUpNetlinkTest(t)
}
func remountSysfs() error {
if err := syscall.Mount("", "/", "none", syscall.MS_SLAVE|syscall.MS_REC, ""); err != nil {
return err
}
if err := syscall.Unmount("/sys", syscall.MNT_DETACH); err != nil {
return err
}
return syscall.Mount("", "/sys", "sysfs", 0, "")
}

74
nl/bridge_linux.go Normal file
View File

@ -0,0 +1,74 @@
package nl
import (
"fmt"
"unsafe"
)
const (
SizeofBridgeVlanInfo = 0x04
)
/* Bridge Flags */
const (
BRIDGE_FLAGS_MASTER = iota /* Bridge command to/from master */
BRIDGE_FLAGS_SELF /* Bridge command to/from lowerdev */
)
/* Bridge management nested attributes
* [IFLA_AF_SPEC] = {
* [IFLA_BRIDGE_FLAGS]
* [IFLA_BRIDGE_MODE]
* [IFLA_BRIDGE_VLAN_INFO]
* }
*/
const (
IFLA_BRIDGE_FLAGS = iota
IFLA_BRIDGE_MODE
IFLA_BRIDGE_VLAN_INFO
)
const (
BRIDGE_VLAN_INFO_MASTER = 1 << iota
BRIDGE_VLAN_INFO_PVID
BRIDGE_VLAN_INFO_UNTAGGED
BRIDGE_VLAN_INFO_RANGE_BEGIN
BRIDGE_VLAN_INFO_RANGE_END
)
// struct bridge_vlan_info {
// __u16 flags;
// __u16 vid;
// };
type BridgeVlanInfo struct {
Flags uint16
Vid uint16
}
func (b *BridgeVlanInfo) Serialize() []byte {
return (*(*[SizeofBridgeVlanInfo]byte)(unsafe.Pointer(b)))[:]
}
func DeserializeBridgeVlanInfo(b []byte) *BridgeVlanInfo {
return (*BridgeVlanInfo)(unsafe.Pointer(&b[0:SizeofBridgeVlanInfo][0]))
}
func (b *BridgeVlanInfo) PortVID() bool {
return b.Flags&BRIDGE_VLAN_INFO_PVID > 0
}
func (b *BridgeVlanInfo) EngressUntag() bool {
return b.Flags&BRIDGE_VLAN_INFO_UNTAGGED > 0
}
func (b *BridgeVlanInfo) String() string {
return fmt.Sprintf("%+v", *b)
}
/* New extended info filters for IFLA_EXT_MASK */
const (
RTEXT_FILTER_VF = 1 << iota
RTEXT_FILTER_BRVLAN
RTEXT_FILTER_BRVLAN_COMPRESSED
)

35
nl/bridge_linux_test.go Normal file
View File

@ -0,0 +1,35 @@
package nl
import (
"bytes"
"crypto/rand"
"encoding/binary"
"testing"
)
func (msg *BridgeVlanInfo) write(b []byte) {
native := NativeEndian()
native.PutUint16(b[0:2], msg.Flags)
native.PutUint16(b[2:4], msg.Vid)
}
func (msg *BridgeVlanInfo) serializeSafe() []byte {
length := SizeofBridgeVlanInfo
b := make([]byte, length)
msg.write(b)
return b
}
func deserializeBridgeVlanInfoSafe(b []byte) *BridgeVlanInfo {
var msg = BridgeVlanInfo{}
binary.Read(bytes.NewReader(b[0:SizeofBridgeVlanInfo]), NativeEndian(), &msg)
return &msg
}
func TestBridgeVlanInfoDeserializeSerialize(t *testing.T) {
var orig = make([]byte, SizeofBridgeVlanInfo)
rand.Read(orig)
safemsg := deserializeBridgeVlanInfoSafe(orig)
msg := DeserializeBridgeVlanInfo(orig)
testDeserializeSerialize(t, orig, safemsg, msg)
}