virtio: Add RX queue full statisitics
[vpp.git] / test / test_ip_ecmp.py
1 #!/usr/bin/env python3
2
3 import unittest
4 import random
5 from ipaddress import IPv4Address, IPv6Address, AddressValueError
6
7 from framework import VppTestCase
8 from asfframework import VppTestRunner
9 from util import ppp
10
11 from scapy.packet import Raw
12 from scapy.layers.l2 import Ether
13 from scapy.layers.inet import IP, UDP
14 from scapy.layers.inet6 import IPv6
15 from vpp_ip_route import VppIpRoute, VppRoutePath
16
17 try:
18     text_type = unicode
19 except NameError:
20     text_type = str
21
22 #
23 # The number of packets to sent.
24 #
25 N_PKTS_IN_STREAM = 300
26
27
28 class TestECMP(VppTestCase):
29     """Equal-cost multi-path routing Test Case"""
30
31     @classmethod
32     def setUpClass(cls):
33         """
34         Perform standard class setup (defined by class method setUpClass in
35         class VppTestCase) before running the test case, set test case related
36         variables and configure VPP.
37         """
38         super(TestECMP, cls).setUpClass()
39
40         # create 4 pg interfaces
41         cls.create_pg_interfaces(range(4))
42
43         # packet sizes to test
44         cls.pg_if_packet_sizes = [64, 1500, 9018]
45
46         # setup interfaces
47         for i in cls.pg_interfaces:
48             i.admin_up()
49             i.generate_remote_hosts(5)
50             i.config_ip4()
51             i.resolve_arp()
52             i.configure_ipv4_neighbors()
53             i.config_ip6()
54             i.resolve_ndp()
55             i.configure_ipv6_neighbors()
56
57     @classmethod
58     def tearDownClass(cls):
59         if not cls.vpp_dead:
60             for i in cls.pg_interfaces:
61                 i.unconfig_ip4()
62                 i.unconfig_ip6()
63                 i.admin_down()
64
65         super(TestECMP, cls).tearDownClass()
66
67     def setUp(self):
68         super(TestECMP, self).setUp()
69         self.reset_packet_infos()
70
71     def tearDown(self):
72         """
73         Show various debug prints after each test.
74         """
75         super(TestECMP, self).tearDown()
76
77     def show_commands_at_teardown(self):
78         self.logger.info(self.vapi.ppcli("show ip4 neighbors"))
79         self.logger.info(self.vapi.ppcli("show ip6 neighbors"))
80
81     def get_ip_address(self, ip_addr_start, ip_prefix_len):
82         """
83
84         :param str ip_addr_start: Starting IPv4 or IPv6 address.
85         :param int ip_prefix_len: IP address prefix length.
86         :return: Random IPv4 or IPv6 address from required range.
87         """
88         try:
89             ip_addr = IPv4Address(text_type(ip_addr_start))
90             ip_max_len = 32
91         except (AttributeError, AddressValueError):
92             ip_addr = IPv6Address(text_type(ip_addr_start))
93             ip_max_len = 128
94
95         return str(ip_addr + random.randint(0, 2 ** (ip_max_len - ip_prefix_len) - 2))
96
97     def create_stream(
98         self, src_if, src_ip_start, dst_ip_start, ip_prefix_len, packet_sizes, ip_l=IP
99     ):
100         """Create input packet stream for defined interfaces.
101
102         :param VppInterface src_if: Source Interface for packet stream.
103         :param str src_ip_start: Starting source IPv4 or IPv6 address.
104         :param str dst_ip_start: Starting destination IPv4 or IPv6 address.
105         :param int ip_prefix_len: IP address prefix length.
106         :param list packet_sizes: packet size to test.
107         :param Scapy ip_l: Required IP layer - IP or IPv6. (Default is IP.)
108         """
109         pkts = []
110         for i in range(0, N_PKTS_IN_STREAM):
111             info = self.create_packet_info(src_if, src_if)
112             payload = self.info_to_payload(info)
113             src_ip = self.get_ip_address(src_ip_start, ip_prefix_len)
114             dst_ip = self.get_ip_address(dst_ip_start, ip_prefix_len)
115             p = (
116                 Ether(dst=src_if.local_mac, src=src_if.remote_mac)
117                 / ip_l(src=src_ip, dst=dst_ip)
118                 / UDP(sport=1234, dport=1234)
119                 / Raw(payload)
120             )
121             info.data = p.copy()
122             size = random.choice(packet_sizes)
123             self.extend_packet(p, size)
124             pkts.append(p)
125         return pkts
126
127     def verify_capture(self, rx_if, capture, ip_l=IP):
128         """Verify captured input packet stream for defined interface.
129
130         :param VppInterface rx_if: Interface to verify captured packet stream.
131         :param list capture: Captured packet stream.
132         :param Scapy ip_l: Required IP layer - IP or IPv6. (Default is IP.)
133         """
134         self.logger.info("Verifying capture on interface %s" % rx_if.name)
135
136         count = 0
137         host_counters = {}
138         for host_mac in rx_if._hosts_by_mac:
139             host_counters[host_mac] = 0
140
141         for packet in capture:
142             try:
143                 ip_received = packet[ip_l]
144                 payload_info = self.payload_to_info(packet[Raw])
145                 packet_index = payload_info.index
146                 ip_sent = self._packet_infos[packet_index].data[ip_l]
147                 self.logger.debug(
148                     "Got packet on port %s: src=%u (id=%u)"
149                     % (rx_if.name, payload_info.src, packet_index)
150                 )
151                 # Check standard fields
152                 self.assertIn(
153                     packet.dst,
154                     rx_if._hosts_by_mac,
155                     "Destination MAC address %s shouldn't be routed "
156                     "via interface %s" % (packet.dst, rx_if.name),
157                 )
158                 self.assertEqual(packet.src, rx_if.local_mac)
159                 self.assertEqual(ip_received.src, ip_sent.src)
160                 self.assertEqual(ip_received.dst, ip_sent.dst)
161                 host_counters[packet.dst] += 1
162                 self._packet_infos.pop(packet_index)
163
164             except:
165                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
166                 raise
167
168         # We expect packet routed via all host of pg interface
169         for host_mac in host_counters:
170             nr = host_counters[host_mac]
171             self.assertNotEqual(nr, 0, "No packet routed via host %s" % host_mac)
172             self.logger.info(
173                 "%u packets routed via host %s of %s interface"
174                 % (nr, host_mac, rx_if.name)
175             )
176             count += nr
177         self.logger.info(
178             "Total amount of %u packets routed via %s interface" % (count, rx_if.name)
179         )
180
181         return count
182
183     def create_ip_routes(self, dst_ip_net, dst_prefix_len, is_ipv6=0):
184         """
185         Create IP routes for defined destination IP network.
186
187         :param str dst_ip_net: Destination IP network.
188         :param int dst_prefix_len: IP address prefix length.
189         :param int is_ipv6: 0 if an ip4 route, else ip6
190         """
191
192         paths = []
193         for pg_if in self.pg_interfaces[1:]:
194             for nh_host in pg_if.remote_hosts:
195                 nh_host_ip = nh_host.ip4 if is_ipv6 == 0 else nh_host.ip6
196                 paths.append(VppRoutePath(nh_host_ip, pg_if.sw_if_index))
197
198         rip = VppIpRoute(self, dst_ip_net, dst_prefix_len, paths)
199         rip.add_vpp_config()
200         self.logger.info("Route via %s on %s created" % (nh_host_ip, pg_if.name))
201
202         self.logger.debug(self.vapi.ppcli("show ip fib"))
203         self.logger.debug(self.vapi.ppcli("show ip6 fib"))
204
205     def test_ip_ecmp(self):
206         """IP equal-cost multi-path routing test"""
207
208         src_ip_net = "16.0.0.1"
209         dst_ip_net = "32.0.0.1"
210         ip_prefix_len = 24
211
212         self.create_ip_routes(dst_ip_net, ip_prefix_len)
213
214         pkts = self.create_stream(
215             self.pg0, src_ip_net, dst_ip_net, ip_prefix_len, self.pg_if_packet_sizes
216         )
217         self.pg0.add_stream(pkts)
218
219         self.pg_enable_capture(self.pg_interfaces)
220         self.pg_start()
221
222         # We expect packets on pg1, pg2 and pg3, but not on pg0
223         rx_count = 0
224         for pg_if in self.pg_interfaces[1:]:
225             capture = pg_if._get_capture(timeout=1)
226             self.assertNotEqual(
227                 len(capture), 0, msg="No packets captured on %s" % pg_if.name
228             )
229             rx_count += self.verify_capture(pg_if, capture)
230         self.pg0.assert_nothing_captured(remark="IP packets forwarded on pg0")
231
232         # Check that all packets were forwarded via pg1, pg2 and pg3
233         self.assertEqual(rx_count, len(pkts))
234
235     def test_ip6_ecmp(self):
236         """IPv6 equal-cost multi-path routing test"""
237
238         src_ip_net = "3ffe:51::1"
239         dst_ip_net = "3ffe:71::1"
240         ip_prefix_len = 64
241
242         self.create_ip_routes(dst_ip_net, ip_prefix_len, is_ipv6=1)
243
244         pkts = self.create_stream(
245             self.pg0,
246             src_ip_net,
247             dst_ip_net,
248             ip_prefix_len,
249             self.pg_if_packet_sizes,
250             ip_l=IPv6,
251         )
252         self.pg0.add_stream(pkts)
253
254         self.pg_enable_capture(self.pg_interfaces)
255         self.pg_start()
256
257         # We expect packets on pg1, pg2 and pg3, but not on pg0
258         rx_count = 0
259         for pg_if in self.pg_interfaces[1:]:
260             capture = pg_if._get_capture(timeout=1)
261             self.assertNotEqual(
262                 len(capture), 0, msg="No packets captured on %s" % pg_if.name
263             )
264             rx_count += self.verify_capture(pg_if, capture, ip_l=IPv6)
265         self.pg0.assert_nothing_captured(remark="IP packets forwarded on pg0")
266
267         # Check that all packets were forwarded via pg1, pg2 and pg3
268         self.assertEqual(rx_count, len(pkts))
269
270
271 if __name__ == "__main__":
272     unittest.main(testRunner=VppTestRunner)