mirror of
https://github.com/jsimonetti/rtnetlink.git
synced 2026-03-28 23:41:57 +01:00
Add drivers for bridge, macvlan, vlan, vxlan and add helpers to LinkService to use them (SetMaster, RemoveMaster) Signed-off-by: Jeroen Simonetti <jeroen@simonetti.nl>
402 lines
9.1 KiB
Go
402 lines
9.1 KiB
Go
//go:build integration
|
|
// +build integration
|
|
|
|
package rtnetlink
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/cilium/ebpf"
|
|
"github.com/cilium/ebpf/asm"
|
|
"github.com/cilium/ebpf/rlimit"
|
|
"github.com/jsimonetti/rtnetlink/v2/internal/testutils"
|
|
"github.com/jsimonetti/rtnetlink/v2/internal/unix"
|
|
"github.com/mdlayher/netlink"
|
|
)
|
|
|
|
// lo accesses the loopback interface present in every network namespace.
|
|
var lo uint32 = 1
|
|
|
|
func xdpPrograms(tb testing.TB) (int32, int32) {
|
|
tb.Helper()
|
|
|
|
// Load XDP_PASS into the return value register.
|
|
bpfProgram := &ebpf.ProgramSpec{
|
|
Type: ebpf.XDP,
|
|
Instructions: asm.Instructions{
|
|
asm.LoadImm(asm.R0, int64(2), asm.DWord),
|
|
asm.Return(),
|
|
},
|
|
License: "MIT",
|
|
}
|
|
prog1, err := ebpf.NewProgram(bpfProgram)
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
prog2, err := ebpf.NewProgram(bpfProgram)
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
tb.Cleanup(func() {
|
|
prog1.Close()
|
|
prog2.Close()
|
|
})
|
|
|
|
// Use the file descriptor of the programs
|
|
return int32(prog1.FD()), int32(prog2.FD())
|
|
}
|
|
|
|
func attachXDP(tb testing.TB, conn *Conn, ifIndex uint32, xdp *LinkXDP) {
|
|
tb.Helper()
|
|
|
|
message := LinkMessage{
|
|
Family: unix.AF_UNSPEC,
|
|
Index: ifIndex,
|
|
Attributes: &LinkAttributes{
|
|
XDP: xdp,
|
|
},
|
|
}
|
|
|
|
if err := conn.Link.Set(&message); err != nil {
|
|
tb.Fatalf("attaching program with fd %d to link at ifindex %d: %s", xdp.FD, ifIndex, err)
|
|
}
|
|
}
|
|
|
|
// getXDP returns the XDP attach, XDP prog ID and errors when the
|
|
// interface could not be fetched
|
|
func getXDP(tb testing.TB, conn *Conn, ifIndex uint32) (uint8, uint32) {
|
|
tb.Helper()
|
|
|
|
interf, err := conn.Link.Get(ifIndex)
|
|
if err != nil {
|
|
tb.Fatalf("getting link xdp properties: %s", err)
|
|
}
|
|
|
|
return interf.Attributes.XDP.Attached, interf.Attributes.XDP.ProgID
|
|
}
|
|
|
|
func TestLinkXDPAttach(t *testing.T) {
|
|
if err := rlimit.RemoveMemlock(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
conn, err := Dial(&netlink.Config{NetNS: testutils.NetNS(t)})
|
|
if err != nil {
|
|
t.Fatalf("failed to establish netlink socket: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
progFD1, progFD2 := xdpPrograms(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
xdp *LinkXDP
|
|
}{
|
|
{
|
|
name: "with FD, no expected FD",
|
|
xdp: &LinkXDP{
|
|
FD: progFD1,
|
|
Flags: unix.XDP_FLAGS_SKB_MODE,
|
|
},
|
|
},
|
|
{
|
|
name: "with FD, expected FD == FD",
|
|
xdp: &LinkXDP{
|
|
FD: progFD1,
|
|
ExpectedFD: progFD1,
|
|
Flags: unix.XDP_FLAGS_SKB_MODE,
|
|
},
|
|
},
|
|
{
|
|
name: "with FD, expected FD != FD",
|
|
xdp: &LinkXDP{
|
|
FD: progFD1,
|
|
ExpectedFD: progFD2,
|
|
Flags: unix.XDP_FLAGS_SKB_MODE,
|
|
},
|
|
},
|
|
{
|
|
name: "with FD, expected FD < 0",
|
|
xdp: &LinkXDP{
|
|
FD: progFD1,
|
|
ExpectedFD: -1,
|
|
Flags: unix.XDP_FLAGS_SKB_MODE,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
attachXDP(t, conn, lo, tt.xdp)
|
|
|
|
attached, progID := getXDP(t, conn, lo)
|
|
if attached != unix.XDP_FLAGS_SKB_MODE {
|
|
t.Fatalf("XDP attached state does not match. Got: %d, wanted: %d", attached, unix.XDP_FLAGS_SKB_MODE)
|
|
}
|
|
if attached == unix.XDP_FLAGS_SKB_MODE && progID == 0 {
|
|
t.Fatalf("XDP program should be attached but program ID is 0")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLinkXDPClear(t *testing.T) {
|
|
if err := rlimit.RemoveMemlock(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
conn, err := Dial(&netlink.Config{NetNS: testutils.NetNS(t)})
|
|
if err != nil {
|
|
t.Fatalf("failed to establish netlink socket: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
progFD1, _ := xdpPrograms(t)
|
|
|
|
attachXDP(t, conn, lo, &LinkXDP{
|
|
FD: progFD1,
|
|
Flags: unix.XDP_FLAGS_SKB_MODE,
|
|
})
|
|
|
|
// clear the BPF program from the link
|
|
attachXDP(t, conn, lo, &LinkXDP{
|
|
FD: -1,
|
|
Flags: unix.XDP_FLAGS_SKB_MODE,
|
|
})
|
|
|
|
attached, progID := getXDP(t, conn, lo)
|
|
if progID != 0 {
|
|
t.Fatalf("there is still a program loaded, while we cleared the link")
|
|
}
|
|
if attached != 0 {
|
|
t.Fatalf(
|
|
"XDP attached state does not match. Got: %d, wanted: %d\nThere should be no program loaded",
|
|
attached, 0,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestLinkXDPReplace(t *testing.T) {
|
|
// As of kernel version 5.7, the use of EXPECTED_FD and XDP_FLAGS_REPLACE
|
|
// is supported. We check here if the test host kernel fills this
|
|
// requirement. If the requirement is not met, we skip this test and
|
|
// output a notice. Running the code on a kernel version lower then 5.7
|
|
// will throw an "invalid argument" error.
|
|
// source kernel 5.6:
|
|
// https://elixir.bootlin.com/linux/v5.6/source/net/core/dev.c#L8662
|
|
// source kernel 5.7:
|
|
// https://elixir.bootlin.com/linux/v5.7/source/net/core/dev.c#L8674
|
|
testutils.SkipOnOldKernel(t, "5.7", "XDP_FLAGS_REPLACE")
|
|
|
|
if err := rlimit.RemoveMemlock(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
conn, err := Dial(&netlink.Config{NetNS: testutils.NetNS(t)})
|
|
if err != nil {
|
|
t.Fatalf("failed to establish netlink socket: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
progFD1, progFD2 := xdpPrograms(t)
|
|
|
|
attachXDP(t, conn, lo, &LinkXDP{
|
|
FD: progFD1,
|
|
Flags: unix.XDP_FLAGS_SKB_MODE,
|
|
})
|
|
|
|
_, progID1 := getXDP(t, conn, lo)
|
|
|
|
if err := conn.Link.Set(&LinkMessage{
|
|
Family: unix.AF_UNSPEC,
|
|
Index: lo,
|
|
Attributes: &LinkAttributes{
|
|
XDP: &LinkXDP{
|
|
FD: progFD2,
|
|
ExpectedFD: progFD2,
|
|
Flags: unix.XDP_FLAGS_SKB_MODE | unix.XDP_FLAGS_REPLACE,
|
|
},
|
|
},
|
|
}); err == nil {
|
|
t.Fatalf("replaced XDP program while expected FD did not match: %v", err)
|
|
}
|
|
|
|
_, progID2 := getXDP(t, conn, lo)
|
|
if progID2 != progID1 {
|
|
t.Fatal("XDP prog ID does not match previous program ID, which it should")
|
|
}
|
|
|
|
attachXDP(t, conn, lo, &LinkXDP{
|
|
FD: progFD2,
|
|
ExpectedFD: progFD1,
|
|
Flags: unix.XDP_FLAGS_SKB_MODE | unix.XDP_FLAGS_REPLACE,
|
|
})
|
|
|
|
_, progID2 = getXDP(t, conn, lo)
|
|
if progID2 == progID1 {
|
|
t.Fatal("XDP prog ID does match previous program ID, which it shouldn't")
|
|
}
|
|
}
|
|
|
|
func TestLinkListByKind(t *testing.T) {
|
|
if err := rlimit.RemoveMemlock(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
conn, err := Dial(&netlink.Config{NetNS: testutils.NetNS(t)})
|
|
if err != nil {
|
|
t.Fatalf("failed to establish netlink socket: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
links, err := conn.Link.ListByKind("SomeImpossibleLinkKind")
|
|
if err != nil {
|
|
t.Fatalf("LinkListByKind() finished with error: %v", err)
|
|
}
|
|
|
|
if len(links) > 0 {
|
|
t.Fatalf("LinkListByKind() found %d links with impossible kind", len(links))
|
|
}
|
|
}
|
|
|
|
func TestLinkSetMaster(t *testing.T) {
|
|
ns := testutils.NetNS(t)
|
|
conn, err := Dial(&netlink.Config{NetNS: ns})
|
|
if err != nil {
|
|
t.Fatalf("failed to dial: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
const (
|
|
bridgeIndex = 2001
|
|
vethIndex = 2002
|
|
)
|
|
|
|
// Create a bridge
|
|
err = conn.Link.New(&LinkMessage{
|
|
Index: bridgeIndex,
|
|
Attributes: &LinkAttributes{
|
|
Name: "testbr0",
|
|
Info: &LinkInfo{
|
|
Kind: "bridge",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to create bridge: %v", err)
|
|
}
|
|
defer conn.Link.Delete(bridgeIndex)
|
|
|
|
// Create a dummy interface
|
|
err = conn.Link.New(&LinkMessage{
|
|
Index: vethIndex,
|
|
Attributes: &LinkAttributes{
|
|
Name: "testdum0",
|
|
Info: &LinkInfo{
|
|
Kind: "dummy",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to create dummy interface: %v", err)
|
|
}
|
|
defer conn.Link.Delete(vethIndex)
|
|
|
|
// Enslave the dummy to the bridge
|
|
err = conn.Link.SetMaster(vethIndex, bridgeIndex, nil)
|
|
if err != nil {
|
|
t.Fatalf("failed to set master: %v", err)
|
|
}
|
|
|
|
// Verify it's enslaved
|
|
got, err := conn.Link.Get(vethIndex)
|
|
if err != nil {
|
|
t.Fatalf("failed to get link: %v", err)
|
|
}
|
|
|
|
if got.Attributes.Master == nil {
|
|
t.Fatal("expected Master to be set, got nil")
|
|
}
|
|
if *got.Attributes.Master != bridgeIndex {
|
|
t.Fatalf("unexpected master index:\n got: %d\nwant: %d", *got.Attributes.Master, bridgeIndex)
|
|
}
|
|
}
|
|
|
|
func TestLinkRemoveMaster(t *testing.T) {
|
|
ns := testutils.NetNS(t)
|
|
conn, err := Dial(&netlink.Config{NetNS: ns})
|
|
if err != nil {
|
|
t.Fatalf("failed to dial: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
const (
|
|
bridgeIndex = 2101
|
|
dummyIndex = 2102
|
|
)
|
|
|
|
// Create a bridge
|
|
err = conn.Link.New(&LinkMessage{
|
|
Index: bridgeIndex,
|
|
Attributes: &LinkAttributes{
|
|
Name: "testbr1",
|
|
Info: &LinkInfo{
|
|
Kind: "bridge",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to create bridge: %v", err)
|
|
}
|
|
defer conn.Link.Delete(bridgeIndex)
|
|
|
|
// Create a dummy interface
|
|
err = conn.Link.New(&LinkMessage{
|
|
Index: dummyIndex,
|
|
Attributes: &LinkAttributes{
|
|
Name: "testdum1",
|
|
Info: &LinkInfo{
|
|
Kind: "dummy",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to create dummy interface: %v", err)
|
|
}
|
|
defer conn.Link.Delete(dummyIndex)
|
|
|
|
// Enslave it to the bridge
|
|
err = conn.Link.SetMaster(dummyIndex, bridgeIndex, nil)
|
|
if err != nil {
|
|
t.Fatalf("failed to set master: %v", err)
|
|
}
|
|
|
|
// Verify it's enslaved
|
|
got, err := conn.Link.Get(dummyIndex)
|
|
if err != nil {
|
|
t.Fatalf("failed to get link: %v", err)
|
|
}
|
|
if got.Attributes.Master == nil || *got.Attributes.Master != bridgeIndex {
|
|
t.Fatal("interface was not enslaved")
|
|
}
|
|
|
|
// Remove from master
|
|
err = conn.Link.RemoveMaster(dummyIndex)
|
|
if err != nil {
|
|
t.Fatalf("failed to remove master: %v", err)
|
|
}
|
|
|
|
// Verify it's un-enslaved
|
|
got, err = conn.Link.Get(dummyIndex)
|
|
if err != nil {
|
|
t.Fatalf("failed to get link: %v", err)
|
|
}
|
|
|
|
// Master should be nil or 0
|
|
if got.Attributes.Master != nil && *got.Attributes.Master != 0 {
|
|
t.Fatalf("expected Master to be 0 or nil, got %d", *got.Attributes.Master)
|
|
}
|
|
}
|