mirror of
https://github.com/jsimonetti/rtnetlink.git
synced 2026-03-29 08:51:53 +02:00
432 lines
9.7 KiB
Go
432 lines
9.7 KiB
Go
package rtnetlink
|
|
|
|
import (
|
|
"net"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/jsimonetti/rtnetlink/internal/unix"
|
|
)
|
|
|
|
// Tests will only pass on little endian machines
|
|
|
|
func TestRouteMessageMarshalUnmarshalBinary(t *testing.T) {
|
|
skipBigEndian(t)
|
|
|
|
var (
|
|
timeout = uint32(255)
|
|
pref = uint8(1)
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
m *RouteMessage
|
|
b []byte
|
|
}{
|
|
{
|
|
name: "empty",
|
|
m: &RouteMessage{},
|
|
b: []byte{
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
},
|
|
{
|
|
name: "no attributes",
|
|
m: &RouteMessage{
|
|
Family: unix.AF_INET,
|
|
DstLength: 8,
|
|
Type: unix.RTN_UNICAST,
|
|
},
|
|
b: []byte{
|
|
0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
},
|
|
{
|
|
name: "full",
|
|
m: &RouteMessage{
|
|
Family: 2,
|
|
DstLength: 8,
|
|
Table: unix.RT_TABLE_MAIN,
|
|
Protocol: unix.RTPROT_STATIC,
|
|
Scope: unix.RT_SCOPE_UNIVERSE,
|
|
Type: unix.RTN_UNICAST,
|
|
Attributes: RouteAttributes{
|
|
Dst: net.IPv4(10, 0, 0, 0),
|
|
Src: net.IPv4(10, 100, 10, 1),
|
|
Gateway: net.IPv4(10, 0, 0, 1),
|
|
OutIface: 5,
|
|
Priority: 1,
|
|
Table: 2,
|
|
Mark: 3,
|
|
Pref: &pref,
|
|
Expires: &timeout,
|
|
Metrics: &RouteMetrics{
|
|
AdvMSS: 1,
|
|
Features: 0xffffffff,
|
|
InitCwnd: 2,
|
|
InitRwnd: 3,
|
|
MTU: 1500,
|
|
},
|
|
Multipath: []NextHop{
|
|
{
|
|
Hop: RTNextHop{
|
|
Length: 16,
|
|
IfIndex: 1,
|
|
},
|
|
Gateway: net.IPv4(10, 0, 0, 2),
|
|
},
|
|
{
|
|
Hop: RTNextHop{
|
|
Length: 16,
|
|
IfIndex: 2,
|
|
},
|
|
Gateway: net.IPv4(10, 0, 0, 3),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
b: []byte{
|
|
// RouteMessage struct literal
|
|
//
|
|
// Family
|
|
0x02,
|
|
// DstLength
|
|
0x08,
|
|
// SrcLength
|
|
0x00,
|
|
// Tos
|
|
0x00,
|
|
// Table
|
|
0xfe,
|
|
// Protocol
|
|
0x04,
|
|
// Scope
|
|
0x00,
|
|
// Type
|
|
0x01,
|
|
// Flags
|
|
0x00, 0x00, 0x00, 0x00,
|
|
// RouteAttributes
|
|
// 2 bytes length, 2 bytes type, N bytes value
|
|
//
|
|
// Dst
|
|
0x08, 0x00, 0x01, 0x00,
|
|
0x0a, 0x00, 0x00, 0x00,
|
|
// Src
|
|
0x08, 0x00, 0x07, 0x00,
|
|
0x0a, 0x64, 0x0a, 0x01,
|
|
// Gateway
|
|
0x08, 0x00, 0x05, 0x00,
|
|
0x0a, 0x00, 0x00, 0x01,
|
|
// OutIface
|
|
0x08, 0x00, 0x04, 0x00,
|
|
0x05, 0x00, 0x00, 0x00,
|
|
// Priority
|
|
0x08, 0x00, 0x06, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
// Table
|
|
0x08, 0x00, 0x0f, 0x00,
|
|
0x02, 0x00, 0x00, 0x00,
|
|
// Mark
|
|
0x08, 0x00, 0x10, 0x00,
|
|
0x03, 0x00, 0x00, 0x00,
|
|
// Pref
|
|
0x05, 0x00, 0x14, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
// Expires
|
|
0x08, 0x00, 0x17, 0x00,
|
|
0xff, 0x00, 0x00, 0x00,
|
|
// RouteMetrics
|
|
// Length must be manually adjusted as more fields are added.
|
|
0x2c, 0x00, 0x08, 0x80,
|
|
// AdvMSS
|
|
0x08, 0x00, 0x08, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
// Features
|
|
0x08, 0x00, 0x0c, 0x00,
|
|
0xff, 0xff, 0xff, 0xff,
|
|
// InitCwnd
|
|
0x08, 0x00, 0x0b, 0x00,
|
|
0x02, 0x00, 0x00, 0x00,
|
|
// InitRwnd
|
|
0x08, 0x00, 0x0e, 0x00,
|
|
0x03, 0x00, 0x00, 0x00,
|
|
// MTU
|
|
0x08, 0x00, 0x02, 0x00,
|
|
0xdc, 0x05, 0x00, 0x00,
|
|
// Multipath
|
|
//
|
|
// 2 bytes length, 2 bytes type, then repeated 8 byte rtnexthop
|
|
// structures followed by their nested netlink attributes.
|
|
0x24, 0x00, 0x09, 0x00,
|
|
// rtnexthop
|
|
0x10, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
// rtnexthop attributes
|
|
0x08, 0x00, 0x05, 0x00,
|
|
// Gateway
|
|
10, 0, 0, 2,
|
|
// rtnexthop
|
|
0x10, 0x00, 0x00, 0x00,
|
|
0x02, 0x00, 0x00, 0x00,
|
|
// rtnexthop attributes
|
|
0x08, 0x00, 0x05, 0x00,
|
|
// Gateway
|
|
10, 0, 0, 3,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// It's important to be able to parse raw bytes into valid
|
|
// structures so we start with that step first. After, we'll do a
|
|
// marshaling round-trip to ensure that the structure's byte output
|
|
// and parsed form match what is expected, while also comparing
|
|
// against the expected fixtures throughout.
|
|
var m1 RouteMessage
|
|
if err := m1.UnmarshalBinary(tt.b); err != nil {
|
|
t.Fatalf("failed to unmarshal first message from binary: %v", err)
|
|
}
|
|
|
|
if diff := cmp.Diff(tt.m, &m1); diff != "" {
|
|
t.Fatalf("unexpected first message (-want +got):\n%s", diff)
|
|
}
|
|
|
|
b, err := m1.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal first message binary: %v", err)
|
|
}
|
|
|
|
if diff := cmp.Diff(tt.b, b); diff != "" {
|
|
t.Fatalf("unexpected first message bytes (-want +got):\n%s", diff)
|
|
}
|
|
|
|
var m2 RouteMessage
|
|
if err := m2.UnmarshalBinary(b); err != nil {
|
|
t.Fatalf("failed to unmarshal second message from binary: %v", err)
|
|
}
|
|
|
|
if diff := cmp.Diff(&m1, &m2); diff != "" {
|
|
t.Fatalf("unexpected parsed messages (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouteMessageMarshalRoundTrip(t *testing.T) {
|
|
skipBigEndian(t)
|
|
|
|
// The above tests begin with unmarshaling raw bytes and are more
|
|
// comprehensive, but due to the complexity of nested route message
|
|
// attributes and structures, it has become rather difficult to maintain
|
|
// over time. These tests will focus on a subset of that functionality to
|
|
// ensure that marshaling and unmarshaling perform symmetrical operations
|
|
// given a proper Go type as input, rather than raw bytes.
|
|
|
|
tests := []struct {
|
|
name string
|
|
m *RouteMessage
|
|
}{
|
|
{
|
|
name: "multipath IPv4 MPLS",
|
|
m: &RouteMessage{
|
|
Attributes: RouteAttributes{
|
|
Multipath: []NextHop{
|
|
{
|
|
Hop: RTNextHop{
|
|
Length: 36,
|
|
IfIndex: 1,
|
|
},
|
|
Gateway: net.IPv4(10, 0, 0, 2),
|
|
MPLS: []MPLSNextHop{{
|
|
Label: 1,
|
|
TrafficClass: 1,
|
|
BottomOfStack: true,
|
|
TTL: 1,
|
|
}},
|
|
},
|
|
{
|
|
Hop: RTNextHop{
|
|
Length: 40,
|
|
IfIndex: 2,
|
|
},
|
|
Gateway: net.IPv4(10, 0, 0, 3),
|
|
MPLS: []MPLSNextHop{
|
|
{
|
|
Label: 1,
|
|
TrafficClass: 1,
|
|
TTL: 1,
|
|
},
|
|
{
|
|
Label: 2,
|
|
TrafficClass: 2,
|
|
BottomOfStack: true,
|
|
TTL: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "multipath IPv6 MPLS",
|
|
m: &RouteMessage{
|
|
Attributes: RouteAttributes{
|
|
Multipath: []NextHop{
|
|
{
|
|
Hop: RTNextHop{
|
|
Length: 48,
|
|
IfIndex: 1,
|
|
},
|
|
Gateway: net.ParseIP("2001:db8::1"),
|
|
MPLS: []MPLSNextHop{{
|
|
Label: 1,
|
|
TrafficClass: 1,
|
|
BottomOfStack: true,
|
|
TTL: 1,
|
|
}},
|
|
},
|
|
{
|
|
Hop: RTNextHop{
|
|
Length: 52,
|
|
IfIndex: 2,
|
|
},
|
|
Gateway: net.ParseIP("2001:db8::2"),
|
|
MPLS: []MPLSNextHop{
|
|
{
|
|
Label: 1,
|
|
TrafficClass: 1,
|
|
TTL: 1,
|
|
},
|
|
{
|
|
Label: 2,
|
|
TrafficClass: 2,
|
|
BottomOfStack: true,
|
|
TTL: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// First do a marshaling and unmarshaling round trip to ensure
|
|
// the inputs and outputs are identical.
|
|
b1, err := tt.m.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal test message: %v", err)
|
|
}
|
|
|
|
var m RouteMessage
|
|
if err := m.UnmarshalBinary(b1); err != nil {
|
|
t.Fatalf("failed to unmarshal: %v", err)
|
|
}
|
|
|
|
if diff := cmp.Diff(tt.m, &m); diff != "" {
|
|
t.Fatalf("unexpected RouteMessage after round-trip (-want +got):\n%s", diff)
|
|
}
|
|
|
|
// Then compare the results of the first marshaled bytes against
|
|
// the newly marshaled bytes.
|
|
b2, err := m.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal parsed message: %v", err)
|
|
}
|
|
|
|
if diff := cmp.Diff(b1, b2); diff != "" {
|
|
t.Fatalf("unexpected final raw byte output (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouteMessageUnmarshalBinaryErrors(t *testing.T) {
|
|
skipBigEndian(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
b []byte
|
|
m Message
|
|
}{
|
|
{
|
|
name: "empty",
|
|
},
|
|
{
|
|
name: "short",
|
|
b: make([]byte, 3),
|
|
},
|
|
{
|
|
name: "invalid attr",
|
|
b: []byte{
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x04, 0x00, 0x01, 0x00, 0x04, 0x00, 0x02, 0x00,
|
|
0x05, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x08, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var m RouteMessage
|
|
err := m.UnmarshalBinary(tt.b)
|
|
if err == nil {
|
|
t.Fatal("expected an error, but none occurred")
|
|
}
|
|
|
|
t.Logf("err: %v", err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouteMessageFuzz(t *testing.T) {
|
|
skipBigEndian(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
s string
|
|
}{
|
|
// Strings in this test table are copied from go-fuzz crashers.
|
|
{
|
|
name: "short rtnexthop",
|
|
s: "\xef\xbf\xea\x00\a\x00\xd1\xea\xf9A\b\xf9\b\x00\t\x00\xbfA\b\xf9" +
|
|
"\b\x00\a\x00\xf9A\b\xf9\b\x00\a\x00\xbfA\b\xf9\b\x00\a\x00" +
|
|
"\xd3\xea\xf9A\b\x00\a\u007f\xff\xff\xffA\b\x00\a\x00\xd3\xea\xf9A" +
|
|
"\b\x00\a\x00\xbfA\b\xf9\b\x00\a\x00\xd3\xea\xf9A\b\x00\a\x00" +
|
|
"\xd3\xea\xf9A\b\x00\a\x00\xbfA\b\xf9\b\x00\a\x00\xd3-\xbf\xbd",
|
|
},
|
|
{
|
|
name: "out of bounds attributes length",
|
|
s: "000000000000\x14\x00\t\x000\xea00" +
|
|
"000000000000",
|
|
},
|
|
{
|
|
name: "bad rtnexthop length",
|
|
s: "000000000000!\x00\t\x00\b\x0000" +
|
|
"0000\b\x00000000\x06\x00000000" +
|
|
"00000",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var m RouteMessage
|
|
if err := m.UnmarshalBinary([]byte(tt.s)); err == nil {
|
|
t.Fatal("expected an error, but none occurred")
|
|
}
|
|
})
|
|
}
|
|
}
|