Tests to target holes in adjacency and DPO test coverage
[vpp.git] / test / test_ip4.py
1 #!/usr/bin/env python
2 import random
3 import socket
4 import unittest
5
6 from framework import VppTestCase, VppTestRunner
7 from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
8 from vpp_ip_route import VppIpRoute, VppRoutePath
9
10 from scapy.packet import Raw
11 from scapy.layers.l2 import Ether, Dot1Q
12 from scapy.layers.inet import IP, UDP, ICMP, icmptypes, icmpcodes
13 from util import ppp
14
15
16 class TestIPv4(VppTestCase):
17     """ IPv4 Test Case """
18
19     def setUp(self):
20         """
21         Perform test setup before test case.
22
23         **Config:**
24             - create 3 pg interfaces
25                 - untagged pg0 interface
26                 - Dot1Q subinterface on pg1
27                 - Dot1AD subinterface on pg2
28             - setup interfaces:
29                 - put it into UP state
30                 - set IPv4 addresses
31                 - resolve neighbor address using ARP
32             - configure 200 fib entries
33
34         :ivar list interfaces: pg interfaces and subinterfaces.
35         :ivar dict flows: IPv4 packet flows in test.
36         :ivar list pg_if_packet_sizes: packet sizes in test.
37         """
38         super(TestIPv4, self).setUp()
39
40         # create 3 pg interfaces
41         self.create_pg_interfaces(range(3))
42
43         # create 2 subinterfaces for pg1 and pg2
44         self.sub_interfaces = [
45             VppDot1QSubint(self, self.pg1, 100),
46             VppDot1ADSubint(self, self.pg2, 200, 300, 400)]
47
48         # packet flows mapping pg0 -> pg1.sub, pg2.sub, etc.
49         self.flows = dict()
50         self.flows[self.pg0] = [self.pg1.sub_if, self.pg2.sub_if]
51         self.flows[self.pg1.sub_if] = [self.pg0, self.pg2.sub_if]
52         self.flows[self.pg2.sub_if] = [self.pg0, self.pg1.sub_if]
53
54         # packet sizes
55         self.pg_if_packet_sizes = [64, 512, 1518, 9018]
56         self.sub_if_packet_sizes = [64, 512, 1518 + 4, 9018 + 4]
57
58         self.interfaces = list(self.pg_interfaces)
59         self.interfaces.extend(self.sub_interfaces)
60
61         # setup all interfaces
62         for i in self.interfaces:
63             i.admin_up()
64             i.config_ip4()
65             i.resolve_arp()
66
67         # config 2M FIB entries
68         self.config_fib_entries(200)
69
70     def tearDown(self):
71         """Run standard test teardown and log ``show ip arp``."""
72         super(TestIPv4, self).tearDown()
73         if not self.vpp_dead:
74             self.logger.info(self.vapi.cli("show ip arp"))
75             # info(self.vapi.cli("show ip fib"))  # many entries
76
77     def config_fib_entries(self, count):
78         """For each interface add to the FIB table *count* routes to
79         "10.0.0.1/32" destination with interface's local address as next-hop
80         address.
81
82         :param int count: Number of FIB entries.
83
84         - *TODO:* check if the next-hop address shouldn't be remote address
85           instead of local address.
86         """
87         n_int = len(self.interfaces)
88         percent = 0
89         counter = 0.0
90         dest_addr = socket.inet_pton(socket.AF_INET, "10.0.0.1")
91         dest_addr_len = 32
92         for i in self.interfaces:
93             next_hop_address = i.local_ip4n
94             for j in range(count / n_int):
95                 self.vapi.ip_add_del_route(
96                     dest_addr, dest_addr_len, next_hop_address)
97                 counter += 1
98                 if counter / count * 100 > percent:
99                     self.logger.info("Configure %d FIB entries .. %d%% done" %
100                                      (count, percent))
101                     percent += 1
102
103     def create_stream(self, src_if, packet_sizes):
104         """Create input packet stream for defined interface.
105
106         :param VppInterface src_if: Interface to create packet stream for.
107         :param list packet_sizes: Required packet sizes.
108         """
109         pkts = []
110         for i in range(0, 257):
111             dst_if = self.flows[src_if][i % 2]
112             info = self.create_packet_info(src_if, dst_if)
113             payload = self.info_to_payload(info)
114             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
115                  IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) /
116                  UDP(sport=1234, dport=1234) /
117                  Raw(payload))
118             info.data = p.copy()
119             if isinstance(src_if, VppSubInterface):
120                 p = src_if.add_dot1_layer(p)
121             size = packet_sizes[(i // 2) % len(packet_sizes)]
122             self.extend_packet(p, size)
123             pkts.append(p)
124         return pkts
125
126     def verify_capture(self, dst_if, capture):
127         """Verify captured input packet stream for defined interface.
128
129         :param VppInterface dst_if: Interface to verify captured packet stream
130                                     for.
131         :param list capture: Captured packet stream.
132         """
133         self.logger.info("Verifying capture on interface %s" % dst_if.name)
134         last_info = dict()
135         for i in self.interfaces:
136             last_info[i.sw_if_index] = None
137         is_sub_if = False
138         dst_sw_if_index = dst_if.sw_if_index
139         if hasattr(dst_if, 'parent'):
140             is_sub_if = True
141         for packet in capture:
142             if is_sub_if:
143                 # Check VLAN tags and Ethernet header
144                 packet = dst_if.remove_dot1_layer(packet)
145             self.assertTrue(Dot1Q not in packet)
146             try:
147                 ip = packet[IP]
148                 udp = packet[UDP]
149                 payload_info = self.payload_to_info(str(packet[Raw]))
150                 packet_index = payload_info.index
151                 self.assertEqual(payload_info.dst, dst_sw_if_index)
152                 self.logger.debug(
153                     "Got packet on port %s: src=%u (id=%u)" %
154                     (dst_if.name, payload_info.src, packet_index))
155                 next_info = self.get_next_packet_info_for_interface2(
156                     payload_info.src, dst_sw_if_index,
157                     last_info[payload_info.src])
158                 last_info[payload_info.src] = next_info
159                 self.assertTrue(next_info is not None)
160                 self.assertEqual(packet_index, next_info.index)
161                 saved_packet = next_info.data
162                 # Check standard fields
163                 self.assertEqual(ip.src, saved_packet[IP].src)
164                 self.assertEqual(ip.dst, saved_packet[IP].dst)
165                 self.assertEqual(udp.sport, saved_packet[UDP].sport)
166                 self.assertEqual(udp.dport, saved_packet[UDP].dport)
167             except:
168                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
169                 raise
170         for i in self.interfaces:
171             remaining_packet = self.get_next_packet_info_for_interface2(
172                 i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index])
173             self.assertTrue(remaining_packet is None,
174                             "Interface %s: Packet expected from interface %s "
175                             "didn't arrive" % (dst_if.name, i.name))
176
177     def test_fib(self):
178         """ IPv4 FIB test
179
180         Test scenario:
181
182             - Create IPv4 stream for pg0 interface
183             - Create IPv4 tagged streams for pg1's and pg2's subinterface.
184             - Send and verify received packets on each interface.
185         """
186
187         pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes)
188         self.pg0.add_stream(pkts)
189
190         for i in self.sub_interfaces:
191             pkts = self.create_stream(i, self.sub_if_packet_sizes)
192             i.parent.add_stream(pkts)
193
194         self.pg_enable_capture(self.pg_interfaces)
195         self.pg_start()
196
197         pkts = self.pg0.get_capture()
198         self.verify_capture(self.pg0, pkts)
199
200         for i in self.sub_interfaces:
201             pkts = i.parent.get_capture()
202             self.verify_capture(i, pkts)
203
204
205 class TestIPv4FibCrud(VppTestCase):
206     """ FIB - add/update/delete - ip4 routes
207
208     Test scenario:
209         - add 1k,
210         - del 100,
211         - add new 1k,
212         - del 1.5k
213
214     ..note:: Python API is too slow to add many routes, needs replacement.
215     """
216
217     def config_fib_many_to_one(self, start_dest_addr, next_hop_addr, count):
218         """
219
220         :param start_dest_addr:
221         :param next_hop_addr:
222         :param count:
223         :return list: added ips with 32 prefix
224         """
225         added_ips = []
226         dest_addr = int(socket.inet_pton(socket.AF_INET,
227                                          start_dest_addr).encode('hex'),
228                         16)
229         dest_addr_len = 32
230         n_next_hop_addr = socket.inet_pton(socket.AF_INET, next_hop_addr)
231         for _ in range(count):
232             n_dest_addr = '{:08x}'.format(dest_addr).decode('hex')
233             self.vapi.ip_add_del_route(n_dest_addr, dest_addr_len,
234                                        n_next_hop_addr)
235             added_ips.append(socket.inet_ntoa(n_dest_addr))
236             dest_addr += 1
237         return added_ips
238
239     def unconfig_fib_many_to_one(self, start_dest_addr, next_hop_addr, count):
240
241         removed_ips = []
242         dest_addr = int(socket.inet_pton(socket.AF_INET,
243                                          start_dest_addr).encode('hex'),
244                         16)
245         dest_addr_len = 32
246         n_next_hop_addr = socket.inet_pton(socket.AF_INET, next_hop_addr)
247         for _ in range(count):
248             n_dest_addr = '{:08x}'.format(dest_addr).decode('hex')
249             self.vapi.ip_add_del_route(n_dest_addr, dest_addr_len,
250                                        n_next_hop_addr, is_add=0)
251             removed_ips.append(socket.inet_ntoa(n_dest_addr))
252             dest_addr += 1
253         return removed_ips
254
255     def create_stream(self, src_if, dst_if, dst_ips, count):
256         pkts = []
257
258         for _ in range(count):
259             dst_addr = random.choice(dst_ips)
260             info = self.create_packet_info(src_if, dst_if)
261             payload = self.info_to_payload(info)
262             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
263                  IP(src=src_if.remote_ip4, dst=dst_addr) /
264                  UDP(sport=1234, dport=1234) /
265                  Raw(payload))
266             info.data = p.copy()
267             self.extend_packet(p, random.choice(self.pg_if_packet_sizes))
268             pkts.append(p)
269
270         return pkts
271
272     def _find_ip_match(self, find_in, pkt):
273         for p in find_in:
274             if self.payload_to_info(str(p[Raw])) == \
275                     self.payload_to_info(str(pkt[Raw])):
276                 if p[IP].src != pkt[IP].src:
277                     break
278                 if p[IP].dst != pkt[IP].dst:
279                     break
280                 if p[UDP].sport != pkt[UDP].sport:
281                     break
282                 if p[UDP].dport != pkt[UDP].dport:
283                     break
284                 return p
285         return None
286
287     @staticmethod
288     def _match_route_detail(route_detail, ip, address_length=32, table_id=0):
289         if route_detail.address == socket.inet_pton(socket.AF_INET, ip):
290             if route_detail.table_id != table_id:
291                 return False
292             elif route_detail.address_length != address_length:
293                 return False
294             else:
295                 return True
296         else:
297             return False
298
299     def verify_capture(self, dst_interface, received_pkts, expected_pkts):
300         self.assertEqual(len(received_pkts), len(expected_pkts))
301         to_verify = list(expected_pkts)
302         for p in received_pkts:
303             self.assertEqual(p.src, dst_interface.local_mac)
304             self.assertEqual(p.dst, dst_interface.remote_mac)
305             x = self._find_ip_match(to_verify, p)
306             to_verify.remove(x)
307         self.assertListEqual(to_verify, [])
308
309     def verify_route_dump(self, fib_dump, ips):
310
311         def _ip_in_route_dump(ip, fib_dump):
312             return next((route for route in fib_dump
313                          if self._match_route_detail(route, ip)),
314                         False)
315
316         for ip in ips:
317             self.assertTrue(_ip_in_route_dump(ip, fib_dump),
318                             'IP {} is not in fib dump.'.format(ip))
319
320     def verify_not_in_route_dump(self, fib_dump, ips):
321
322         def _ip_in_route_dump(ip, fib_dump):
323             return next((route for route in fib_dump
324                          if self._match_route_detail(route, ip)),
325                         False)
326
327         for ip in ips:
328             self.assertFalse(_ip_in_route_dump(ip, fib_dump),
329                              'IP {} is in fib dump.'.format(ip))
330
331     @classmethod
332     def setUpClass(cls):
333         """
334         #. Create and initialize 3 pg interfaces.
335         #. initialize class attributes configured_routes and deleted_routes
336            to store information between tests.
337         """
338         super(TestIPv4FibCrud, cls).setUpClass()
339
340         try:
341             # create 3 pg interfaces
342             cls.create_pg_interfaces(range(3))
343
344             cls.interfaces = list(cls.pg_interfaces)
345
346             # setup all interfaces
347             for i in cls.interfaces:
348                 i.admin_up()
349                 i.config_ip4()
350                 i.resolve_arp()
351
352             cls.configured_routes = []
353             cls.deleted_routes = []
354             cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
355
356         except Exception:
357             super(TestIPv4FibCrud, cls).tearDownClass()
358             raise
359
360     def setUp(self):
361         super(TestIPv4FibCrud, self).setUp()
362         self.reset_packet_infos()
363
364     def test_1_add_routes(self):
365         """ Add 1k routes
366
367         - add 100 routes check with traffic script.
368         """
369         # config 1M FIB entries
370         self.configured_routes.extend(self.config_fib_many_to_one(
371             "10.0.0.0", self.pg0.remote_ip4, 100))
372
373         fib_dump = self.vapi.ip_fib_dump()
374         self.verify_route_dump(fib_dump, self.configured_routes)
375
376         self.stream_1 = self.create_stream(
377             self.pg1, self.pg0, self.configured_routes, 100)
378         self.stream_2 = self.create_stream(
379             self.pg2, self.pg0, self.configured_routes, 100)
380         self.pg1.add_stream(self.stream_1)
381         self.pg2.add_stream(self.stream_2)
382
383         self.pg_enable_capture(self.pg_interfaces)
384         self.pg_start()
385
386         pkts = self.pg0.get_capture(len(self.stream_1) + len(self.stream_2))
387         self.verify_capture(self.pg0, pkts, self.stream_1 + self.stream_2)
388
389     def test_2_del_routes(self):
390         """ Delete 100 routes
391
392         - delete 10 routes check with traffic script.
393         """
394         self.deleted_routes.extend(self.unconfig_fib_many_to_one(
395             "10.0.0.10", self.pg0.remote_ip4, 10))
396         for x in self.deleted_routes:
397             self.configured_routes.remove(x)
398
399         fib_dump = self.vapi.ip_fib_dump()
400         self.verify_route_dump(fib_dump, self.configured_routes)
401
402         self.stream_1 = self.create_stream(
403             self.pg1, self.pg0, self.configured_routes, 100)
404         self.stream_2 = self.create_stream(
405             self.pg2, self.pg0, self.configured_routes, 100)
406         self.stream_3 = self.create_stream(
407             self.pg1, self.pg0, self.deleted_routes, 100)
408         self.stream_4 = self.create_stream(
409             self.pg2, self.pg0, self.deleted_routes, 100)
410         self.pg1.add_stream(self.stream_1 + self.stream_3)
411         self.pg2.add_stream(self.stream_2 + self.stream_4)
412         self.pg_enable_capture(self.pg_interfaces)
413         self.pg_start()
414
415         pkts = self.pg0.get_capture(len(self.stream_1) + len(self.stream_2))
416         self.verify_capture(self.pg0, pkts, self.stream_1 + self.stream_2)
417
418     def test_3_add_new_routes(self):
419         """ Add 1k routes
420
421         - re-add 5 routes check with traffic script.
422         - add 100 routes check with traffic script.
423         """
424         tmp = self.config_fib_many_to_one(
425             "10.0.0.10", self.pg0.remote_ip4, 5)
426         self.configured_routes.extend(tmp)
427         for x in tmp:
428             self.deleted_routes.remove(x)
429
430         self.configured_routes.extend(self.config_fib_many_to_one(
431             "10.0.1.0", self.pg0.remote_ip4, 100))
432
433         fib_dump = self.vapi.ip_fib_dump()
434         self.verify_route_dump(fib_dump, self.configured_routes)
435
436         self.stream_1 = self.create_stream(
437             self.pg1, self.pg0, self.configured_routes, 300)
438         self.stream_2 = self.create_stream(
439             self.pg2, self.pg0, self.configured_routes, 300)
440         self.stream_3 = self.create_stream(
441             self.pg1, self.pg0, self.deleted_routes, 100)
442         self.stream_4 = self.create_stream(
443             self.pg2, self.pg0, self.deleted_routes, 100)
444
445         self.pg1.add_stream(self.stream_1 + self.stream_3)
446         self.pg2.add_stream(self.stream_2 + self.stream_4)
447         self.pg_enable_capture(self.pg_interfaces)
448         self.pg_start()
449
450         pkts = self.pg0.get_capture(len(self.stream_1) + len(self.stream_2))
451         self.verify_capture(self.pg0, pkts, self.stream_1 + self.stream_2)
452
453     def test_4_del_routes(self):
454         """ Delete 1.5k routes
455
456         - delete 5 routes check with traffic script.
457         - add 100 routes check with traffic script.
458         """
459         self.deleted_routes.extend(self.unconfig_fib_many_to_one(
460             "10.0.0.0", self.pg0.remote_ip4, 15))
461         self.deleted_routes.extend(self.unconfig_fib_many_to_one(
462             "10.0.0.20", self.pg0.remote_ip4, 85))
463         self.deleted_routes.extend(self.unconfig_fib_many_to_one(
464             "10.0.1.0", self.pg0.remote_ip4, 100))
465         fib_dump = self.vapi.ip_fib_dump()
466         self.verify_not_in_route_dump(fib_dump, self.deleted_routes)
467
468
469 class TestIPNull(VppTestCase):
470     """ IPv4 routes via NULL """
471
472     def setUp(self):
473         super(TestIPNull, self).setUp()
474
475         # create 2 pg interfaces
476         self.create_pg_interfaces(range(1))
477
478         for i in self.pg_interfaces:
479             i.admin_up()
480             i.config_ip4()
481             i.resolve_arp()
482
483     def tearDown(self):
484         super(TestIPNull, self).tearDown()
485         for i in self.pg_interfaces:
486             i.unconfig_ip4()
487             i.admin_down()
488
489     def test_ip_null(self):
490         """ IP NULL route """
491
492         #
493         # A route via IP NULL that will reply with ICMP unreachables
494         #
495         ip_unreach = VppIpRoute(self, "10.0.0.1", 32, [], is_unreach=1)
496         ip_unreach.add_vpp_config()
497
498         p_unreach = (Ether(src=self.pg0.remote_mac,
499                            dst=self.pg0.local_mac) /
500                      IP(src=self.pg0.remote_ip4, dst="10.0.0.1") /
501                      UDP(sport=1234, dport=1234) /
502                      Raw('\xa5' * 100))
503
504         self.pg0.add_stream(p_unreach)
505         self.pg_enable_capture(self.pg_interfaces)
506         self.pg_start()
507
508         rx = self.pg0.get_capture(1)
509         rx = rx[0]
510         icmp = rx[ICMP]
511
512         self.assertEqual(icmptypes[icmp.type], "dest-unreach")
513         self.assertEqual(icmpcodes[icmp.type][icmp.code], "host-unreachable")
514         self.assertEqual(icmp.src, self.pg0.remote_ip4)
515         self.assertEqual(icmp.dst, "10.0.0.1")
516
517         #
518         # ICMP replies are rate limited. so sit and spin.
519         #
520         self.sleep(1)
521
522         #
523         # A route via IP NULL that will reply with ICMP prohibited
524         #
525         ip_prohibit = VppIpRoute(self, "10.0.0.2", 32, [], is_prohibit=1)
526         ip_prohibit.add_vpp_config()
527
528         p_prohibit = (Ether(src=self.pg0.remote_mac,
529                             dst=self.pg0.local_mac) /
530                       IP(src=self.pg0.remote_ip4, dst="10.0.0.2") /
531                       UDP(sport=1234, dport=1234) /
532                       Raw('\xa5' * 100))
533
534         self.pg0.add_stream(p_prohibit)
535         self.pg_enable_capture(self.pg_interfaces)
536         self.pg_start()
537
538         rx = self.pg0.get_capture(1)
539
540         rx = rx[0]
541         icmp = rx[ICMP]
542
543         self.assertEqual(icmptypes[icmp.type], "dest-unreach")
544         self.assertEqual(icmpcodes[icmp.type][icmp.code], "host-prohibited")
545         self.assertEqual(icmp.src, self.pg0.remote_ip4)
546         self.assertEqual(icmp.dst, "10.0.0.2")
547
548
549 if __name__ == '__main__':
550     unittest.main(testRunner=VppTestRunner)