blob: 1d3e872e45ef5bf18cfda205c58dbbcc23b57b95 [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
5import socket
6from ipaddress import IPv4Address, IPv6Address, AddressValueError
7
8from framework import VppTestCase, VppTestRunner
9from 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):
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()
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
95 return str(ip_addr +
96 random.randint(0, 2 ** (ip_max_len - ip_prefix_len) - 2))
97
98 def create_stream(self, src_if, src_ip_start, dst_ip_start,
99 ip_prefix_len, packet_sizes, ip_l=IP):
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 = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
116 ip_l(src=src_ip, dst=dst_ip) /
117 UDP(sport=1234, dport=1234) /
118 Raw(payload))
119 info.data = p.copy()
120 size = random.choice(packet_sizes)
121 self.extend_packet(p, size)
122 pkts.append(p)
123 return pkts
124
125 def verify_capture(self, rx_if, capture, ip_l=IP):
126 """Verify captured input packet stream for defined interface.
127
128 :param VppInterface rx_if: Interface to verify captured packet stream.
129 :param list capture: Captured packet stream.
130 :param Scapy ip_l: Required IP layer - IP or IPv6. (Default is IP.)
131 """
132 self.logger.info("Verifying capture on interface %s" % rx_if.name)
133
134 count = 0
135 host_counters = {}
136 for host_mac in rx_if._hosts_by_mac:
137 host_counters[host_mac] = 0
138
139 for packet in capture:
140 try:
141 ip_received = packet[ip_l]
Paul Vinciguerraeaea4212019-03-06 11:58:06 -0800142 payload_info = self.payload_to_info(packet[Raw])
Jan Geletye8b68a32018-07-13 16:38:04 +0200143 packet_index = payload_info.index
144 ip_sent = self._packet_infos[packet_index].data[ip_l]
145 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
146 (rx_if.name, payload_info.src, packet_index))
147 # Check standard fields
148 self.assertIn(packet.dst, rx_if._hosts_by_mac,
149 "Destination MAC address %s shouldn't be routed "
150 "via interface %s" % (packet.dst, rx_if.name))
151 self.assertEqual(packet.src, rx_if.local_mac)
152 self.assertEqual(ip_received.src, ip_sent.src)
153 self.assertEqual(ip_received.dst, ip_sent.dst)
154 host_counters[packet.dst] += 1
155 self._packet_infos.pop(packet_index)
156
157 except:
158 self.logger.error(ppp("Unexpected or invalid packet:", packet))
159 raise
160
161 # We expect packet routed via all host of pg interface
162 for host_mac in host_counters:
163 nr = host_counters[host_mac]
164 self.assertNotEqual(
165 nr, 0, "No packet routed via host %s" % host_mac)
166 self.logger.info("%u packets routed via host %s of %s interface" %
167 (nr, host_mac, rx_if.name))
168 count += nr
169 self.logger.info("Total amount of %u packets routed via %s interface" %
170 (count, rx_if.name))
171
172 return count
173
174 def create_ip_routes(self, dst_ip_net, dst_prefix_len, is_ipv6=0):
175 """
176 Create IP routes for defined destination IP network.
177
178 :param str dst_ip_net: Destination IP network.
179 :param int dst_prefix_len: IP address prefix length.
180 :param int is_ipv6: 0 if an ip4 route, else ip6
181 """
Jan Geletye8b68a32018-07-13 16:38:04 +0200182
Neale Ranns097fa662018-05-01 05:17:55 -0700183 paths = []
Jan Geletye8b68a32018-07-13 16:38:04 +0200184 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
Neale Ranns097fa662018-05-01 05:17:55 -0700187 paths.append(VppRoutePath(nh_host_ip,
188 pg_if.sw_if_index))
189
190 rip = VppIpRoute(self, dst_ip_net, dst_prefix_len, paths)
191 rip.add_vpp_config()
192 self.logger.info("Route via %s on %s created" %
193 (nh_host_ip, pg_if.name))
Jan Geletye8b68a32018-07-13 16:38:04 +0200194
195 self.logger.debug(self.vapi.ppcli("show ip fib"))
196 self.logger.debug(self.vapi.ppcli("show ip6 fib"))
197
198 def test_ip_ecmp(self):
199 """ IP equal-cost multi-path routing test """
200
201 src_ip_net = '16.0.0.1'
202 dst_ip_net = '32.0.0.1'
203 ip_prefix_len = 24
204
205 self.create_ip_routes(dst_ip_net, ip_prefix_len)
206
207 pkts = self.create_stream(self.pg0, src_ip_net, dst_ip_net,
208 ip_prefix_len, self.pg_if_packet_sizes)
209 self.pg0.add_stream(pkts)
210
211 self.pg_enable_capture(self.pg_interfaces)
212 self.pg_start()
213
214 # We expect packets on pg1, pg2 and pg3, but not on pg0
215 rx_count = 0
216 for pg_if in self.pg_interfaces[1:]:
217 capture = pg_if._get_capture(timeout=1)
218 self.assertNotEqual(
219 len(capture), 0, msg="No packets captured on %s" % pg_if.name)
220 rx_count += self.verify_capture(pg_if, capture)
221 self.pg0.assert_nothing_captured(remark="IP packets forwarded on pg0")
222
223 # Check that all packets were forwarded via pg1, pg2 and pg3
224 self.assertEqual(rx_count, len(pkts))
225
226 def test_ip6_ecmp(self):
227 """ IPv6 equal-cost multi-path routing test """
228
229 src_ip_net = '3ffe:51::1'
230 dst_ip_net = '3ffe:71::1'
231 ip_prefix_len = 64
232
233 self.create_ip_routes(dst_ip_net, ip_prefix_len, is_ipv6=1)
234
235 pkts = self.create_stream(
236 self.pg0, src_ip_net, dst_ip_net,
237 ip_prefix_len, self.pg_if_packet_sizes, ip_l=IPv6)
238 self.pg0.add_stream(pkts)
239
240 self.pg_enable_capture(self.pg_interfaces)
241 self.pg_start()
242
243 # We expect packets on pg1, pg2 and pg3, but not on pg0
244 rx_count = 0
245 for pg_if in self.pg_interfaces[1:]:
246 capture = pg_if._get_capture(timeout=1)
247 self.assertNotEqual(
248 len(capture), 0, msg="No packets captured on %s" % pg_if.name)
249 rx_count += self.verify_capture(pg_if, capture, ip_l=IPv6)
250 self.pg0.assert_nothing_captured(remark="IP packets forwarded on pg0")
251
252 # Check that all packets were forwarded via pg1, pg2 and pg3
253 self.assertEqual(rx_count, len(pkts))
254
255
256if __name__ == '__main__':
257 unittest.main(testRunner=VppTestRunner)