4247a396d246d422e2d3c5cb45ec140cf2dbc262
[vpp.git] / test / test_vxlan.py
1 #!/usr/bin/env python3
2
3 import socket
4 from util import ip4_range, reassemble4
5 import unittest
6 from framework import VppTestCase, VppTestRunner
7 from template_bd import BridgeDomain
8
9 from scapy.layers.l2 import Ether
10 from scapy.layers.l2 import ARP
11 from scapy.packet import Raw, bind_layers
12 from scapy.layers.inet import IP, UDP
13 from scapy.layers.vxlan import VXLAN
14
15 import util
16 from vpp_ip_route import VppIpRoute, VppRoutePath
17 from vpp_vxlan_tunnel import VppVxlanTunnel
18 from vpp_ip import INVALID_INDEX
19 from vpp_neighbor import VppNeighbor
20
21
22 class TestVxlan(BridgeDomain, VppTestCase):
23     """ VXLAN Test Case """
24
25     def __init__(self, *args):
26         BridgeDomain.__init__(self)
27         VppTestCase.__init__(self, *args)
28
29     def encapsulate(self, pkt, vni):
30         """
31         Encapsulate the original payload frame by adding VXLAN header with its
32         UDP, IP and Ethernet fields
33         """
34         return (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
35                 IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
36                 UDP(sport=self.dport, dport=self.dport, chksum=0) /
37                 VXLAN(vni=vni, flags=self.flags) /
38                 pkt)
39
40     def ip_range(self, start, end):
41         """ range of remote ip's """
42         return ip4_range(self.pg0.remote_ip4, start, end)
43
44     def encap_mcast(self, pkt, src_ip, src_mac, vni):
45         """
46         Encapsulate the original payload frame by adding VXLAN header with its
47         UDP, IP and Ethernet fields
48         """
49         return (Ether(src=src_mac, dst=self.mcast_mac) /
50                 IP(src=src_ip, dst=self.mcast_ip4) /
51                 UDP(sport=self.dport, dport=self.dport, chksum=0) /
52                 VXLAN(vni=vni, flags=self.flags) /
53                 pkt)
54
55     def decapsulate(self, pkt):
56         """
57         Decapsulate the original payload frame by removing VXLAN header
58         """
59         # check if is set I flag
60         self.assertEqual(pkt[VXLAN].flags, int('0x8', 16))
61         return pkt[VXLAN].payload
62
63     # Method for checking VXLAN encapsulation.
64     #
65     def check_encapsulation(self, pkt, vni, local_only=False, mcast_pkt=False):
66         # TODO: add error messages
67         # Verify source MAC is VPP_MAC and destination MAC is MY_MAC resolved
68         #  by VPP using ARP.
69         self.assertEqual(pkt[Ether].src, self.pg0.local_mac)
70         if not local_only:
71             if not mcast_pkt:
72                 self.assertEqual(pkt[Ether].dst, self.pg0.remote_mac)
73             else:
74                 self.assertEqual(pkt[Ether].dst, type(self).mcast_mac)
75         # Verify VXLAN tunnel source IP is VPP_IP and destination IP is MY_IP.
76         self.assertEqual(pkt[IP].src, self.pg0.local_ip4)
77         if not local_only:
78             if not mcast_pkt:
79                 self.assertEqual(pkt[IP].dst, self.pg0.remote_ip4)
80             else:
81                 self.assertEqual(pkt[IP].dst, type(self).mcast_ip4)
82         # Verify UDP destination port is VXLAN 4789, source UDP port could be
83         #  arbitrary.
84         self.assertEqual(pkt[UDP].dport, self.dport)
85         # Verify UDP checksum
86         self.assert_udp_checksum_valid(pkt)
87         # Verify VNI
88         self.assertEqual(pkt[VXLAN].vni, vni)
89
90     @classmethod
91     def create_vxlan_flood_test_bd(cls, vni, n_ucast_tunnels, port):
92         # Create 10 ucast vxlan tunnels under bd
93         ip_range_start = 10
94         ip_range_end = ip_range_start + n_ucast_tunnels
95         next_hop_address = cls.pg0.remote_ip4
96         for dest_ip4 in ip4_range(next_hop_address, ip_range_start,
97                                   ip_range_end):
98             # add host route so dest_ip4 will not be resolved
99             rip = VppIpRoute(cls, dest_ip4, 32,
100                              [VppRoutePath(next_hop_address,
101                                            INVALID_INDEX)],
102                              register=False)
103             rip.add_vpp_config()
104
105             r = VppVxlanTunnel(cls, src=cls.pg0.local_ip4,
106                                src_port=port, dst_port=port,
107                                dst=dest_ip4, vni=vni)
108             r.add_vpp_config()
109             cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index, bd_id=vni)
110
111     @classmethod
112     def add_del_shared_mcast_dst_load(cls, port, is_add):
113         """
114         add or del tunnels sharing the same mcast dst
115         to test vxlan ref_count mechanism
116         """
117         n_shared_dst_tunnels = 20
118         vni_start = 10000
119         vni_end = vni_start + n_shared_dst_tunnels
120         for vni in range(vni_start, vni_end):
121             r = VppVxlanTunnel(cls, src=cls.pg0.local_ip4,
122                                src_port=port, dst_port=port,
123                                dst=cls.mcast_ip4, mcast_sw_if_index=1, vni=vni)
124             if is_add:
125                 r.add_vpp_config()
126                 if r.sw_if_index == 0xffffffff:
127                     raise ValueError("bad sw_if_index: ~0")
128             else:
129                 r.remove_vpp_config()
130
131     @classmethod
132     def add_shared_mcast_dst_load(cls, port):
133         cls.add_del_shared_mcast_dst_load(port=port, is_add=1)
134
135     @classmethod
136     def del_shared_mcast_dst_load(cls, port):
137         cls.add_del_shared_mcast_dst_load(port=port, is_add=0)
138
139     @classmethod
140     def add_del_mcast_tunnels_load(cls, port, is_add):
141         """
142         add or del tunnels to test vxlan stability
143         """
144         n_distinct_dst_tunnels = 200
145         ip_range_start = 10
146         ip_range_end = ip_range_start + n_distinct_dst_tunnels
147         for dest_ip4 in ip4_range(cls.mcast_ip4, ip_range_start,
148                                   ip_range_end):
149             vni = bytearray(socket.inet_pton(socket.AF_INET, dest_ip4))[3]
150             r = VppVxlanTunnel(cls, src=cls.pg0.local_ip4,
151                                src_port=port, dst_port=port,
152                                dst=dest_ip4, mcast_sw_if_index=1, vni=vni)
153             if is_add:
154                 r.add_vpp_config()
155             else:
156                 r.remove_vpp_config()
157
158     @classmethod
159     def add_mcast_tunnels_load(cls, port):
160         cls.add_del_mcast_tunnels_load(port=port, is_add=1)
161
162     @classmethod
163     def del_mcast_tunnels_load(cls, port):
164         cls.add_del_mcast_tunnels_load(port=port, is_add=0)
165
166     # Class method to start the VXLAN test case.
167     #  Overrides setUpClass method in VppTestCase class.
168     #  Python try..except statement is used to ensure that the tear down of
169     #  the class will be executed even if exception is raised.
170     #  @param cls The class pointer.
171     @classmethod
172     def setUpClass(cls):
173         super(TestVxlan, cls).setUpClass()
174
175         try:
176             cls.flags = 0x8
177
178             # Create 2 pg interfaces.
179             cls.create_pg_interfaces(range(4))
180             for pg in cls.pg_interfaces:
181                 pg.admin_up()
182
183             # Configure IPv4 addresses on VPP pg0.
184             cls.pg0.config_ip4()
185
186             # Resolve MAC address for VPP's IP address on pg0.
187             cls.pg0.resolve_arp()
188
189             # Our Multicast address
190             cls.mcast_ip4 = '239.1.1.1'
191             cls.mcast_mac = util.mcast_ip_to_mac(cls.mcast_ip4)
192         except Exception:
193             cls.tearDownClass()
194             raise
195
196     @classmethod
197     def tearDownClass(cls):
198         super(TestVxlan, cls).tearDownClass()
199
200     def setUp(self):
201         super(TestVxlan, self).setUp()
202
203     def createVxLANInterfaces(self, port=4789):
204         # Create VXLAN VTEP on VPP pg0, and put vxlan_tunnel0 and pg1
205         #  into BD.
206         self.dport = port
207
208         self.single_tunnel_vni = 0x12345
209         self.single_tunnel_bd = 1
210         r = VppVxlanTunnel(self, src=self.pg0.local_ip4,
211                            dst=self.pg0.remote_ip4,
212                            src_port=self.dport, dst_port=self.dport,
213                            vni=self.single_tunnel_vni)
214         r.add_vpp_config()
215         self.vapi.sw_interface_set_l2_bridge(rx_sw_if_index=r.sw_if_index,
216                                              bd_id=self.single_tunnel_bd)
217         self.vapi.sw_interface_set_l2_bridge(
218             rx_sw_if_index=self.pg1.sw_if_index, bd_id=self.single_tunnel_bd)
219
220         # Setup vni 2 to test multicast flooding
221         self.n_ucast_tunnels = 10
222         self.mcast_flood_bd = 2
223         self.create_vxlan_flood_test_bd(self.mcast_flood_bd,
224                                         self.n_ucast_tunnels,
225                                         self.dport)
226         r = VppVxlanTunnel(self, src=self.pg0.local_ip4, dst=self.mcast_ip4,
227                            src_port=self.dport, dst_port=self.dport,
228                            mcast_sw_if_index=1, vni=self.mcast_flood_bd)
229         r.add_vpp_config()
230         self.vapi.sw_interface_set_l2_bridge(rx_sw_if_index=r.sw_if_index,
231                                              bd_id=self.mcast_flood_bd)
232         self.vapi.sw_interface_set_l2_bridge(
233             rx_sw_if_index=self.pg2.sw_if_index, bd_id=self.mcast_flood_bd)
234
235         # Add and delete mcast tunnels to check stability
236         self.add_shared_mcast_dst_load(self.dport)
237         self.add_mcast_tunnels_load(self.dport)
238         self.del_shared_mcast_dst_load(self.dport)
239         self.del_mcast_tunnels_load(self.dport)
240
241         # Setup vni 3 to test unicast flooding
242         self.ucast_flood_bd = 3
243         self.create_vxlan_flood_test_bd(self.ucast_flood_bd,
244                                         self.n_ucast_tunnels,
245                                         self.dport)
246         self.vapi.sw_interface_set_l2_bridge(
247             rx_sw_if_index=self.pg3.sw_if_index, bd_id=self.ucast_flood_bd)
248
249         # Set scapy listen custom port for VxLAN
250         bind_layers(UDP, VXLAN, dport=self.dport)
251
252     def encap_big_packet(self):
253         self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [1500, 0, 0, 0])
254
255         frame = (Ether(src='00:00:00:00:00:02', dst='00:00:00:00:00:01') /
256                  IP(src='4.3.2.1', dst='1.2.3.4') /
257                  UDP(sport=20000, dport=10000) /
258                  Raw(b'\xa5' * 1450))
259
260         self.pg1.add_stream([frame])
261
262         self.pg0.enable_capture()
263
264         self.pg_start()
265
266         # Pick first received frame and check if it's correctly encapsulated.
267         out = self.pg0.get_capture(2)
268         ether = out[0]
269         pkt = reassemble4(out)
270         pkt = ether / pkt
271         self.check_encapsulation(pkt, self.single_tunnel_vni)
272
273         payload = self.decapsulate(pkt)
274         # TODO: Scapy bug?
275         # self.assert_eq_pkts(payload, frame)
276
277     """
278     Tests with default port (4789)
279     """
280     def test_decap(self):
281         """ Decapsulation test
282         from BridgeDoman
283         """
284         self.createVxLANInterfaces()
285         super(TestVxlan, self).test_decap()
286
287     def test_encap(self):
288         """ Encapsulation test
289         from BridgeDoman
290         """
291         self.createVxLANInterfaces()
292         super(TestVxlan, self).test_encap()
293
294     def test_encap_big_packet(self):
295         """ Encapsulation test send big frame from pg1
296         Verify receipt of encapsulated frames on pg0
297         """
298         self.createVxLANInterfaces()
299         self.encap_big_packet()
300
301     def test_ucast_flood(self):
302         """ Unicast flood test
303         from BridgeDoman
304         """
305         self.createVxLANInterfaces()
306         super(TestVxlan, self).test_ucast_flood()
307
308     def test_mcast_flood(self):
309         """ Multicast flood test
310         from BridgeDoman
311         """
312         self.createVxLANInterfaces()
313         super(TestVxlan, self).test_mcast_flood()
314
315     def test_mcast_rcv(self):
316         """ Multicast receive test
317         from BridgeDoman
318         """
319         self.createVxLANInterfaces()
320         super(TestVxlan, self).test_mcast_rcv()
321
322     """
323     Tests with custom port
324     """
325     def test_decap_custom_port(self):
326         """ Decapsulation test custom port
327         from BridgeDoman
328         """
329         self.createVxLANInterfaces(1111)
330         super(TestVxlan, self).test_decap()
331
332     def test_encap_custom_port(self):
333         """ Encapsulation test custom port
334         from BridgeDoman
335         """
336         self.createVxLANInterfaces(1111)
337         super(TestVxlan, self).test_encap()
338
339     def test_ucast_flood_custom_port(self):
340         """ Unicast flood test custom port
341         from BridgeDoman
342         """
343         self.createVxLANInterfaces(1111)
344         super(TestVxlan, self).test_ucast_flood()
345
346     def test_mcast_flood_custom_port(self):
347         """ Multicast flood test custom port
348         from BridgeDoman
349         """
350         self.createVxLANInterfaces(1111)
351         super(TestVxlan, self).test_mcast_flood()
352
353     def test_mcast_rcv_custom_port(self):
354         """ Multicast receive test custom port
355         from BridgeDoman
356         """
357         self.createVxLANInterfaces(1111)
358         super(TestVxlan, self).test_mcast_rcv()
359
360     # Method to define VPP actions before tear down of the test case.
361     #  Overrides tearDown method in VppTestCase class.
362     #  @param self The object pointer.
363
364     def tearDown(self):
365         super(TestVxlan, self).tearDown()
366
367     def show_commands_at_teardown(self):
368         self.logger.info(self.vapi.cli("show bridge-domain 1 detail"))
369         self.logger.info(self.vapi.cli("show bridge-domain 2 detail"))
370         self.logger.info(self.vapi.cli("show bridge-domain 3 detail"))
371         self.logger.info(self.vapi.cli("show vxlan tunnel"))
372
373
374 class TestVxlan2(VppTestCase):
375     """ VXLAN Test Case """
376     def setUp(self):
377         super(TestVxlan2, self).setUp()
378
379         # Create 2 pg interfaces.
380         self.create_pg_interfaces(range(4))
381         for pg in self.pg_interfaces:
382             pg.admin_up()
383
384         # Configure IPv4 addresses on VPP pg0.
385         self.pg0.config_ip4()
386         self.pg0.resolve_arp()
387
388     def tearDown(self):
389         super(TestVxlan2, self).tearDown()
390
391     def test_xconnect(self):
392         """ VXLAN source address not local """
393
394         #
395         # test the broken configuration of a VXLAN tunnel whose
396         # source address is not local ot the box. packets sent
397         # through the tunnel should be dropped
398         #
399         t = VppVxlanTunnel(self,
400                            src="10.0.0.5",
401                            dst=self.pg0.local_ip4,
402                            vni=1000)
403         t.add_vpp_config()
404         t.admin_up()
405
406         self.vapi.sw_interface_set_l2_xconnect(t.sw_if_index,
407                                                self.pg1.sw_if_index,
408                                                enable=1)
409         self.vapi.sw_interface_set_l2_xconnect(self.pg1.sw_if_index,
410                                                t.sw_if_index,
411                                                enable=1)
412
413         p = (Ether(src="00:11:22:33:44:55",
414                    dst="00:00:00:11:22:33") /
415              IP(src="4.3.2.1", dst="1.2.3.4") /
416              UDP(sport=20000, dport=10000) /
417              Raw(b'\xa5' * 1450))
418
419         rx = self.send_and_assert_no_replies(self.pg1, [p])
420
421
422 class TestVxlanL2Mode(VppTestCase):
423     """ VXLAN Test Case """
424     def setUp(self):
425         super(TestVxlanL2Mode, self).setUp()
426
427         # Create 2 pg interfaces.
428         self.create_pg_interfaces(range(2))
429         for pg in self.pg_interfaces:
430             pg.admin_up()
431
432         # Configure IPv4 addresses on VPP pg0.
433         self.pg0.config_ip4()
434         self.pg0.resolve_arp()
435
436         # Configure IPv4 addresses on VPP pg1.
437         self.pg1.config_ip4()
438
439     def tearDown(self):
440         super(TestVxlanL2Mode, self).tearDown()
441
442     def test_l2_mode(self):
443         """ VXLAN L2 mode """
444         t = VppVxlanTunnel(self,
445                            src=self.pg0.local_ip4,
446                            dst=self.pg0.remote_ip4,
447                            vni=1000, is_l3=False)
448         t.add_vpp_config()
449         t.config_ip4()
450         t.admin_up()
451
452         dstIP = t.local_ip4[:-1] + "2"
453
454         # Create a packet to send
455         p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
456              IP(src=self.pg1.local_ip4, dst=dstIP) /
457              UDP(sport=555, dport=556) /
458              Raw(b'\x00' * 80))
459
460         # Expect ARP request
461         rx = self.send_and_expect(self.pg1, [p], self.pg0)
462         for p in rx:
463             self.assertEqual(p[Ether].dst, self.pg0.remote_mac)
464             self.assertEqual(p[Ether].src, self.pg0.local_mac)
465             self.assertEqual(p[ARP].op, 1)
466             self.assertEqual(p[ARP].pdst, dstIP)
467
468         # Resolve ARP
469         VppNeighbor(self, t.sw_if_index,
470                     self.pg1.remote_mac,
471                     dstIP).add_vpp_config()
472
473         # Send packets
474         NUM_PKTS = 128
475         rx = self.send_and_expect(self.pg1, p * NUM_PKTS, self.pg0)
476         self.assertEqual(NUM_PKTS, len(rx))
477
478
479 if __name__ == '__main__':
480     unittest.main(testRunner=VppTestRunner)