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
10 from framework import VppTestCase
12 from vpp_ip_route import VppIpRoute, VppRoutePath
13 from vpp_ip import INVALID_INDEX
15 """ TestLB is a subclass of VPPTestCase classes.
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
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.
35 class TestLB(VppTestCase):
36 """Load Balancer Test Case"""
40 super(TestLB, cls).setUpClass()
43 cls.packets = range(100)
46 cls.create_pg_interfaces(range(2))
47 cls.interfaces = list(cls.pg_interfaces)
49 for i in cls.interfaces:
61 [VppRoutePath(cls.pg1.remote_ip4, INVALID_INDEX)],
69 [VppRoutePath(cls.pg1.remote_ip6, INVALID_INDEX)],
73 cls.vapi.lb_conf(ip4_src_address="39.40.41.42", ip6_src_address="2004::1")
75 super(TestLB, cls).tearDownClass()
79 def tearDownClass(cls):
80 super(TestLB, cls).tearDownClass()
83 super(TestLB, self).tearDown()
85 def show_commands_at_teardown(self):
86 self.logger.info(self.vapi.cli("show lb vip verbose"))
88 def getIPv4Flow(self, id):
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)
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
99 def generatePackets(self, src_if, isv4):
100 self.reset_packet_infos()
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)
107 Ether(dst=src_if.local_mac, src=src_if.remote_mac) / ip / Raw(payload)
109 self.extend_packet(packet, 128)
110 info.data = packet.copy()
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)
124 scapy.compat.raw(inner), scapy.compat.raw(self.info.data[IPver])
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))
131 load = [0] * len(self.ass)
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)
148 self.checkInner(gre, isv4)
149 elif encap == "gre6":
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")
159 socket.inet_pton(socket.AF_INET6, ip.dst),
160 socket.inet_pton(socket.AF_INET6, "2002::%u" % asid),
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":
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":
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)
188 self.assertEqual(udp.dport, 3307)
189 elif encap == "nat6":
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)
198 socket.inet_pton(socket.AF_INET6, ip.dst),
199 socket.inet_pton(socket.AF_INET6, "2002::%u" % asid),
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)
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
214 self.logger.error(ppp("Unexpected or invalid packet:", p))
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)):
222 "ASS is not balanced: load[%d] = %d" % (asid, load[asid])
224 raise Exception("Load Balancer algorithm is biased")
226 def test_lb_ip4_gre4(self):
227 """Load Balancer IP4 GRE4 on vip case"""
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))
233 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
234 self.pg_enable_capture(self.pg_interfaces)
236 self.checkCapture(encap="gre4", isv4=True)
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")
244 def test_lb_ip6_gre4(self):
245 """Load Balancer IP6 GRE4 on vip case"""
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))
252 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
253 self.pg_enable_capture(self.pg_interfaces)
256 self.checkCapture(encap="gre4", isv4=False)
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")
263 def test_lb_ip4_gre6(self):
264 """Load Balancer IP4 GRE6 on vip case"""
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))
270 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
271 self.pg_enable_capture(self.pg_interfaces)
274 self.checkCapture(encap="gre6", isv4=True)
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")
281 def test_lb_ip6_gre6(self):
282 """Load Balancer IP6 GRE6 on vip case"""
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))
288 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
289 self.pg_enable_capture(self.pg_interfaces)
292 self.checkCapture(encap="gre6", isv4=False)
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")
299 def test_lb_ip4_gre4_port(self):
300 """Load Balancer IP4 GRE4 on per-port-vip case"""
302 self.vapi.cli("lb vip 90.0.0.0/8 protocol udp port 20000 encap gre4")
303 for asid in self.ass:
305 "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u" % (asid)
308 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
309 self.pg_enable_capture(self.pg_interfaces)
311 self.checkCapture(encap="gre4", isv4=True)
314 for asid in self.ass:
316 "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u del" % (asid)
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")
321 def test_lb_ip6_gre4_port(self):
322 """Load Balancer IP6 GRE4 on per-port-vip case"""
325 self.vapi.cli("lb vip 2001::/16 protocol udp port 20000 encap gre4")
326 for asid in self.ass:
328 "lb as 2001::/16 protocol udp port 20000 10.0.0.%u" % (asid)
331 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
332 self.pg_enable_capture(self.pg_interfaces)
335 self.checkCapture(encap="gre4", isv4=False)
337 for asid in self.ass:
339 "lb as 2001::/16 protocol udp port 20000 10.0.0.%u del" % (asid)
341 self.vapi.cli("lb vip 2001::/16 protocol udp port 20000 encap gre4 del")
342 self.vapi.cli("test lb flowtable flush")
344 def test_lb_ip4_gre6_port(self):
345 """Load Balancer IP4 GRE6 on per-port-vip case"""
347 self.vapi.cli("lb vip 90.0.0.0/8 protocol udp port 20000 encap gre6")
348 for asid in self.ass:
350 "lb as 90.0.0.0/8 protocol udp port 20000 2002::%u" % (asid)
353 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
354 self.pg_enable_capture(self.pg_interfaces)
357 self.checkCapture(encap="gre6", isv4=True)
359 for asid in self.ass:
361 "lb as 90.0.0.0/8 protocol udp port 20000 2002::%u del" % (asid)
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")
366 def test_lb_ip6_gre6_port(self):
367 """Load Balancer IP6 GRE6 on per-port-vip case"""
369 self.vapi.cli("lb vip 2001::/16 protocol udp port 20000 encap gre6")
370 for asid in self.ass:
372 "lb as 2001::/16 protocol udp port 20000 2002::%u" % (asid)
375 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
376 self.pg_enable_capture(self.pg_interfaces)
379 self.checkCapture(encap="gre6", isv4=False)
381 for asid in self.ass:
383 "lb as 2001::/16 protocol udp port 20000 2002::%u del" % (asid)
385 self.vapi.cli("lb vip 2001::/16 protocol udp port 20000 encap gre6 del")
386 self.vapi.cli("test lb flowtable flush")
388 def test_lb_ip4_l3dsr(self):
389 """Load Balancer IP4 L3DSR on vip case"""
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))
395 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
396 self.pg_enable_capture(self.pg_interfaces)
398 self.checkCapture(encap="l3dsr", isv4=True)
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")
406 def test_lb_ip4_l3dsr_port(self):
407 """Load Balancer IP4 L3DSR on per-port-vip case"""
410 "lb vip 90.0.0.0/8 protocol udp port 20000 encap l3dsr dscp 7"
412 for asid in self.ass:
414 "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u" % (asid)
417 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
418 self.pg_enable_capture(self.pg_interfaces)
420 self.checkCapture(encap="l3dsr", isv4=True)
423 for asid in self.ass:
425 "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u del" % (asid)
428 "lb vip 90.0.0.0/8 protocol udp port 20000 encap l3dsr dscp 7 del"
430 self.vapi.cli("test lb flowtable flush")
432 def test_lb_ip4_l3dsr_port_src_ip_sticky(self):
433 """Load Balancer IP4 L3DSR on per-port-vip with src_ip_sticky case"""
436 "lb vip 90.0.0.0/8 protocol udp port 20000 encap l3dsr dscp 7 src_ip_sticky"
438 for asid in self.ass:
440 "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u" % (asid)
443 # Generate duplicated packets
444 pkts = self.generatePackets(self.pg0, isv4=True)
445 pkts = pkts[: len(pkts) // 2]
448 self.pg0.add_stream(pkts)
449 self.pg_enable_capture(self.pg_interfaces)
451 self.checkCapture(encap="l3dsr", isv4=True, src_ip_sticky=True)
454 for asid in self.ass:
456 "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u del" % (asid)
459 "lb vip 90.0.0.0/8 protocol udp port 20000 encap l3dsr dscp 7 src_ip_sticky del"
461 self.vapi.cli("test lb flowtable flush")
463 def test_lb_ip4_nat4_port(self):
464 """Load Balancer IP4 NAT4 on per-port-vip case"""
467 "lb vip 90.0.0.0/8 protocol udp port 20000 encap nat4"
468 " type clusterip target_port 3307"
470 for asid in self.ass:
472 "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u" % (asid)
475 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
476 self.pg_enable_capture(self.pg_interfaces)
478 self.checkCapture(encap="nat4", isv4=True)
481 for asid in self.ass:
483 "lb as 90.0.0.0/8 protocol udp port 20000 10.0.0.%u del" % (asid)
486 "lb vip 90.0.0.0/8 protocol udp port 20000 encap nat4"
487 " type clusterip target_port 3307 del"
489 self.vapi.cli("test lb flowtable flush")
491 def test_lb_ip6_nat6_port(self):
492 """Load Balancer IP6 NAT6 on per-port-vip case"""
495 "lb vip 2001::/16 protocol udp port 20000 encap nat6"
496 " type clusterip target_port 3307"
498 for asid in self.ass:
500 "lb as 2001::/16 protocol udp port 20000 2002::%u" % (asid)
503 self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
504 self.pg_enable_capture(self.pg_interfaces)
506 self.checkCapture(encap="nat6", isv4=False)
509 for asid in self.ass:
511 "lb as 2001::/16 protocol udp port 20000 2002::%u del" % (asid)
514 "lb vip 2001::/16 protocol udp port 20000 encap nat6"
515 " type clusterip target_port 3307 del"
517 self.vapi.cli("test lb flowtable flush")