blob: e3ceb594826283e7a13925931fbd2b6e918a1038 [file] [log] [blame]
Jan Geletye8b68a32018-07-13 16:38:04 +02001#!/usr/bin/env python
2
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
15
Paul Vinciguerra1e18eb22018-11-25 16:09:26 -080016try:
17 text_type = unicode
18except NameError:
19 text_type = str
20
Jan Geletye8b68a32018-07-13 16:38:04 +020021#
22# The number of packets to sent.
23#
24N_PKTS_IN_STREAM = 300
25
26
27class TestECMP(VppTestCase):
28 """ Equal-cost multi-path routing Test Case """
29
30 @classmethod
31 def setUpClass(cls):
32 """
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.
36 """
37 super(TestECMP, cls).setUpClass()
38
39 # create 4 pg interfaces
40 cls.create_pg_interfaces(range(4))
41
42 # packet sizes to test
43 cls.pg_if_packet_sizes = [64, 1500, 9018]
44
45 # setup interfaces
46 for i in cls.pg_interfaces:
47 i.admin_up()
48 i.generate_remote_hosts(5)
49 i.config_ip4()
50 i.resolve_arp()
51 i.configure_ipv4_neighbors()
52 i.config_ip6()
53 i.resolve_ndp()
54 i.configure_ipv6_neighbors()
55
56 @classmethod
57 def tearDownClass(cls):
58 if not cls.vpp_dead:
59 for i in cls.pg_interfaces:
60 i.unconfig_ip4()
61 i.unconfig_ip6()
62 i.admin_down()
63
64 super(TestECMP, cls).tearDownClass()
65
66 def setUp(self):
67 super(TestECMP, self).setUp()
68 self.reset_packet_infos()
69
70 def tearDown(self):
71 """
72 Show various debug prints after each test.
73 """
74 super(TestECMP, self).tearDown()
Paul Vinciguerra90cf21b2019-03-13 09:23:05 -070075
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"))
Jan Geletye8b68a32018-07-13 16:38:04 +020079
80 def get_ip_address(self, ip_addr_start, ip_prefix_len):
81 """
82
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.
86 """
87 try:
Paul Vinciguerra1e18eb22018-11-25 16:09:26 -080088 ip_addr = IPv4Address(text_type(ip_addr_start))
Jan Geletye8b68a32018-07-13 16:38:04 +020089 ip_max_len = 32
90 except (AttributeError, AddressValueError):
Paul Vinciguerra1e18eb22018-11-25 16:09:26 -080091 ip_addr = IPv6Address(text_type(ip_addr_start))
Jan Geletye8b68a32018-07-13 16:38:04 +020092 ip_max_len = 128
93
94 return str(ip_addr +
95 random.randint(0, 2 ** (ip_max_len - ip_prefix_len) - 2))
96
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.
100
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.)
107 """
108 pkts = []
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) /
117 Raw(payload))
118 info.data = p.copy()
119 size = random.choice(packet_sizes)
120 self.extend_packet(p, size)
121 pkts.append(p)
122 return pkts
123
124 def verify_capture(self, rx_if, capture, ip_l=IP):
125 """Verify captured input packet stream for defined interface.
126
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.)
130 """
131 self.logger.info("Verifying capture on interface %s" % rx_if.name)
132
133 count = 0
134 host_counters = {}
135 for host_mac in rx_if._hosts_by_mac:
136 host_counters[host_mac] = 0
137
138 for packet in capture:
139 try:
140 ip_received = packet[ip_l]
Paul Vinciguerraeaea4212019-03-06 11:58:06 -0800141 payload_info = self.payload_to_info(packet[Raw])
Jan Geletye8b68a32018-07-13 16:38:04 +0200142 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)
155
156 except:
157 self.logger.error(ppp("Unexpected or invalid packet:", packet))
158 raise
159
160 # We expect packet routed via all host of pg interface
161 for host_mac in host_counters:
162 nr = host_counters[host_mac]
163 self.assertNotEqual(
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))
167 count += nr
168 self.logger.info("Total amount of %u packets routed via %s interface" %
169 (count, rx_if.name))
170
171 return count
172
173 def create_ip_routes(self, dst_ip_net, dst_prefix_len, is_ipv6=0):
174 """
175 Create IP routes for defined destination IP network.
176
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
180 """
181 af = socket.AF_INET if is_ipv6 == 0 else socket.AF_INET6
182 dst_ip = socket.inet_pton(af, dst_ip_net)
183
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(
Ole Troana5b2eec2019-03-11 19:23:25 +0100190 dst_address=dst_ip,
191 dst_address_length=dst_prefix_len,
192 next_hop_address=next_hop_address,
Jan Geletye8b68a32018-07-13 16:38:04 +0200193 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))
197
198 self.logger.debug(self.vapi.ppcli("show ip fib"))
199 self.logger.debug(self.vapi.ppcli("show ip6 fib"))
200
201 def test_ip_ecmp(self):
202 """ IP equal-cost multi-path routing test """
203
204 src_ip_net = '16.0.0.1'
205 dst_ip_net = '32.0.0.1'
206 ip_prefix_len = 24
207
208 self.create_ip_routes(dst_ip_net, ip_prefix_len)
209
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)
213
214 self.pg_enable_capture(self.pg_interfaces)
215 self.pg_start()
216
217 # We expect packets on pg1, pg2 and pg3, but not on pg0
218 rx_count = 0
219 for pg_if in self.pg_interfaces[1:]:
220 capture = pg_if._get_capture(timeout=1)
221 self.assertNotEqual(
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")
225
226 # Check that all packets were forwarded via pg1, pg2 and pg3
227 self.assertEqual(rx_count, len(pkts))
228
229 def test_ip6_ecmp(self):
230 """ IPv6 equal-cost multi-path routing test """
231
232 src_ip_net = '3ffe:51::1'
233 dst_ip_net = '3ffe:71::1'
234 ip_prefix_len = 64
235
236 self.create_ip_routes(dst_ip_net, ip_prefix_len, is_ipv6=1)
237
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)
242
243 self.pg_enable_capture(self.pg_interfaces)
244 self.pg_start()
245
246 # We expect packets on pg1, pg2 and pg3, but not on pg0
247 rx_count = 0
248 for pg_if in self.pg_interfaces[1:]:
249 capture = pg_if._get_capture(timeout=1)
250 self.assertNotEqual(
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")
254
255 # Check that all packets were forwarded via pg1, pg2 and pg3
256 self.assertEqual(rx_count, len(pkts))
257
258
259if __name__ == '__main__':
260 unittest.main(testRunner=VppTestRunner)