6 from ipaddress import IPv4Address, IPv6Address, AddressValueError
8 from framework import VppTestCase, 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
22 # The number of packets to sent.
24 N_PKTS_IN_STREAM = 300
27 class TestECMP(VppTestCase):
28 """ Equal-cost multi-path routing Test Case """
33 Perform standard class setup (defined by class method setUpClass in
34 class VppTestCase) before running the test case, set test case related
35 variables and configure VPP.
37 super(TestECMP, cls).setUpClass()
39 # create 4 pg interfaces
40 cls.create_pg_interfaces(range(4))
42 # packet sizes to test
43 cls.pg_if_packet_sizes = [64, 1500, 9018]
46 for i in cls.pg_interfaces:
48 i.generate_remote_hosts(5)
51 i.configure_ipv4_neighbors()
54 i.configure_ipv6_neighbors()
57 def tearDownClass(cls):
59 for i in cls.pg_interfaces:
64 super(TestECMP, cls).tearDownClass()
67 super(TestECMP, self).setUp()
68 self.reset_packet_infos()
72 Show various debug prints after each test.
74 super(TestECMP, self).tearDown()
76 def show_commands_at_teardown(self):
77 self.logger.info(self.vapi.ppcli("show ip arp"))
78 self.logger.info(self.vapi.ppcli("show ip6 neighbors"))
80 def get_ip_address(self, ip_addr_start, ip_prefix_len):
83 :param str ip_addr_start: Starting IPv4 or IPv6 address.
84 :param int ip_prefix_len: IP address prefix length.
85 :return: Random IPv4 or IPv6 address from required range.
88 ip_addr = IPv4Address(text_type(ip_addr_start))
90 except (AttributeError, AddressValueError):
91 ip_addr = IPv6Address(text_type(ip_addr_start))
95 random.randint(0, 2 ** (ip_max_len - ip_prefix_len) - 2))
97 def create_stream(self, src_if, src_ip_start, dst_ip_start,
98 ip_prefix_len, packet_sizes, ip_l=IP):
99 """Create input packet stream for defined interfaces.
101 :param VppInterface src_if: Source Interface for packet stream.
102 :param str src_ip_start: Starting source IPv4 or IPv6 address.
103 :param str dst_ip_start: Starting destination IPv4 or IPv6 address.
104 :param int ip_prefix_len: IP address prefix length.
105 :param list packet_sizes: packet size to test.
106 :param Scapy ip_l: Required IP layer - IP or IPv6. (Default is IP.)
109 for i in range(0, N_PKTS_IN_STREAM):
110 info = self.create_packet_info(src_if, src_if)
111 payload = self.info_to_payload(info)
112 src_ip = self.get_ip_address(src_ip_start, ip_prefix_len)
113 dst_ip = self.get_ip_address(dst_ip_start, ip_prefix_len)
114 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
115 ip_l(src=src_ip, dst=dst_ip) /
116 UDP(sport=1234, dport=1234) /
119 size = random.choice(packet_sizes)
120 self.extend_packet(p, size)
124 def verify_capture(self, rx_if, capture, ip_l=IP):
125 """Verify captured input packet stream for defined interface.
127 :param VppInterface rx_if: Interface to verify captured packet stream.
128 :param list capture: Captured packet stream.
129 :param Scapy ip_l: Required IP layer - IP or IPv6. (Default is IP.)
131 self.logger.info("Verifying capture on interface %s" % rx_if.name)
135 for host_mac in rx_if._hosts_by_mac:
136 host_counters[host_mac] = 0
138 for packet in capture:
140 ip_received = packet[ip_l]
141 payload_info = self.payload_to_info(packet[Raw])
142 packet_index = payload_info.index
143 ip_sent = self._packet_infos[packet_index].data[ip_l]
144 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
145 (rx_if.name, payload_info.src, packet_index))
146 # Check standard fields
147 self.assertIn(packet.dst, rx_if._hosts_by_mac,
148 "Destination MAC address %s shouldn't be routed "
149 "via interface %s" % (packet.dst, rx_if.name))
150 self.assertEqual(packet.src, rx_if.local_mac)
151 self.assertEqual(ip_received.src, ip_sent.src)
152 self.assertEqual(ip_received.dst, ip_sent.dst)
153 host_counters[packet.dst] += 1
154 self._packet_infos.pop(packet_index)
157 self.logger.error(ppp("Unexpected or invalid packet:", packet))
160 # We expect packet routed via all host of pg interface
161 for host_mac in host_counters:
162 nr = host_counters[host_mac]
164 nr, 0, "No packet routed via host %s" % host_mac)
165 self.logger.info("%u packets routed via host %s of %s interface" %
166 (nr, host_mac, rx_if.name))
168 self.logger.info("Total amount of %u packets routed via %s interface" %
173 def create_ip_routes(self, dst_ip_net, dst_prefix_len, is_ipv6=0):
175 Create IP routes for defined destination IP network.
177 :param str dst_ip_net: Destination IP network.
178 :param int dst_prefix_len: IP address prefix length.
179 :param int is_ipv6: 0 if an ip4 route, else ip6
181 af = socket.AF_INET if is_ipv6 == 0 else socket.AF_INET6
182 dst_ip = socket.inet_pton(af, dst_ip_net)
184 for pg_if in self.pg_interfaces[1:]:
185 for nh_host in pg_if.remote_hosts:
186 nh_host_ip = nh_host.ip4 if is_ipv6 == 0 else nh_host.ip6
187 next_hop_address = socket.inet_pton(af, nh_host_ip)
188 next_hop_sw_if_index = pg_if.sw_if_index
189 self.vapi.ip_add_del_route(
191 dst_address_length=dst_prefix_len,
192 next_hop_address=next_hop_address,
193 next_hop_sw_if_index=next_hop_sw_if_index,
194 is_ipv6=is_ipv6, is_multipath=1)
195 self.logger.info("Route via %s on %s created" %
196 (nh_host_ip, pg_if.name))
198 self.logger.debug(self.vapi.ppcli("show ip fib"))
199 self.logger.debug(self.vapi.ppcli("show ip6 fib"))
201 def test_ip_ecmp(self):
202 """ IP equal-cost multi-path routing test """
204 src_ip_net = '16.0.0.1'
205 dst_ip_net = '32.0.0.1'
208 self.create_ip_routes(dst_ip_net, ip_prefix_len)
210 pkts = self.create_stream(self.pg0, src_ip_net, dst_ip_net,
211 ip_prefix_len, self.pg_if_packet_sizes)
212 self.pg0.add_stream(pkts)
214 self.pg_enable_capture(self.pg_interfaces)
217 # We expect packets on pg1, pg2 and pg3, but not on pg0
219 for pg_if in self.pg_interfaces[1:]:
220 capture = pg_if._get_capture(timeout=1)
222 len(capture), 0, msg="No packets captured on %s" % pg_if.name)
223 rx_count += self.verify_capture(pg_if, capture)
224 self.pg0.assert_nothing_captured(remark="IP packets forwarded on pg0")
226 # Check that all packets were forwarded via pg1, pg2 and pg3
227 self.assertEqual(rx_count, len(pkts))
229 def test_ip6_ecmp(self):
230 """ IPv6 equal-cost multi-path routing test """
232 src_ip_net = '3ffe:51::1'
233 dst_ip_net = '3ffe:71::1'
236 self.create_ip_routes(dst_ip_net, ip_prefix_len, is_ipv6=1)
238 pkts = self.create_stream(
239 self.pg0, src_ip_net, dst_ip_net,
240 ip_prefix_len, self.pg_if_packet_sizes, ip_l=IPv6)
241 self.pg0.add_stream(pkts)
243 self.pg_enable_capture(self.pg_interfaces)
246 # We expect packets on pg1, pg2 and pg3, but not on pg0
248 for pg_if in self.pg_interfaces[1:]:
249 capture = pg_if._get_capture(timeout=1)
251 len(capture), 0, msg="No packets captured on %s" % pg_if.name)
252 rx_count += self.verify_capture(pg_if, capture, ip_l=IPv6)
253 self.pg0.assert_nothing_captured(remark="IP packets forwarded on pg0")
255 # Check that all packets were forwarded via pg1, pg2 and pg3
256 self.assertEqual(rx_count, len(pkts))
259 if __name__ == '__main__':
260 unittest.main(testRunner=VppTestRunner)