make test: improve robustness and performance
[vpp.git] / test / test_lb.py
1 import socket
2
3 from scapy.layers.inet import IP, UDP
4 from scapy.layers.inet6 import ICMPv6ND_RA, IPv6
5 from scapy.layers.l2 import Ether, GRE
6 from scapy.packet import Raw
7
8 from framework import VppTestCase
9 from util import ppp
10
11 """ TestLB is a subclass of  VPPTestCase classes.
12
13  TestLB class defines Load Balancer test cases for:
14   - IP4 to GRE4 encap
15   - IP4 to GRE6 encap
16   - IP6 to GRE4 encap
17   - IP6 to GRE6 encap
18
19  As stated in comments below, GRE has issues with IPv6.
20  All test cases involving IPv6 are executed, but
21  received packets are not parsed and checked.
22
23 """
24
25
26 class TestLB(VppTestCase):
27     """ Load Balancer Test Case """
28
29     @classmethod
30     def setUpClass(cls):
31         super(TestLB, cls).setUpClass()
32
33         cls.ass = range(5)
34         cls.packets = range(100)
35
36         try:
37             cls.create_pg_interfaces(range(2))
38             cls.interfaces = list(cls.pg_interfaces)
39
40             for i in cls.interfaces:
41                 i.admin_up()
42                 i.config_ip4()
43                 i.config_ip6()
44                 i.disable_ipv6_ra()
45                 i.resolve_arp()
46                 i.resolve_ndp()
47             dst4 = socket.inet_pton(socket.AF_INET, "10.0.0.0")
48             dst6 = socket.inet_pton(socket.AF_INET6, "2002::")
49             cls.vapi.ip_add_del_route(dst4, 24, cls.pg1.remote_ip4n)
50             cls.vapi.ip_add_del_route(dst6, 16, cls.pg1.remote_ip6n, is_ipv6=1)
51             cls.vapi.cli("lb conf ip4-src-address 39.40.41.42")
52             cls.vapi.cli("lb conf ip6-src-address 2004::1")
53         except Exception:
54             super(TestLB, cls).tearDownClass()
55             raise
56
57     def tearDown(self):
58         super(TestLB, self).tearDown()
59         if not self.vpp_dead:
60             self.logger.info(self.vapi.cli("show lb vip verbose"))
61
62     def getIPv4Flow(self, id):
63         return (IP(dst="90.0.%u.%u" % (id / 255, id % 255),
64                    src="40.0.%u.%u" % (id / 255, id % 255)) /
65                 UDP(sport=10000 + id, dport=20000 + id))
66
67     def getIPv6Flow(self, id):
68         return (IPv6(dst="2001::%u" % (id), src="fd00:f00d:ffff::%u" % (id)) /
69                 UDP(sport=10000 + id, dport=20000 + id))
70
71     def generatePackets(self, src_if, isv4):
72         self.packet_infos = {}
73         pkts = []
74         for pktid in self.packets:
75             info = self.create_packet_info(src_if.sw_if_index, pktid)
76             payload = self.info_to_payload(info)
77             ip = self.getIPv4Flow(pktid) if isv4 else self.getIPv6Flow(pktid)
78             packet = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
79                       ip /
80                       Raw(payload))
81             self.extend_packet(packet, 128)
82             info.data = packet.copy()
83             pkts.append(packet)
84         return pkts
85
86     def checkInner(self, gre, isv4):
87         IPver = IP if isv4 else IPv6
88         self.assertEqual(gre.proto, 0x0800 if isv4 else 0x86DD)
89         self.assertEqual(gre.flags, 0)
90         self.assertEqual(gre.version, 0)
91         inner = IPver(str(gre.payload))
92         payload_info = self.payload_to_info(str(inner[Raw]))
93         self.info = self.get_next_packet_info_for_interface2(
94             self.pg0.sw_if_index, payload_info.dst, self.info)
95         self.assertEqual(str(inner), str(self.info.data[IPver]))
96
97     def checkCapture(self, gre4, isv4):
98         # RA might appear in capture
99         try:
100             out = self.pg0.get_capture()
101             # filter out any IPv6 RAs from the capture
102             for p in out:
103                 if (p.haslayer(ICMPv6ND_RA)):
104                     out.remove(p)
105             self.assertEqual(len(out), 0)
106         except:
107             pass
108         out = self.pg1.get_capture()
109         self.assertEqual(len(out), len(self.packets))
110
111         load = [0] * len(self.ass)
112         self.info = None
113         for p in out:
114             try:
115                 asid = 0
116                 gre = None
117                 if gre4:
118                     ip = p[IP]
119                     asid = int(ip.dst.split(".")[3])
120                     self.assertEqual(ip.version, 4)
121                     self.assertEqual(ip.flags, 0)
122                     self.assertEqual(ip.src, "39.40.41.42")
123                     self.assertEqual(ip.dst, "10.0.0.%u" % asid)
124                     self.assertEqual(ip.proto, 47)
125                     self.assertEqual(len(ip.options), 0)
126                     self.assertGreaterEqual(ip.ttl, 64)
127                     gre = p[GRE]
128                 else:
129                     ip = p[IPv6]
130                     asid = ip.dst.split(":")
131                     asid = asid[len(asid) - 1]
132                     asid = 0 if asid == "" else int(asid)
133                     self.assertEqual(ip.version, 6)
134                     self.assertEqual(ip.tc, 0)
135                     self.assertEqual(ip.fl, 0)
136                     self.assertEqual(ip.src, "2004::1")
137                     self.assertEqual(
138                         socket.inet_pton(socket.AF_INET6, ip.dst),
139                         socket.inet_pton(socket.AF_INET6, "2002::%u" % asid)
140                     )
141                     self.assertEqual(ip.nh, 47)
142                     self.assertGreaterEqual(ip.hlim, 64)
143                     # self.assertEqual(len(ip.options), 0)
144                     gre = GRE(str(p[IPv6].payload))
145                 self.checkInner(gre, isv4)
146                 load[asid] += 1
147             except:
148                 self.logger.error(ppp("Unexpected or invalid packet:", p))
149                 raise
150
151         # This is just to roughly check that the balancing algorithm
152         # is not completly biased.
153         for asid in self.ass:
154             if load[asid] < len(self.packets) / (len(self.ass) * 2):
155                 self.log(
156                     "ASS is not balanced: load[%d] = %d" % (asid, load[asid]))
157                 raise Exception("Load Balancer algorithm is biased")
158
159     def test_lb_ip4_gre4(self):
160         """ Load Balancer IP4 GRE4 """
161         try:
162             self.vapi.cli("lb vip 90.0.0.0/8 encap gre4")
163             for asid in self.ass:
164                 self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u" % (asid))
165
166             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
167             self.pg_enable_capture(self.pg_interfaces)
168             self.pg_start()
169             self.checkCapture(gre4=True, isv4=True)
170
171         finally:
172             for asid in self.ass:
173                 self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u del" % (asid))
174             self.vapi.cli("lb vip 90.0.0.0/8 encap gre4 del")
175
176     def test_lb_ip6_gre4(self):
177         """ Load Balancer IP6 GRE4 """
178
179         try:
180             self.vapi.cli("lb vip 2001::/16 encap gre4")
181             for asid in self.ass:
182                 self.vapi.cli("lb as 2001::/16 10.0.0.%u" % (asid))
183
184             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
185             self.pg_enable_capture(self.pg_interfaces)
186             self.pg_start()
187
188             self.checkCapture(gre4=True, isv4=False)
189         finally:
190             for asid in self.ass:
191                 self.vapi.cli("lb as 2001::/16 10.0.0.%u del" % (asid))
192             self.vapi.cli("lb vip 2001::/16 encap gre4 del")
193
194     def test_lb_ip4_gre6(self):
195         """ Load Balancer IP4 GRE6 """
196         try:
197             self.vapi.cli("lb vip 90.0.0.0/8 encap gre6")
198             for asid in self.ass:
199                 self.vapi.cli("lb as 90.0.0.0/8 2002::%u" % (asid))
200
201             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
202             self.pg_enable_capture(self.pg_interfaces)
203             self.pg_start()
204
205             self.checkCapture(gre4=False, isv4=True)
206         finally:
207             for asid in self.ass:
208                 self.vapi.cli("lb as 90.0.0.0/8 2002::%u" % (asid))
209             self.vapi.cli("lb vip 90.0.0.0/8 encap gre6 del")
210
211     def test_lb_ip6_gre6(self):
212         """ Load Balancer IP6 GRE6 """
213         try:
214             self.vapi.cli("lb vip 2001::/16 encap gre6")
215             for asid in self.ass:
216                 self.vapi.cli("lb as 2001::/16 2002::%u" % (asid))
217
218             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
219             self.pg_enable_capture(self.pg_interfaces)
220             self.pg_start()
221
222             self.checkCapture(gre4=False, isv4=False)
223         finally:
224             for asid in self.ass:
225                 self.vapi.cli("lb as 2001::/16 2002::%u del" % (asid))
226             self.vapi.cli("lb vip 2001::/16 encap gre6 del")