Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions driver/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func (b *Bridge) Encode(ae *netlink.AttributeEncoder) error {
ae.Uint8(unix.IFLA_BR_VLAN_FILTERING, uint8(*b.VlanFiltering))
}
if b.VlanProtocol != nil {
ae.Uint16(unix.IFLA_BR_VLAN_PROTOCOL, uint16(*b.VlanProtocol))
b.VlanProtocol.encode(ae, unix.IFLA_BR_VLAN_PROTOCOL)
}
if b.GroupFwdMask != nil {
ae.Uint16(unix.IFLA_BR_GROUP_FWD_MASK, *b.GroupFwdMask)
Expand Down Expand Up @@ -302,7 +302,7 @@ func (b *Bridge) Decode(ad *netlink.AttributeDecoder) error {
v := BridgeEnable(ad.Uint8())
b.VlanFiltering = &v
case unix.IFLA_BR_VLAN_PROTOCOL:
v := VlanProtocol(ad.Uint16())
v := decodeVlanProtocol(ad)
b.VlanProtocol = &v
case unix.IFLA_BR_GROUP_FWD_MASK:
v := ad.Uint16()
Expand Down
38 changes: 16 additions & 22 deletions driver/bridge_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package driver

import (
"encoding/binary"
"net"
"testing"

Expand Down Expand Up @@ -188,23 +189,14 @@ func TestBridgeDecodeRaw(t *testing.T) {
}{
{
name: "STP state",
b: []byte{
0x08, 0x00, // Length: 8
unix.IFLA_BR_STP_STATE, 0x00, // Type: IFLA_BR_STP_STATE
0x01, 0x00, 0x00, 0x00, // Value: 1
},
b: rawBridgeAttribute(unix.IFLA_BR_STP_STATE, binary.NativeEndian.AppendUint32(nil, 1)),
want: &Bridge{
StpState: ptr(BridgeStpStateEnabled),
},
},
{
name: "VLAN filtering",
b: []byte{
0x05, 0x00, // Length: 5
unix.IFLA_BR_VLAN_FILTERING, 0x00, // Type: IFLA_BR_VLAN_FILTERING
0x01, // Value: 1
0x00, 0x00, 0x00, // Padding
},
b: rawBridgeAttribute(unix.IFLA_BR_VLAN_FILTERING, []byte{1}),
want: &Bridge{
VlanFiltering: ptr(BridgeEnableEnabled),
},
Expand Down Expand Up @@ -383,23 +375,14 @@ func TestBridgePortDecodeRaw(t *testing.T) {
}{
{
name: "port state",
b: []byte{
0x05, 0x00, // Length: 5
unix.IFLA_BRPORT_STATE, 0x00, // Type: IFLA_BRPORT_STATE
0x03, // Value: 3 (forwarding)
0x00, 0x00, 0x00, // Padding
},
b: rawBridgeAttribute(unix.IFLA_BRPORT_STATE, []byte{3}),
want: &BridgePort{
State: ptr(BridgePortStateForwarding),
},
},
{
name: "port cost",
b: []byte{
0x08, 0x00, // Length: 8
unix.IFLA_BRPORT_COST, 0x00, // Type: IFLA_BRPORT_COST
0x64, 0x00, 0x00, 0x00, // Value: 100
},
b: rawBridgeAttribute(unix.IFLA_BRPORT_COST, binary.NativeEndian.AppendUint32(nil, 100)),
want: &BridgePort{
Cost: ptr(uint32(100)),
},
Expand Down Expand Up @@ -429,3 +412,14 @@ func TestBridgePortDecodeRaw(t *testing.T) {
func ptr[T any](t T) *T {
return &t
}

// rawBridgeAttribute builds a native-endian netlink header around a raw payload.
func rawBridgeAttribute(typ uint16, payload []byte) []byte {
b := binary.NativeEndian.AppendUint16(nil, uint16(4+len(payload)))
b = binary.NativeEndian.AppendUint16(b, typ)
b = append(b, payload...)
for len(b)%4 != 0 {
b = append(b, 0)
}
return b
}
26 changes: 22 additions & 4 deletions driver/vlan.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package driver

import (
"encoding/binary"
"fmt"

"github.com/jsimonetti/rtnetlink/v2"
Expand All @@ -15,10 +16,10 @@ type VlanProtocol uint16
// VLAN protocols.
const (
// VlanProtocol8021Q represents 802.1Q VLAN tagging (standard VLAN).
VlanProtocol8021Q VlanProtocol = 0x0081
VlanProtocol8021Q VlanProtocol = 0x8100

// VlanProtocol8021AD represents 802.1ad QinQ (VLAN stacking).
VlanProtocol8021AD VlanProtocol = 0xA888
VlanProtocol8021AD VlanProtocol = 0x88A8
)

// String returns a string representation of the VlanProtocol.
Expand All @@ -33,6 +34,23 @@ func (p VlanProtocol) String() string {
}
}

// VLAN protocol payloads use network byte order even though the surrounding
// attributes use native byte order.
func (p VlanProtocol) encode(ae *netlink.AttributeEncoder, typ uint16) {
order := ae.ByteOrder
ae.ByteOrder = binary.BigEndian
ae.Uint16(typ, uint16(p))
ae.ByteOrder = order
}

func decodeVlanProtocol(ad *netlink.AttributeDecoder) VlanProtocol {
order := ad.ByteOrder
ad.ByteOrder = binary.BigEndian
protocol := VlanProtocol(ad.Uint16())
ad.ByteOrder = order
return protocol
}

// VlanFlag represents VLAN flags.
type VlanFlag uint32

Expand Down Expand Up @@ -88,7 +106,7 @@ func (v *Vlan) Encode(ae *netlink.AttributeEncoder) error {
}

if v.Protocol != nil {
ae.Uint16(unix.IFLA_VLAN_PROTOCOL, uint16(*v.Protocol))
v.Protocol.encode(ae, unix.IFLA_VLAN_PROTOCOL)
}

if v.Flags != nil {
Expand Down Expand Up @@ -138,7 +156,7 @@ func (v *Vlan) Decode(ad *netlink.AttributeDecoder) error {
v.ID = &id

case unix.IFLA_VLAN_PROTOCOL:
protocol := VlanProtocol(ad.Uint16())
protocol := decodeVlanProtocol(ad)
v.Protocol = &protocol

case unix.IFLA_VLAN_FLAGS:
Expand Down
92 changes: 92 additions & 0 deletions driver/vlan_protocol_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package driver

import (
"bytes"
"encoding/binary"
"testing"

"github.com/jsimonetti/rtnetlink/v2"
"github.com/jsimonetti/rtnetlink/v2/internal/unix"
"github.com/mdlayher/netlink"
)

func TestVlanProtocolNetworkOrder(t *testing.T) {
for _, protocol := range []struct {
name string
value VlanProtocol
wire []byte
}{
{"802.1Q", VlanProtocol8021Q, []byte{0x81, 0x00}},
{"802.1ad", VlanProtocol8021AD, []byte{0x88, 0xa8}},
} {
for _, order := range []binary.ByteOrder{binary.LittleEndian, binary.BigEndian} {
for _, driver := range []struct {
name string
new func() rtnetlink.LinkDriver
configured rtnetlink.LinkDriver
protocolType uint16
nativeType uint16
}{
{"vlan", func() rtnetlink.LinkDriver { return &Vlan{} }, &Vlan{Protocol: &protocol.value}, unix.IFLA_VLAN_PROTOCOL, unix.IFLA_VLAN_ID},
{"bridge", func() rtnetlink.LinkDriver { return &Bridge{} }, &Bridge{VlanProtocol: &protocol.value}, unix.IFLA_BR_VLAN_PROTOCOL, unix.IFLA_BR_GROUP_FWD_MASK},
} {
t.Run(protocol.name+"/"+order.String()+"/"+driver.name, func(t *testing.T) {
ae := netlink.NewAttributeEncoder()
ae.ByteOrder = order
if err := driver.configured.Encode(ae); err != nil {
t.Fatal(err)
}
// Protocol byte order must not change later native-endian attributes.
ae.Uint16(driver.nativeType, 0x1234)
b, err := ae.Encode()
if err != nil {
t.Fatal(err)
}
attrs, err := netlink.UnmarshalAttributes(b)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(attrs[0].Data, protocol.wire) {
t.Errorf("protocol payload: got %x, want %x", attrs[0].Data, protocol.wire)
}
if got := order.Uint16(attrs[1].Data); got != 0x1234 {
t.Errorf("native attribute: got %x, want 1234", got)
}

// Decode an independent kernel-format fixture, not the encoder output.
ae = netlink.NewAttributeEncoder()
ae.ByteOrder = order
ae.Bytes(driver.protocolType, protocol.wire)
ae.Uint16(driver.nativeType, 0x1234)
b, err = ae.Encode()
if err != nil {
t.Fatal(err)
}
ad, err := netlink.NewAttributeDecoder(b)
if err != nil {
t.Fatal(err)
}
ad.ByteOrder = order
decoded := driver.new()
if err := decoded.Decode(ad); err != nil {
t.Fatal(err)
}
var gotProtocol *VlanProtocol
var gotNative *uint16
switch d := decoded.(type) {
case *Vlan:
gotProtocol, gotNative = d.Protocol, d.ID
case *Bridge:
gotProtocol, gotNative = d.VlanProtocol, d.GroupFwdMask
}
if gotProtocol == nil || *gotProtocol != protocol.value {
t.Errorf("decoded protocol: got %v, want %v", gotProtocol, protocol.value)
}
if gotNative == nil || *gotNative != 0x1234 {
t.Errorf("decoded native attribute: got %v, want 1234", gotNative)
}
})
}
}
}
}
2 changes: 1 addition & 1 deletion driver/vlan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func TestVlanDecodeRaw(t *testing.T) {
data: func() []byte {
ae := netlink.NewAttributeEncoder()
ae.Uint16(unix.IFLA_VLAN_ID, 100)
ae.Uint16(unix.IFLA_VLAN_PROTOCOL, uint16(VlanProtocol8021Q))
ae.Bytes(unix.IFLA_VLAN_PROTOCOL, []byte{0x81, 0x00})
b, _ := ae.Encode()
return b
}(),
Expand Down
Loading