Skip to content
Merged
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
286 changes: 286 additions & 0 deletions netlink_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ package ipvs

import (
"errors"
"net"
"reflect"
"syscall"
"testing"

"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)

// unknownAttrType represents an unrecognized netlink attribute,
// used to verify that unknown attributes are ignored.
const unknownAttrType = 0x7fff

func Test_getIPFamily(t *testing.T) {
testcases := []struct {
name string
Expand Down Expand Up @@ -51,3 +59,281 @@ func Test_getIPFamily(t *testing.T) {
})
}
}

func Test_AssembleService(t *testing.T) {
tests := []struct {
name string
attrs []syscall.NetlinkRouteAttr
want Service
}{
{
name: "IPv4 service",
attrs: []syscall.NetlinkRouteAttr{
{
Attr: syscall.RtAttr{Type: uint16(ipvsSvcAttrAddress)},
Value: net.IPv4(192, 0, 2, 1).To4(),
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsSvcAttrAddressFamily)},
Value: nl.Uint16Attr(unix.AF_INET),
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsSvcAttrProtocol)},
Value: nl.Uint16Attr(unix.IPPROTO_TCP),
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsSvcAttrPort)},
Value: []byte{0x00, 0x50},
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsSvcAttrSchedName)},
Value: nl.ZeroTerminated("rr"),
},
{
// Unknown attributes should be ignored for forward compatibility.
Attr: syscall.RtAttr{Type: unknownAttrType},
Value: []byte{1, 2, 3, 4},
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsSvcAttrPEName)},
Value: nl.ZeroTerminated("sip"),
},
},
want: Service{
AddressFamily: unix.AF_INET,
Address: net.IPv4(192, 0, 2, 1).To4(),
Protocol: unix.IPPROTO_TCP,
Port: 80,
SchedName: "rr",
PEName: "sip",
},
},
{
name: "IPv6 service",
attrs: []syscall.NetlinkRouteAttr{
{
Attr: syscall.RtAttr{Type: uint16(ipvsSvcAttrAddressFamily)},
Value: nl.Uint16Attr(unix.AF_INET6),
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsSvcAttrAddress)},
Value: net.ParseIP("2001:db8::1").To16(),
},
},
want: Service{
AddressFamily: unix.AF_INET6,
Address: net.ParseIP("2001:db8::1"),
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := assembleService(tc.attrs)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(*got, tc.want) {
t.Errorf("assembleService() = %+v, want %+v", *got, tc.want)
}
})
}
}

func Test_AssembleDestination(t *testing.T) {
tests := []struct {
name string
attrs []syscall.NetlinkRouteAttr
want Destination
}{
{
name: "IPv4 destination",
attrs: []syscall.NetlinkRouteAttr{
{
Attr: syscall.RtAttr{Type: uint16(ipvsDestAttrAddress)},
Value: net.IPv4(192, 0, 2, 2).To4(),
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsDestAttrAddressFamily)},
Value: nl.Uint16Attr(unix.AF_INET),
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsDestAttrPort)},
Value: []byte{0x1f, 0x90},
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsDestAttrWeight)},
Value: nl.Uint32Attr(65536),
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsDestAttrActiveConnections)},
Value: nl.Uint32Attr(10),
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsDestAttrInactiveConnections)},
Value: nl.Uint32Attr(20),
},
{
// Unknown attributes should be ignored for forward compatibility.
Attr: syscall.RtAttr{Type: unknownAttrType},
Value: []byte{1, 2, 3, 4},
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsDestAttrPersistentConnections)},
Value: nl.Uint32Attr(30),
},
},
want: Destination{
AddressFamily: unix.AF_INET,
Address: net.IPv4(192, 0, 2, 2).To4(),
Port: 8080,
Weight: 65536,
ActiveConnections: 10,
InactiveConnections: 20,
PersistentConnections: 30,
},
},
{
name: "IPv6 destination",
attrs: []syscall.NetlinkRouteAttr{
{
Attr: syscall.RtAttr{Type: uint16(ipvsDestAttrAddressFamily)},
Value: nl.Uint16Attr(unix.AF_INET6),
},
{
Attr: syscall.RtAttr{Type: uint16(ipvsDestAttrAddress)},
Value: net.ParseIP("2001:db8::2").To16(),
},
},
want: Destination{
AddressFamily: unix.AF_INET6,
Address: net.ParseIP("2001:db8::2"),
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := assembleDestination(tc.attrs)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(*got, tc.want) {
t.Errorf("assembleDestination() = %+v, want %+v", *got, tc.want)
}
})
}
}

func Test_AssembleStats(t *testing.T) {
tests := []struct {
name string
attr ipvsStats
value []byte
want SvcStats
}{
{
name: "connections",
attr: ipvsStatsConns,
value: nl.Uint32Attr(65536),
want: SvcStats{Connections: 65536},
},
{
name: "packets in",
attr: ipvsStatsPktsIn,
value: nl.Uint32Attr(123456),
want: SvcStats{PacketsIn: 123456},
},
{
name: "packets out",
attr: ipvsStatsPktsOut,
value: nl.Uint32Attr(234567),
want: SvcStats{PacketsOut: 234567},
},
{
name: "bytes in",
attr: ipvsStatsBytesIn,
value: nl.Uint64Attr(1 << 40),
want: SvcStats{BytesIn: 1 << 40},
},
{
name: "bytes out",
attr: ipvsStatsBytesOut,
value: nl.Uint64Attr(1 << 41),
want: SvcStats{BytesOut: 1 << 41},
},
{
name: "connections per second",
attr: ipvsStatsCPS,
value: nl.Uint32Attr(100),
want: SvcStats{CPS: 100},
},
{
name: "packets in per second",
attr: ipvsStatsPPSIn,
value: nl.Uint32Attr(200),
want: SvcStats{PPSIn: 200},
},
{
name: "packets out per second",
attr: ipvsStatsPPSOut,
value: nl.Uint32Attr(300),
want: SvcStats{PPSOut: 300},
},
{
name: "bytes in per second",
attr: ipvsStatsBPSIn,
value: nl.Uint32Attr(400),
want: SvcStats{BPSIn: 400},
},
{
name: "bytes out per second",
attr: ipvsStatsBPSOut,
value: nl.Uint32Attr(500),
want: SvcStats{BPSOut: 500},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
attr := nl.NewRtAttr(int(tc.attr), tc.value)

got, err := assembleStats(attr.Serialize())
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("assembleStats() = %+v, want %+v", got, tc.want)
}
})
}

t.Run("unknown attribute", func(t *testing.T) {
// Unknown attributes must not prevent subsequent attributes
// from being decoded.
unknown := nl.NewRtAttr(unknownAttrType, []byte{1, 2, 3, 4})
known := nl.NewRtAttr(int(ipvsStatsConns), nl.Uint32Attr(123))

msg := append(unknown.Serialize(), known.Serialize()...)
got, err := assembleStats(msg)
if err != nil {
t.Fatal(err)
}
want := SvcStats{Connections: 123}
if !reflect.DeepEqual(got, want) {
t.Errorf("assembleStats() = %+v, want %+v", got, want)
}
})

t.Run("malformed attribute", func(t *testing.T) {
// The attribute header claims more bytes than are available.
msg := make([]byte, 4)
native.PutUint16(msg[:2], 8)
native.PutUint16(msg[2:], uint16(ipvsStatsConns))

_, err := assembleStats(msg)
if err == nil {
t.Fatal("assembleStats() expected an error for malformed attribute")
}
})
}
Loading