mirror of
https://github.com/vishvananda/netlink
synced 2024-12-26 08:32:19 +00:00
Add bridge vlan support
This commit is contained in:
parent
bd6d5de5cc
commit
7593cff56f
115
bridge_linux.go
Normal file
115
bridge_linux.go
Normal 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
80
bridge_linux_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
74
nl/bridge_linux.go
Normal 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
35
nl/bridge_linux_test.go
Normal 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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user