refactor test framework
[vpp.git] / test / test_lb.py
1 import socket
2 import unittest
3 from logging import *
4
5 from scapy.layers.inet import IP, UDP
6 from scapy.layers.inet6 import IPv6
7 from scapy.layers.l2 import Ether, GRE
8 from scapy.packet import Raw
9
10 from framework import VppTestCase
11
12 """ TestLB is a subclass of  VPPTestCase classes.
13
14  TestLB class defines Load Balancer test cases for:
15   - IP4 to GRE4 encap
16   - IP4 to GRE6 encap
17   - IP6 to GRE4 encap
18   - IP6 to GRE6 encap
19
20  As stated in comments below, GRE has issues with IPv6.
21  All test cases involving IPv6 are executed, but
22  received packets are not parsed and checked.
23
24 """
25
26
27 class TestLB(VppTestCase):
28     """ Load Balancer Test Case """
29
30     @classmethod
31     def setUpClass(cls):
32         super(TestLB, cls).setUpClass()
33
34         cls.ass = range(5)
35         cls.packets = range(100)
36
37         try:
38             cls.create_pg_interfaces(range(2))
39             cls.interfaces = list(cls.pg_interfaces)
40
41             for i in cls.interfaces:
42                 i.admin_up()
43                 i.config_ip4()
44                 i.config_ip6()
45                 i.disable_ipv6_ra()
46                 i.resolve_arp()
47                 i.resolve_ndp()
48             dst4 = socket.inet_pton(socket.AF_INET, "10.0.0.0")
49             dst6 = socket.inet_pton(socket.AF_INET6, "2002::")
50             cls.vapi.ip_add_del_route(dst4, 24, cls.pg1.remote_ip4n)
51             cls.vapi.ip_add_del_route(dst6, 16, cls.pg1.remote_ip6n, is_ipv6=1)
52             cls.vapi.cli("lb conf ip4-src-address 39.40.41.42")
53             cls.vapi.cli("lb conf ip6-src-address 2004::1")
54         except Exception:
55             super(TestLB, cls).tearDownClass()
56             raise
57
58     def tearDown(self):
59         super(TestLB, self).tearDown()
60         if not self.vpp_dead:
61             info(self.vapi.cli("show lb vip verbose"))
62
63     def getIPv4Flow(self, id):
64         return (IP(dst="90.0.%u.%u" % (id / 255, id % 255),
65                    src="40.0.%u.%u" % (id / 255, id % 255)) /
66                 UDP(sport=10000 + id, dport=20000 + id))
67
68     def getIPv6Flow(self, id):
69         return (IPv6(dst="2001::%u" % (id), src="fd00:f00d:ffff::%u" % (id)) /
70                 UDP(sport=10000 + id, dport=20000 + id))
71
72     def generatePackets(self, src_if, isv4):
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         packet_index = payload_info.index
94         self.info = self.get_next_packet_info_for_interface2(self.pg0.sw_if_index,
95                                                              payload_info.dst,
96                                                              self.info)
97         self.assertEqual(str(inner), str(self.info.data[IPver]))
98
99     def checkCapture(self, gre4, isv4):
100         out = self.pg0.get_capture()
101         # This check is edited because RA appears in output, maybe disable RA?
102         # self.assertEqual(len(out), 0)
103         self.assertLess(len(out), 20)
104         out = self.pg1.get_capture()
105         self.assertEqual(len(out), len(self.packets))
106
107         load = [0] * len(self.ass)
108         self.info = None
109         for p in out:
110             try:
111                 asid = 0
112                 gre = None
113                 if gre4:
114                     ip = p[IP]
115                     asid = int(ip.dst.split(".")[3])
116                     self.assertEqual(ip.version, 4)
117                     self.assertEqual(ip.flags, 0)
118                     self.assertEqual(ip.src, "39.40.41.42")
119                     self.assertEqual(ip.dst, "10.0.0.%u" % asid)
120                     self.assertEqual(ip.proto, 47)
121                     self.assertEqual(len(ip.options), 0)
122                     self.assertGreaterEqual(ip.ttl, 64)
123                     gre = p[GRE]
124                 else:
125                     ip = p[IPv6]
126                     asid = ip.dst.split(":")
127                     asid = asid[len(asid) - 1]
128                     asid = 0 if asid == "" else int(asid)
129                     self.assertEqual(ip.version, 6)
130                     self.assertEqual(ip.tc, 0)
131                     self.assertEqual(ip.fl, 0)
132                     self.assertEqual(ip.src, "2004::1")
133                     self.assertEqual(
134                         socket.inet_pton(socket.AF_INET6, ip.dst),
135                         socket.inet_pton(socket.AF_INET6, "2002::%u" % asid)
136                     )
137                     self.assertEqual(ip.nh, 47)
138                     self.assertGreaterEqual(ip.hlim, 64)
139                     # self.assertEqual(len(ip.options), 0)
140                     gre = GRE(str(p[IPv6].payload))
141                 self.checkInner(gre, isv4)
142                 load[asid] += 1
143             except:
144                 error("Unexpected or invalid packet:")
145                 p.show()
146                 raise
147
148         # This is just to roughly check that the balancing algorithm
149         # is not completly biased.
150         for asid in self.ass:
151             if load[asid] < len(self.packets) / (len(self.ass) * 2):
152                 self.log(
153                     "ASS is not balanced: load[%d] = %d" % (asid, load[asid]))
154                 raise Exception("Load Balancer algorithm is biased")
155
156     def test_lb_ip4_gre4(self):
157         """ Load Balancer IP4 GRE4 """
158         try:
159             self.vapi.cli("lb vip 90.0.0.0/8 encap gre4")
160             for asid in self.ass:
161                 self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u" % (asid))
162
163             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
164             self.pg_enable_capture(self.pg_interfaces)
165             self.pg_start()
166             self.checkCapture(gre4=True, isv4=True)
167
168         finally:
169             for asid in self.ass:
170                 self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u del" % (asid))
171             self.vapi.cli("lb vip 90.0.0.0/8 encap gre4 del")
172
173     def test_lb_ip6_gre4(self):
174         """ Load Balancer IP6 GRE4 """
175
176         try:
177             self.vapi.cli("lb vip 2001::/16 encap gre4")
178             for asid in self.ass:
179                 self.vapi.cli("lb as 2001::/16 10.0.0.%u" % (asid))
180
181             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
182             self.pg_enable_capture(self.pg_interfaces)
183             self.pg_start()
184
185             self.checkCapture(gre4=True, isv4=False)
186         finally:
187             for asid in self.ass:
188                 self.vapi.cli("lb as 2001::/16 10.0.0.%u del" % (asid))
189             self.vapi.cli("lb vip 2001::/16 encap gre4 del")
190
191     def test_lb_ip4_gre6(self):
192         """ Load Balancer IP4 GRE6 """
193         try:
194             self.vapi.cli("lb vip 90.0.0.0/8 encap gre6")
195             for asid in self.ass:
196                 self.vapi.cli("lb as 90.0.0.0/8 2002::%u" % (asid))
197
198             self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
199             self.pg_enable_capture(self.pg_interfaces)
200             self.pg_start()
201
202             # Scapy fails parsing GRE over IPv6.
203             # This check is therefore disabled for now.
204             # One can easily patch layers/inet6.py to fix the issue.
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")