virtio: Add RX queue full statisitics
[vpp.git] / test / test_lb.py
1 import socket
2
3 import scapy.compat
4 from scapy.layers.inet import IP, UDP
5 from scapy.layers.inet6 import IPv6
6 from scapy.layers.l2 import Ether, GRE
7 from scapy.packet import Raw
8 from scapy.data import IP_PROTOS
9
10 from framework import VppTestCase
11 from util import ppp
12 from vpp_ip_route import VppIpRoute, VppRoutePath
13 from vpp_ip import INVALID_INDEX
14
15 """ TestLB is a subclass of  VPPTestCase classes.
16
17  TestLB class defines Load Balancer test cases for:
18   - IP4 to GRE4 encap on per-port vip case
19   - IP4 to GRE6 encap on per-port vip case
20   - IP6 to GRE4 encap on per-port vip case
21   - IP6 to GRE6 encap on per-port vip case
22   - IP4 to L3DSR encap on vip case
23   - IP4 to L3DSR encap on per-port vip case
24   - IP4 to L3DSR encap on per-port vip with src_ip_sticky case
25   - IP4 to NAT4 encap on per-port vip case
26   - IP6 to NAT6 encap on per-port vip case
27
28  As stated in comments below, GRE has issues with IPv6.
29  All test cases involving IPv6 are executed, but
30  received packets are not parsed and checked.
31
32 """
33
34
35 class TestLB(VppTestCase):
36     """Load Balancer Test Case"""
37
38     @classmethod
39     def setUpClass(cls):
40         super(TestLB, cls).setUpClass()
41
42         cls.ass = range(5)
43         cls.packets = range(100)
44
45         try:
46             cls.create_pg_interfaces(range(2))
47             cls.interfaces = list(cls.pg_interfaces)
48
49             for i in cls.interfaces:
50                 i.admin_up()
51                 i.config_ip4()
52                 i.config_ip6()
53                 i.disable_ipv6_ra()
54                 i.resolve_arp()
55                 i.resolve_ndp()
56
57             dst4 = VppIpRoute(
58                 cls,
59                 "10.0.0.0",
60                 24,
61                 [VppRoutePath(cls.pg1.remote_ip4, INVALID_INDEX)],
62                 register=False,
63             )
64             dst4.add_vpp_config()
65             dst6 = VppIpRoute(
66                 cls,
67                 "2002::",
68                 16,
69                 [VppRoutePath(cls.pg1.remote_ip6, INVALID_INDEX)],
70                 register=False,
71             )
72             dst6.add_vpp_config()
73             cls.vapi.lb_conf(ip4_src_address="39.40.41.42", ip6_src_address="2004::1")
74         except Exception:
75             super(TestLB, cls).tearDownClass()
76             raise
77
78     @classmethod
79     def tearDownClass(cls):
80         super(TestLB, cls).tearDownClass()
81
82     def tearDown(self):
83         super(TestLB, self).tearDown()
84
85     def show_commands_at_teardown(self):
86         self.logger.info(self.vapi.cli("show lb vip verbose"))
87
88     def getIPv4Flow(self, id):
89         return IP(
90             dst="90.0.%u.%u" % (id / 255, id % 255),
91             src="40.0.%u.%u" % (id / 255, id % 255),
92         ) / UDP(sport=10000 + id, dport=20000)
93
94     def getIPv6Flow(self, id):
95         return IPv6(dst="2001::%u" % (id), src="fd00:f00d:ffff::%u" % (id)) / UDP(
96             sport=10000 + id, dport=20000
97         )
98
99     def generatePackets(self, src_if, isv4):
100         self.reset_packet_infos()
101         pkts = []
102         for pktid in self.packets:
103             info = self.create_packet_info(src_if, self.pg1)
104             payload = self.info_to_payload(info)
105             ip = self.getIPv4Flow(pktid) if isv4 else self.getIPv6Flow(pktid)
106             packet = (
107                 Ether(dst=src_if.local_mac, src=src_if.remote_mac) / ip / Raw(payload)
108             )
109             self.extend_packet(packet, 128)
110             info.data = packet.copy()
111             pkts.append(packet)
112         return pkts
113
114     def checkInner(self, gre, isv4):
115         IPver = IP if isv4 else IPv6
116         self.assertEqual(gre.proto, 0x0800 if isv4 else 0x86DD)
117         self.assertEqual(gre.flags, 0)
118         self.assertEqual(gre.version, 0)
119         inner = IPver(scapy.compat.raw(gre.payload))
120         payload_info = self.payload_to_info(inner[Raw])
121         self.info = self.packet_infos[payload_info.index]
122         self.assertEqual(payload_info.src, self.pg0.sw_if_index)
123         self.assertEqual(
124             scapy.compat.raw(inner), scapy.compat.raw(self.info.data[IPver])
125         )
126
127     def checkCapture(self, encap, isv4, src_ip_sticky=False):
128         self.pg0.assert_nothing_captured()
129         out = self.pg1.get_capture(len(self.packets))
130
131         load = [0] * len(self.ass)
132         sticky_as = {}
133         self.info = None
134         for p in out:
135             try:
136                 asid = 0
137                 gre = None
138                 if encap == "gre4":
139                     ip = p[IP]
140                     asid = int(ip.dst.split(".")[3])
141                     self.assertEqual(ip.version, 4)
142                     self.assertEqual(ip.flags, 0)
143                     self.assertEqual(ip.src, "39.40.41.42")
144                     self.assertEqual(ip.dst, "10.0.0.%u" % asid)
145                     self.assertEqual(ip.proto, 47)
146                     self.assertEqual(len(ip.options), 0)
147                     gre = p[GRE]
148                     self.checkInner(gre, isv4)
149                 elif encap == "gre6":
150                     ip = p[IPv6]
151                     asid = ip.dst.split(":")
152                     asid = asid[len(asid) - 1]
153                     asid = 0 if asid == "" else int(asid)
154                     self.assertEqual(ip.version, 6)
155                     self.assertEqual(ip.tc, 0)
156                     self.assertEqual(ip.fl, 0)
157                     self.assertEqual(ip.src, "2004::1")
158                     self.assertEqual(
159                         socket.inet_pton(socket.AF_INET6, ip.dst),
160                         socket.inet_pton(socket.AF_INET6, "2002::%u" % asid),
161                     )
162                     self.assertEqual(ip.nh, 47)
163                     # self.assertEqual(len(ip.options), 0)
164                     gre = GRE(scapy.compat.raw(p[IPv6].payload))
165                     self.checkInner(gre, isv4)
166                 elif encap == "l3dsr":
167                     ip = p[IP]
168                     asid = int(ip.dst.split(".")[3])
169                     self.assertEqual(ip.version, 4)
170                     self.assertEqual(ip.flags, 0)
171                     self.assertEqual(ip.dst, "10.0.0.%u" % asid)
172                     self.assertEqual(ip.tos, 0x1C)
173                     self.assertEqual(len(ip.options), 0)
174                     self.assert_ip_checksum_valid(p)
175                     if ip.proto == IP_PROTOS.tcp:
176                         self.assert_tcp_checksum_valid(p)
177                     elif ip.proto == IP_PROTOS.udp:
178                         self.assert_udp_checksum_valid(p)
179                 elif encap == "nat4":
180                     ip = p[IP]
181                     asid = int(ip.dst.split(".")[3])
182                     self.assertEqual(ip.version, 4)
183                     self.assertEqual(ip.flags, 0)
184                     self.assertEqual(ip.dst, "10.0.0.%u" % asid)
185                     self.assertEqual(ip.proto, 17)
186                     self.assertEqual(len(ip.options), 0)
187                     udp = p[UDP]
188                     self.assertEqual(udp.dport, 3307)
189                 elif encap == "nat6":
190                     ip = p[IPv6]
191                     asid = ip.dst.split(":")
192                     asid = asid[len(asid) - 1]
193                     asid = 0 if asid == "" else int(asid)
194                     self.assertEqual(ip.version, 6)
195                     self.assertEqual(ip.tc, 0)
196                     self.assertEqual(ip.fl, 0)
197                     self.assertEqual(
198                         socket.inet_pton(socket.AF_INET6, ip.dst),
199                         socket.inet_pton(socket.AF_INET6, "2002::%u" % asid),
200                     )
201                     self.assertEqual(ip.nh, 17)
202                     self.assertGreaterEqual(ip.hlim, 63)
203                     udp = UDP(scapy.compat.raw(p[IPv6].payload))
204                     self.assertEqual(udp.dport, 3307)
205                 load[asid] += 1
206
207                 # In case of source ip sticky, check that packets with same
208                 # src_ip are routed to same as.
209                 if src_ip_sticky and sticky_as.get(ip.src, asid) != asid:
210                     raise Exception("Packets with same src_ip are routed to another as")
211                 sticky_as[ip.src] = asid
212
213             except:
214                 self.logger.error(ppp("Unexpected or invalid packet:", p))
215                 raise
216
217         # This is just to roughly check that the balancing algorithm
218         # is not completely biased.
219         for asid in self.ass:
220             if load[asid] < int(len(self.packets) / (len(self.ass) * 2)):
221                 self.logger.error(
222                     "ASS is not balanced: load[%d] = %d" % (asid, load[asid])
223                 )
224                 raise Exception("Load Balancer algorithm is biased")
225
226     def test_lb_ip4_gre4(self):
227         """Load Balancer IP4 GRE4 on vip case"""
228         try:
229             self.vapi.cli("lb vip 90.0.0.0/8 encap gre4")
230             for asid in self.ass:
231                 self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u" % (asid))
232
233             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
234             self.pg_enable_capture(self.pg_interfaces)
235             self.pg_start()
236             self.checkCapture(encap="gre4", isv4=True)
237
238         finally:
239             for asid in self.ass:
240                 self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u del" % (asid))
241             self.vapi.cli("lb vip 90.0.0.0/8 encap gre4 del")
242             self.vapi.cli("test lb flowtable flush")
243
244     def test_lb_ip6_gre4(self):
245         """Load Balancer IP6 GRE4 on vip case"""
246
247         try:
248             self.vapi.cli("lb vip 2001::/16 encap gre4")
249             for asid in self.ass:
250                 self.vapi.cli("lb as 2001::/16 10.0.0.%u" % (asid))
251
252             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
253             self.pg_enable_capture(self.pg_interfaces)
254             self.pg_start()
255
256             self.checkCapture(encap="gre4", isv4=False)
257         finally:
258             for asid in self.ass:
259                 self.vapi.cli("lb as 2001::/16 10.0.0.%u del" % (asid))
260             self.vapi.cli("lb vip 2001::/16 encap gre4 del")
261             self.vapi.cli("test lb flowtable flush")
262
263     def test_lb_ip4_gre6(self):
264         """Load Balancer IP4 GRE6 on vip case"""
265         try:
266             self.vapi.cli("lb vip 90.0.0.0/8 encap gre6")
267             for asid in self.ass:
268                 self.vapi.cli("lb as 90.0.0.0/8 2002::%u" % (asid))
269
270             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
271             self.pg_enable_capture(self.pg_interfaces)
272             self.pg_start()
273
274             self.checkCapture(encap="gre6", isv4=True)
275         finally:
276             for asid in self.ass:
277                 self.vapi.cli("lb as 90.0.0.0/8 2002::%u del" % (asid))
278             self.vapi.cli("lb vip 90.0.0.0/8 encap gre6 del")
279             self.vapi.cli("test lb flowtable flush")
280
281     def test_lb_ip6_gre6(self):
282         """Load Balancer IP6 GRE6 on vip case"""
283         try:
284             self.vapi.cli("lb vip 2001::/16 encap gre6")
285             for asid in self.ass:
286                 self.vapi.cli("lb as 2001::/16 2002::%u" % (asid))
287
288             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
289             self.pg_enable_capture(self.pg_interfaces)
290             self.pg_start()
291
292             self.checkCapture(encap="gre6", isv4=False)
293         finally:
294             for asid in self.ass:
295                 self.vapi.cli("lb as 2001::/16 2002::%u del" % (asid))
296             self.vapi.cli("lb vip 2001::/16 encap gre6 del")
297             self.vapi.cli("test lb flowtable flush")
298
299     def test_lb_ip4_gre4_port(self):
300         """Load Balancer IP4 GRE4 on per-port-vip case"""
301         try:
302             self.vapi.cli("lb vip 90.0.0.0/8 protocol udp port 20000 encap gre4")
303             for asid in self.ass:
304                 self.vapi.cli(
305                     "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u" % (asid)
306                 )
307
308             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
309             self.pg_enable_capture(self.pg_interfaces)
310             self.pg_start()
311             self.checkCapture(encap="gre4", isv4=True)
312
313         finally:
314             for asid in self.ass:
315                 self.vapi.cli(
316                     "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u del" % (asid)
317                 )
318             self.vapi.cli("lb vip 90.0.0.0/8 protocol udp port 20000 encap gre4 del")
319             self.vapi.cli("test lb flowtable flush")
320
321     def test_lb_ip6_gre4_port(self):
322         """Load Balancer IP6 GRE4 on per-port-vip case"""
323
324         try:
325             self.vapi.cli("lb vip 2001::/16 protocol udp port 20000 encap gre4")
326             for asid in self.ass:
327                 self.vapi.cli(
328                     "lb as 2001::/16 protocol udp port 20000 10.0.0.%u" % (asid)
329                 )
330
331             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
332             self.pg_enable_capture(self.pg_interfaces)
333             self.pg_start()
334
335             self.checkCapture(encap="gre4", isv4=False)
336         finally:
337             for asid in self.ass:
338                 self.vapi.cli(
339                     "lb as 2001::/16 protocol udp port 20000 10.0.0.%u del" % (asid)
340                 )
341             self.vapi.cli("lb vip 2001::/16 protocol udp port 20000 encap gre4 del")
342             self.vapi.cli("test lb flowtable flush")
343
344     def test_lb_ip4_gre6_port(self):
345         """Load Balancer IP4 GRE6 on per-port-vip case"""
346         try:
347             self.vapi.cli("lb vip 90.0.0.0/8 protocol udp port 20000 encap gre6")
348             for asid in self.ass:
349                 self.vapi.cli(
350                     "lb as 90.0.0.0/8 protocol udp port 20000 2002::%u" % (asid)
351                 )
352
353             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
354             self.pg_enable_capture(self.pg_interfaces)
355             self.pg_start()
356
357             self.checkCapture(encap="gre6", isv4=True)
358         finally:
359             for asid in self.ass:
360                 self.vapi.cli(
361                     "lb as 90.0.0.0/8 protocol udp port 20000 2002::%u del" % (asid)
362                 )
363             self.vapi.cli("lb vip 90.0.0.0/8 protocol udp port 20000 encap gre6 del")
364             self.vapi.cli("test lb flowtable flush")
365
366     def test_lb_ip6_gre6_port(self):
367         """Load Balancer IP6 GRE6 on per-port-vip case"""
368         try:
369             self.vapi.cli("lb vip 2001::/16 protocol udp port 20000 encap gre6")
370             for asid in self.ass:
371                 self.vapi.cli(
372                     "lb as 2001::/16 protocol udp port 20000 2002::%u" % (asid)
373                 )
374
375             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
376             self.pg_enable_capture(self.pg_interfaces)
377             self.pg_start()
378
379             self.checkCapture(encap="gre6", isv4=False)
380         finally:
381             for asid in self.ass:
382                 self.vapi.cli(
383                     "lb as 2001::/16 protocol udp port 20000 2002::%u del" % (asid)
384                 )
385             self.vapi.cli("lb vip 2001::/16 protocol udp port 20000 encap gre6 del")
386             self.vapi.cli("test lb flowtable flush")
387
388     def test_lb_ip4_l3dsr(self):
389         """Load Balancer IP4 L3DSR on vip case"""
390         try:
391             self.vapi.cli("lb vip 90.0.0.0/8 encap l3dsr dscp 7")
392             for asid in self.ass:
393                 self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u" % (asid))
394
395             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
396             self.pg_enable_capture(self.pg_interfaces)
397             self.pg_start()
398             self.checkCapture(encap="l3dsr", isv4=True)
399
400         finally:
401             for asid in self.ass:
402                 self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u del" % (asid))
403             self.vapi.cli("lb vip 90.0.0.0/8 encap l3dsr dscp 7 del")
404             self.vapi.cli("test lb flowtable flush")
405
406     def test_lb_ip4_l3dsr_src_ip_sticky(self):
407         """Load Balancer IP4 L3DSR on vip with src_ip_sticky case"""
408         try:
409             self.vapi.cli("lb vip 90.0.0.0/8 encap l3dsr dscp 7 src_ip_sticky")
410             for asid in self.ass:
411                 self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u" % (asid))
412
413             # Generate duplicated packets
414             pkts = self.generatePackets(self.pg0, isv4=True)
415             pkts = pkts[: len(pkts) // 2]
416             pkts = pkts + pkts
417
418             self.pg0.add_stream(pkts)
419             self.pg_enable_capture(self.pg_interfaces)
420             self.pg_start()
421             self.checkCapture(encap="l3dsr", isv4=True, src_ip_sticky=True)
422
423         finally:
424             for asid in self.ass:
425                 self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u del" % (asid))
426             self.vapi.cli("lb vip 90.0.0.0/8 encap l3dsr dscp 7 src_ip_sticky del")
427             self.vapi.cli("test lb flowtable flush")
428
429     def test_lb_ip4_l3dsr_port(self):
430         """Load Balancer IP4 L3DSR on per-port-vip case"""
431         try:
432             self.vapi.cli(
433                 "lb vip 90.0.0.0/8 protocol udp port 20000 encap l3dsr dscp 7"
434             )
435             for asid in self.ass:
436                 self.vapi.cli(
437                     "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u" % (asid)
438                 )
439
440             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
441             self.pg_enable_capture(self.pg_interfaces)
442             self.pg_start()
443             self.checkCapture(encap="l3dsr", isv4=True)
444
445         finally:
446             for asid in self.ass:
447                 self.vapi.cli(
448                     "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u del" % (asid)
449                 )
450             self.vapi.cli(
451                 "lb vip 90.0.0.0/8 protocol udp port 20000 encap l3dsr dscp 7 del"
452             )
453             self.vapi.cli("test lb flowtable flush")
454
455     def test_lb_ip4_l3dsr_port_src_ip_sticky(self):
456         """Load Balancer IP4 L3DSR on per-port-vip with src_ip_sticky case"""
457         try:
458             # This VIP at port 1000 does not receive packets, but is defined
459             # as a dummy to verify that the src_ip_sticky flag can be set
460             # independently for each port.
461             self.vapi.cli(
462                 "lb vip 90.0.0.0/8 protocol udp port 10000 encap l3dsr dscp 7"
463             )
464             self.vapi.cli(
465                 "lb vip 90.0.0.0/8 protocol udp port 20000 encap l3dsr dscp 7 src_ip_sticky"
466             )
467             for asid in self.ass:
468                 self.vapi.cli(
469                     "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u" % (asid)
470                 )
471
472             # Generate duplicated packets
473             pkts = self.generatePackets(self.pg0, isv4=True)
474             pkts = pkts[: len(pkts) // 2]
475             pkts = pkts + pkts
476
477             self.pg0.add_stream(pkts)
478             self.pg_enable_capture(self.pg_interfaces)
479             self.pg_start()
480             self.checkCapture(encap="l3dsr", isv4=True, src_ip_sticky=True)
481
482         finally:
483             for asid in self.ass:
484                 self.vapi.cli(
485                     "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u del" % (asid)
486                 )
487             self.vapi.cli(
488                 "lb vip 90.0.0.0/8 protocol udp port 20000 encap l3dsr dscp 7 src_ip_sticky del"
489             )
490             self.vapi.cli(
491                 "lb vip 90.0.0.0/8 protocol udp port 10000 encap l3dsr dscp 7 del"
492             )
493             self.vapi.cli("test lb flowtable flush")
494
495     def test_lb_ip4_nat4_port(self):
496         """Load Balancer IP4 NAT4 on per-port-vip case"""
497         try:
498             self.vapi.cli(
499                 "lb vip 90.0.0.0/8 protocol udp port 20000 encap nat4"
500                 " type clusterip target_port 3307"
501             )
502             for asid in self.ass:
503                 self.vapi.cli(
504                     "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u" % (asid)
505                 )
506
507             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
508             self.pg_enable_capture(self.pg_interfaces)
509             self.pg_start()
510             self.checkCapture(encap="nat4", isv4=True)
511
512         finally:
513             for asid in self.ass:
514                 self.vapi.cli(
515                     "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u del" % (asid)
516                 )
517             self.vapi.cli(
518                 "lb vip 90.0.0.0/8 protocol udp port 20000 encap nat4"
519                 " type clusterip target_port 3307 del"
520             )
521             self.vapi.cli("test lb flowtable flush")
522
523     def test_lb_ip6_nat6_port(self):
524         """Load Balancer IP6 NAT6 on per-port-vip case"""
525         try:
526             self.vapi.cli(
527                 "lb vip 2001::/16 protocol udp port 20000 encap nat6"
528                 " type clusterip target_port 3307"
529             )
530             for asid in self.ass:
531                 self.vapi.cli(
532                     "lb as 2001::/16 protocol udp port 20000 2002::%u" % (asid)
533                 )
534
535             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
536             self.pg_enable_capture(self.pg_interfaces)
537             self.pg_start()
538             self.checkCapture(encap="nat6", isv4=False)
539
540         finally:
541             for asid in self.ass:
542                 self.vapi.cli(
543                     "lb as 2001::/16 protocol udp port 20000 2002::%u del" % (asid)
544                 )
545             self.vapi.cli(
546                 "lb vip 2001::/16 protocol udp port 20000 encap nat6"
547                 " type clusterip target_port 3307 del"
548             )
549             self.vapi.cli("test lb flowtable flush")