From 7593cff56f70016d6139dbd2a8272e0f04976b56 Mon Sep 17 00:00:00 2001 From: Chun Chen Date: Tue, 6 Jun 2017 17:09:16 +0800 Subject: [PATCH] Add bridge vlan support --- bridge_linux.go | 115 ++++++++++++++++++++++++++++++++++++++++ bridge_linux_test.go | 80 ++++++++++++++++++++++++++++ netlink_test.go | 11 ++++ nl/bridge_linux.go | 74 ++++++++++++++++++++++++++ nl/bridge_linux_test.go | 35 ++++++++++++ 5 files changed, 315 insertions(+) create mode 100644 bridge_linux.go create mode 100644 bridge_linux_test.go create mode 100644 nl/bridge_linux.go create mode 100644 nl/bridge_linux_test.go diff --git a/bridge_linux.go b/bridge_linux.go new file mode 100644 index 0000000..a65d6a1 --- /dev/null +++ b/bridge_linux.go @@ -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 +} diff --git a/bridge_linux_test.go b/bridge_linux_test.go new file mode 100644 index 0000000..e9eb541 --- /dev/null +++ b/bridge_linux_test.go @@ -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) + } + } + } +} diff --git a/netlink_test.go b/netlink_test.go index 7e35e40..173d287 100644 --- a/netlink_test.go +++ b/netlink_test.go @@ -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, "") +} diff --git a/nl/bridge_linux.go b/nl/bridge_linux.go new file mode 100644 index 0000000..6c0d333 --- /dev/null +++ b/nl/bridge_linux.go @@ -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 +) diff --git a/nl/bridge_linux_test.go b/nl/bridge_linux_test.go new file mode 100644 index 0000000..8f952fc --- /dev/null +++ b/nl/bridge_linux_test.go @@ -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) +}