blob: 360959baaaa23caf391473b30abacc9fc4e1e79f [file] [log] [blame]
Renato Botelho do Coutoead1e532019-10-31 13:31:07 -05001#!/usr/bin/env python3
Jan Geletye8b68a32018-07-13 16:38:04 +02002
3import unittest
4import random
Jan Geletye8b68a32018-07-13 16:38:04 +02005from ipaddress import IPv4Address, IPv6Address, AddressValueError
6
Dave Wallace8800f732023-08-31 00:47:44 -04007from framework import VppTestCase
8from asfframework import VppTestRunner
Jan Geletye8b68a32018-07-13 16:38:04 +02009from util import ppp
10
11from scapy.packet import Raw
12from scapy.layers.l2 import Ether
13from scapy.layers.inet import IP, UDP
14from scapy.layers.inet6 import IPv6
Neale Ranns097fa662018-05-01 05:17:55 -070015from vpp_ip_route import VppIpRoute, VppRoutePath
Jan Geletye8b68a32018-07-13 16:38:04 +020016
Paul Vinciguerra1e18eb22018-11-25 16:09:26 -080017try:
18 text_type = unicode
19except NameError:
20 text_type = str
21
Jan Geletye8b68a32018-07-13 16:38:04 +020022#
23# The number of packets to sent.
24#
25N_PKTS_IN_STREAM = 300
26
27
28class TestECMP(VppTestCase):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020029 """Equal-cost multi-path routing Test Case"""
Jan Geletye8b68a32018-07-13 16:38:04 +020030
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()
Paul Vinciguerra90cf21b2019-03-13 09:23:05 -070076
77 def show_commands_at_teardown(self):
Neale Rannscbe25aa2019-09-30 10:53:31 +000078 self.logger.info(self.vapi.ppcli("show ip4 neighbors"))
Paul Vinciguerra90cf21b2019-03-13 09:23:05 -070079 self.logger.info(self.vapi.ppcli("show ip6 neighbors"))
Jan Geletye8b68a32018-07-13 16:38:04 +020080
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:
Paul Vinciguerra1e18eb22018-11-25 16:09:26 -080089 ip_addr = IPv4Address(text_type(ip_addr_start))
Jan Geletye8b68a32018-07-13 16:38:04 +020090 ip_max_len = 32
91 except (AttributeError, AddressValueError):
Paul Vinciguerra1e18eb22018-11-25 16:09:26 -080092 ip_addr = IPv6Address(text_type(ip_addr_start))
Jan Geletye8b68a32018-07-13 16:38:04 +020093 ip_max_len = 128
94
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020095 return str(ip_addr + random.randint(0, 2 ** (ip_max_len - ip_prefix_len) - 2))
Jan Geletye8b68a32018-07-13 16:38:04 +020096
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020097 def create_stream(
98 self, src_if, src_ip_start, dst_ip_start, ip_prefix_len, packet_sizes, ip_l=IP
99 ):
Jan Geletye8b68a32018-07-13 16:38:04 +0200100 """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)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200115 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 )
Jan Geletye8b68a32018-07-13 16:38:04 +0200121 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]
Paul Vinciguerraeaea4212019-03-06 11:58:06 -0800144 payload_info = self.payload_to_info(packet[Raw])
Jan Geletye8b68a32018-07-13 16:38:04 +0200145 packet_index = payload_info.index
146 ip_sent = self._packet_infos[packet_index].data[ip_l]
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200147 self.logger.debug(
148 "Got packet on port %s: src=%u (id=%u)"
149 % (rx_if.name, payload_info.src, packet_index)
150 )
Jan Geletye8b68a32018-07-13 16:38:04 +0200151 # Check standard fields
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200152 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 )
Jan Geletye8b68a32018-07-13 16:38:04 +0200158 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]
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200171 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 )
Jan Geletye8b68a32018-07-13 16:38:04 +0200176 count += nr
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200177 self.logger.info(
178 "Total amount of %u packets routed via %s interface" % (count, rx_if.name)
179 )
Jan Geletye8b68a32018-07-13 16:38:04 +0200180
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 """
Jan Geletye8b68a32018-07-13 16:38:04 +0200191
Neale Ranns097fa662018-05-01 05:17:55 -0700192 paths = []
Jan Geletye8b68a32018-07-13 16:38:04 +0200193 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
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200196 paths.append(VppRoutePath(nh_host_ip, pg_if.sw_if_index))
Neale Ranns097fa662018-05-01 05:17:55 -0700197
198 rip = VppIpRoute(self, dst_ip_net, dst_prefix_len, paths)
199 rip.add_vpp_config()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200200 self.logger.info("Route via %s on %s created" % (nh_host_ip, pg_if.name))
Jan Geletye8b68a32018-07-13 16:38:04 +0200201
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):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200206 """IP equal-cost multi-path routing test"""
Jan Geletye8b68a32018-07-13 16:38:04 +0200207
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200208 src_ip_net = "16.0.0.1"
209 dst_ip_net = "32.0.0.1"
Jan Geletye8b68a32018-07-13 16:38:04 +0200210 ip_prefix_len = 24
211
212 self.create_ip_routes(dst_ip_net, ip_prefix_len)
213
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200214 pkts = self.create_stream(
215 self.pg0, src_ip_net, dst_ip_net, ip_prefix_len, self.pg_if_packet_sizes
216 )
Jan Geletye8b68a32018-07-13 16:38:04 +0200217 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(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200227 len(capture), 0, msg="No packets captured on %s" % pg_if.name
228 )
Jan Geletye8b68a32018-07-13 16:38:04 +0200229 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):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200236 """IPv6 equal-cost multi-path routing test"""
Jan Geletye8b68a32018-07-13 16:38:04 +0200237
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200238 src_ip_net = "3ffe:51::1"
239 dst_ip_net = "3ffe:71::1"
Jan Geletye8b68a32018-07-13 16:38:04 +0200240 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(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200245 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 )
Jan Geletye8b68a32018-07-13 16:38:04 +0200252 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(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200262 len(capture), 0, msg="No packets captured on %s" % pg_if.name
263 )
Jan Geletye8b68a32018-07-13 16:38:04 +0200264 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
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200271if __name__ == "__main__":
Jan Geletye8b68a32018-07-13 16:38:04 +0200272 unittest.main(testRunner=VppTestRunner)