mirror of
https://github.com/vishvananda/netlink
synced 2024-12-27 00:52:11 +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"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/vishvananda/netns"
|
"github.com/vishvananda/netns"
|
||||||
@ -81,3 +82,13 @@ func setUpNetlinkTestWithKModule(t *testing.T, name string) tearDownNetlinkTest
|
|||||||
}
|
}
|
||||||
return setUpNetlinkTest(t)
|
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