5 from ipaddress import IPv4Address, IPv6Address, AddressValueError
7 from framework import VppTestCase
8 from asfframework import VppTestRunner
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
23 # The number of packets to sent.
25 N_PKTS_IN_STREAM = 300
28 class TestECMP(VppTestCase):
29 """Equal-cost multi-path routing Test Case"""
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.
38 super(TestECMP, cls).setUpClass()
40 # create 4 pg interfaces
41 cls.create_pg_interfaces(range(4))
43 # packet sizes to test
44 cls.pg_if_packet_sizes = [64, 1500, 9018]
47 for i in cls.pg_interfaces:
49 i.generate_remote_hosts(5)
52 i.configure_ipv4_neighbors()
55 i.configure_ipv6_neighbors()
58 def tearDownClass(cls):
60 for i in cls.pg_interfaces:
65 super(TestECMP, cls).tearDownClass()
68 super(TestECMP, self).setUp()
69 self.reset_packet_infos()
73 Show various debug prints after each test.
75 super(TestECMP, self).tearDown()
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"))
81 def get_ip_address(self, ip_addr_start, ip_prefix_len):
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.
89 ip_addr = IPv4Address(text_type(ip_addr_start))
91 except (AttributeError, AddressValueError):
92 ip_addr = IPv6Address(text_type(ip_addr_start))
95 return str(ip_addr + random.randint(0, 2 ** (ip_max_len - ip_prefix_len) - 2))
98 self, src_if, src_ip_start, dst_ip_start, ip_prefix_len, packet_sizes, ip_l=IP
100 """Create input packet stream for defined interfaces.
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.)
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)
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)
122 size = random.choice(packet_sizes)
123 self.extend_packet(p, size)
127 def verify_capture(self, rx_if, capture, ip_l=IP):
128 """Verify captured input packet stream for defined interface.
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.)
134 self.logger.info("Verifying capture on interface %s" % rx_if.name)
138 for host_mac in rx_if._hosts_by_mac:
139 host_counters[host_mac] = 0
141 for packet in capture:
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]
148 "Got packet on port %s: src=%u (id=%u)"
149 % (rx_if.name, payload_info.src, packet_index)
151 # Check standard fields
155 "Destination MAC address %s shouldn't be routed "
156 "via interface %s" % (packet.dst, rx_if.name),
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)
165 self.logger.error(ppp("Unexpected or invalid packet:", packet))
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)
173 "%u packets routed via host %s of %s interface"
174 % (nr, host_mac, rx_if.name)
178 "Total amount of %u packets routed via %s interface" % (count, rx_if.name)
183 def create_ip_routes(self, dst_ip_net, dst_prefix_len, is_ipv6=0):
185 Create IP routes for defined destination IP network.
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
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))
198 rip = VppIpRoute(self, dst_ip_net, dst_prefix_len, paths)
200 self.logger.info("Route via %s on %s created" % (nh_host_ip, pg_if.name))
202 self.logger.debug(self.vapi.ppcli("show ip fib"))
203 self.logger.debug(self.vapi.ppcli("show ip6 fib"))
205 def test_ip_ecmp(self):
206 """IP equal-cost multi-path routing test"""
208 src_ip_net = "16.0.0.1"
209 dst_ip_net = "32.0.0.1"
212 self.create_ip_routes(dst_ip_net, ip_prefix_len)
214 pkts = self.create_stream(
215 self.pg0, src_ip_net, dst_ip_net, ip_prefix_len, self.pg_if_packet_sizes
217 self.pg0.add_stream(pkts)
219 self.pg_enable_capture(self.pg_interfaces)
222 # We expect packets on pg1, pg2 and pg3, but not on pg0
224 for pg_if in self.pg_interfaces[1:]:
225 capture = pg_if._get_capture(timeout=1)
227 len(capture), 0, msg="No packets captured on %s" % pg_if.name
229 rx_count += self.verify_capture(pg_if, capture)
230 self.pg0.assert_nothing_captured(remark="IP packets forwarded on pg0")
232 # Check that all packets were forwarded via pg1, pg2 and pg3
233 self.assertEqual(rx_count, len(pkts))
235 def test_ip6_ecmp(self):
236 """IPv6 equal-cost multi-path routing test"""
238 src_ip_net = "3ffe:51::1"
239 dst_ip_net = "3ffe:71::1"
242 self.create_ip_routes(dst_ip_net, ip_prefix_len, is_ipv6=1)
244 pkts = self.create_stream(
249 self.pg_if_packet_sizes,
252 self.pg0.add_stream(pkts)
254 self.pg_enable_capture(self.pg_interfaces)
257 # We expect packets on pg1, pg2 and pg3, but not on pg0
259 for pg_if in self.pg_interfaces[1:]:
260 capture = pg_if._get_capture(timeout=1)
262 len(capture), 0, msg="No packets captured on %s" % pg_if.name
264 rx_count += self.verify_capture(pg_if, capture, ip_l=IPv6)
265 self.pg0.assert_nothing_captured(remark="IP packets forwarded on pg0")
267 # Check that all packets were forwarded via pg1, pg2 and pg3
268 self.assertEqual(rx_count, len(pkts))
271 if __name__ == "__main__":
272 unittest.main(testRunner=VppTestRunner)