blob: dbcc79e9033a166aef933554847facea946b8cac [file] [log] [blame]
Lorenz Brun8bc82862024-04-30 11:47:09 +00001// Package lacp contains an integration test for our custom LACP patches.
2// It tests relevant behavior that other parts of the Monogon network stack
3// rely on, like proper carrier state indications.
4package lacp
5
6import (
7 "hash/fnv"
8 "math"
9 "net"
10 "os"
11 "strings"
12 "testing"
13 "time"
14
15 "github.com/vishvananda/netlink"
16 "golang.org/x/sys/unix"
17)
18
19// createVethPair is a helper creating a pair of virtual network interfaces
20// acting as a cable (i.e. traffic going in one interface comes back out on
21// the other one). Both ends are returned.
22func createVethPair(t *testing.T, name string) (*netlink.Veth, *netlink.Veth) {
23 t.Helper()
24 vethLink := netlink.Veth{
25 LinkAttrs: netlink.LinkAttrs{
26 Name: name + "a",
27 NetNsID: -1,
28 TxQLen: -1,
29 },
30 PeerName: name + "b",
31 }
32 if err := netlink.LinkAdd(&vethLink); err != nil {
33 t.Fatalf("while creating veth pair: %v", err)
34 }
35 vethLinkB, err := netlink.LinkByName(name + "b")
36 if err != nil {
37 t.Fatalf("while creating veth pair: while getting veth peer: %v", err)
38 }
39
40 return &vethLink, vethLinkB.(*netlink.Veth)
41}
42
43// setupNetem is a helper for setting up Linux's network emulation queuing
44// discipline on a network interface, which can simulate various network
45// imperfections like extra latency, reordering or packet loss on packets
46// transmitted on the interface specified. As it is internally implemented
47// as a queuing discipline it only affects transmitted packets.
48func setupNetem(t *testing.T, link netlink.Link, conf netlink.NetemQdiscAttrs) *netlink.Netem {
49 t.Helper()
50 h := fnv.New32a()
51 h.Write([]byte(link.Attrs().Name))
52 qdisc := netlink.NewNetem(netlink.QdiscAttrs{
53 LinkIndex: link.Attrs().Index,
54 Handle: netlink.MakeHandle(uint16(h.Sum32()%math.MaxUint16), 0),
55 Parent: netlink.HANDLE_ROOT,
56 }, conf)
57 if err := netlink.QdiscAdd(qdisc); err != nil {
58 t.Fatalf("while setting up qdisc netem for %q: %v", link.Attrs().Name, err)
59 }
60 return qdisc
61}
62
63// changeNetem is a helper for reconfiguring an existing netem instance
64// on-the-fly with new parameters.
65func changeNetem(t *testing.T, qdisc *netlink.Netem, conf netlink.NetemQdiscAttrs) {
66 t.Helper()
67 changedQd := netlink.NewNetem(qdisc.QdiscAttrs, conf)
68 if err := netlink.QdiscChange(changedQd); err != nil {
69 t.Fatalf("while changing qdisc netem for link index %v: %v", qdisc.LinkIndex, err)
70 }
71}
72
73func createBond(t *testing.T, name string, links ...netlink.Link) *netlink.Bond {
74 t.Helper()
75 bondLink := netlink.NewLinkBond(netlink.LinkAttrs{
76 Name: name,
77 NetNsID: -1,
78 TxQLen: -1,
79 Flags: net.FlagUp,
80 })
81 bondLink.Mode = netlink.BOND_MODE_802_3AD
82 bondLink.LacpRate = netlink.BOND_LACP_RATE_FAST
83 bondLink.MinLinks = 1
84 bondLink.AdSelect = netlink.BOND_AD_SELECT_BANDWIDTH
85 if err := netlink.LinkAdd(bondLink); err != nil {
86 t.Fatalf("while creating bond: %v", err)
87 }
88 for _, l := range links {
89 if err := netlink.LinkSetBondSlave(l, bondLink); err != nil {
90 t.Fatalf("while enslaving link to bond %q: %v", name, err)
91 }
92 }
93 return bondLink
94}
95
96// assertRunning is a helper for asserting an interface's IFF_RUNNING state.
97func assertRunning(t *testing.T, l netlink.Link, expected bool) {
98 t.Helper()
99 linkCurrent, err := netlink.LinkByIndex(l.Attrs().Index)
100 if err != nil {
101 t.Fatalf("while checking if link %q is running: %v", l.Attrs().Name, err)
102 }
103 is := linkCurrent.Attrs().RawFlags&unix.IFF_RUNNING != 0
104 if expected != is {
105 t.Errorf("expected interface %q running state to be %v, is %v", l.Attrs().Name, expected, is)
106 }
107}
108
109func TestLACP(t *testing.T) {
110 if os.Getenv("IN_KTEST") != "true" {
111 t.Skip("Not in ktest")
112 }
113
114 if err := os.WriteFile("/sys/kernel/debug/dynamic_debug/control", []byte("module bonding +p"), 0); err != nil {
115 t.Fatal(err)
116 }
117 // Log dynamic debug to console
118 if err := os.WriteFile("/proc/sys/kernel/printk", []byte("8"), 0); err != nil {
119 t.Fatal(err)
120 }
121
122 link1a, link1b := createVethPair(t, "link1")
123 link2a, link2b := createVethPair(t, "link2")
124
125 // Drop all traffic
126 l1aq := setupNetem(t, link1a, netlink.NetemQdiscAttrs{Loss: 100.0})
127 l1bq := setupNetem(t, link1b, netlink.NetemQdiscAttrs{Loss: 100.0})
128 l2aq := setupNetem(t, link2a, netlink.NetemQdiscAttrs{Loss: 100.0})
129 l2bq := setupNetem(t, link2b, netlink.NetemQdiscAttrs{Loss: 100.0})
130
131 bondA := createBond(t, "bonda", link1a, link2a)
132 bondB := createBond(t, "bondb", link1b, link2b)
133
134 time.Sleep(5 * time.Second)
135
136 // Bonds should not come up with links dropping all traffic
137 assertRunning(t, bondA, false)
138 assertRunning(t, bondB, false)
139
140 changeNetem(t, l1aq, netlink.NetemQdiscAttrs{Loss: 0.0})
141 changeNetem(t, l1bq, netlink.NetemQdiscAttrs{Loss: 0.0})
142 t.Log("Enabled L1")
143
144 time.Sleep(5 * time.Second)
145
146 // Bonds should come up with one link working
147 assertRunning(t, bondA, true)
148 assertRunning(t, bondB, true)
149
150 changeNetem(t, l2aq, netlink.NetemQdiscAttrs{Loss: 0.0})
151 changeNetem(t, l2bq, netlink.NetemQdiscAttrs{Loss: 0.0})
152 t.Log("Enabled L2")
153
154 time.Sleep(3 * time.Second)
155
156 // Bonds be up with both links
157 assertRunning(t, bondA, true)
158 assertRunning(t, bondB, true)
159
160 bondAState, err := os.ReadFile("/proc/net/bonding/bonda")
161 if err != nil {
162 panic(err)
163 }
164 t.Log(string(bondAState))
165 if !strings.Contains(string(bondAState), "Number of ports: 2") {
166 t.Errorf("bonda aggregator should contain two ports")
167 }
168 if !strings.Contains(string(bondAState), "port state: 63") {
169 t.Errorf("bonda port state should be 63")
170 }
171 t.Log("------------")
172 bondBState, err := os.ReadFile("/proc/net/bonding/bondb")
173 if err != nil {
174 panic(err)
175 }
176 t.Log(string(bondBState))
177 if !strings.Contains(string(bondBState), "Number of ports: 2") {
178 t.Errorf("bondb aggregator should contain two ports")
179 }
180 if !strings.Contains(string(bondBState), "port state: 63") {
181 t.Errorf("bondb port state should be 63")
182 }
183 changeNetem(t, l1aq, netlink.NetemQdiscAttrs{Loss: 100.0})
184 changeNetem(t, l1bq, netlink.NetemQdiscAttrs{Loss: 100.0})
185 changeNetem(t, l2aq, netlink.NetemQdiscAttrs{Loss: 100.0})
186 changeNetem(t, l2bq, netlink.NetemQdiscAttrs{Loss: 100.0})
187 t.Log("Disabled both links")
188
189 time.Sleep(5 * time.Second)
190
191 // Bonds should be back down
192 assertRunning(t, bondA, false)
193 assertRunning(t, bondB, false)
194}