vpp_papi_provider: Remove more wrapper functions.
[vpp.git] / test / test_ip_ecmp.py
1 #!/usr/bin/env python
2
3 import unittest
4 import random
5 import socket
6 from ipaddress import IPv4Address, IPv6Address, AddressValueError
7
8 from framework import VppTestCase, VppTestRunner
9 from util import ppp
10
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
16 try:
17     text_type = unicode
18 except NameError:
19     text_type = str
20
21 #
22 # The number of packets to sent.
23 #
24 N_PKTS_IN_STREAM = 300
25
26
27 class 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()
75         if not self.vpp_dead:
76             self.logger.info(self.vapi.ppcli("show ip arp"))
77             self.logger.info(self.vapi.ppcli("show ip6 neighbors"))
78
79     def get_ip_address(self, ip_addr_start, ip_prefix_len):
80         """
81
82         :param str ip_addr_start: Starting IPv4 or IPv6 address.
83         :param int ip_prefix_len: IP address prefix length.
84         :return: Random IPv4 or IPv6 address from required range.
85         """
86         try:
87             ip_addr = IPv4Address(text_type(ip_addr_start))
88             ip_max_len = 32
89         except (AttributeError, AddressValueError):
90             ip_addr = IPv6Address(text_type(ip_addr_start))
91             ip_max_len = 128
92
93         return str(ip_addr +
94                    random.randint(0, 2 ** (ip_max_len - ip_prefix_len) - 2))
95
96     def create_stream(self, src_if, src_ip_start, dst_ip_start,
97                       ip_prefix_len, packet_sizes, ip_l=IP):
98         """Create input packet stream for defined interfaces.
99
100         :param VppInterface src_if: Source Interface for packet stream.
101         :param str src_ip_start: Starting source IPv4 or IPv6 address.
102         :param str dst_ip_start: Starting destination IPv4 or IPv6 address.
103         :param int ip_prefix_len: IP address prefix length.
104         :param list packet_sizes: packet size to test.
105         :param Scapy ip_l: Required IP layer - IP or IPv6. (Default is IP.)
106         """
107         pkts = []
108         for i in range(0, N_PKTS_IN_STREAM):
109             info = self.create_packet_info(src_if, src_if)
110             payload = self.info_to_payload(info)
111             src_ip = self.get_ip_address(src_ip_start, ip_prefix_len)
112             dst_ip = self.get_ip_address(dst_ip_start, ip_prefix_len)
113             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
114                  ip_l(src=src_ip, dst=dst_ip) /
115                  UDP(sport=1234, dport=1234) /
116                  Raw(payload))
117             info.data = p.copy()
118             size = random.choice(packet_sizes)
119             self.extend_packet(p, size)
120             pkts.append(p)
121         return pkts
122
123     def verify_capture(self, rx_if, capture, ip_l=IP):
124         """Verify captured input packet stream for defined interface.
125
126         :param VppInterface rx_if: Interface to verify captured packet stream.
127         :param list capture: Captured packet stream.
128         :param Scapy ip_l: Required IP layer - IP or IPv6. (Default is IP.)
129         """
130         self.logger.info("Verifying capture on interface %s" % rx_if.name)
131
132         count = 0
133         host_counters = {}
134         for host_mac in rx_if._hosts_by_mac:
135             host_counters[host_mac] = 0
136
137         for packet in capture:
138             try:
139                 ip_received = packet[ip_l]
140                 payload_info = self.payload_to_info(packet[Raw])
141                 packet_index = payload_info.index
142                 ip_sent = self._packet_infos[packet_index].data[ip_l]
143                 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
144                                   (rx_if.name, payload_info.src, packet_index))
145                 # Check standard fields
146                 self.assertIn(packet.dst, rx_if._hosts_by_mac,
147                               "Destination MAC address %s shouldn't be routed "
148                               "via interface %s" % (packet.dst, rx_if.name))
149                 self.assertEqual(packet.src, rx_if.local_mac)
150                 self.assertEqual(ip_received.src, ip_sent.src)
151                 self.assertEqual(ip_received.dst, ip_sent.dst)
152                 host_counters[packet.dst] += 1
153                 self._packet_infos.pop(packet_index)
154
155             except:
156                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
157                 raise
158
159         # We expect packet routed via all host of pg interface
160         for host_mac in host_counters:
161             nr = host_counters[host_mac]
162             self.assertNotEqual(
163                 nr, 0, "No packet routed via host %s" % host_mac)
164             self.logger.info("%u packets routed via host %s of %s interface" %
165                              (nr, host_mac, rx_if.name))
166             count += nr
167         self.logger.info("Total amount of %u packets routed via %s interface" %
168                          (count, rx_if.name))
169
170         return count
171
172     def create_ip_routes(self, dst_ip_net, dst_prefix_len, is_ipv6=0):
173         """
174         Create IP routes for defined destination IP network.
175
176         :param str dst_ip_net: Destination IP network.
177         :param int dst_prefix_len: IP address prefix length.
178         :param int is_ipv6: 0 if an ip4 route, else ip6
179         """
180         af = socket.AF_INET if is_ipv6 == 0 else socket.AF_INET6
181         dst_ip = socket.inet_pton(af, dst_ip_net)
182
183         for pg_if in self.pg_interfaces[1:]:
184             for nh_host in pg_if.remote_hosts:
185                 nh_host_ip = nh_host.ip4 if is_ipv6 == 0 else nh_host.ip6
186                 next_hop_address = socket.inet_pton(af, nh_host_ip)
187                 next_hop_sw_if_index = pg_if.sw_if_index
188                 self.vapi.ip_add_del_route(
189                     dst_address=dst_ip,
190                     dst_address_length=dst_prefix_len,
191                     next_hop_address=next_hop_address,
192                     next_hop_sw_if_index=next_hop_sw_if_index,
193                     is_ipv6=is_ipv6, is_multipath=1)
194                 self.logger.info("Route via %s on %s created" %
195                                  (nh_host_ip, pg_if.name))
196
197         self.logger.debug(self.vapi.ppcli("show ip fib"))
198         self.logger.debug(self.vapi.ppcli("show ip6 fib"))
199
200     def test_ip_ecmp(self):
201         """ IP equal-cost multi-path routing test """
202
203         src_ip_net = '16.0.0.1'
204         dst_ip_net = '32.0.0.1'
205         ip_prefix_len = 24
206
207         self.create_ip_routes(dst_ip_net, ip_prefix_len)
208
209         pkts = self.create_stream(self.pg0, src_ip_net, dst_ip_net,
210                                   ip_prefix_len, self.pg_if_packet_sizes)
211         self.pg0.add_stream(pkts)
212
213         self.pg_enable_capture(self.pg_interfaces)
214         self.pg_start()
215
216         # We expect packets on pg1, pg2 and pg3, but not on pg0
217         rx_count = 0
218         for pg_if in self.pg_interfaces[1:]:
219             capture = pg_if._get_capture(timeout=1)
220             self.assertNotEqual(
221                 len(capture), 0, msg="No packets captured on %s" % pg_if.name)
222             rx_count += self.verify_capture(pg_if, capture)
223         self.pg0.assert_nothing_captured(remark="IP packets forwarded on pg0")
224
225         # Check that all packets were forwarded via pg1, pg2 and pg3
226         self.assertEqual(rx_count, len(pkts))
227
228     def test_ip6_ecmp(self):
229         """ IPv6 equal-cost multi-path routing test """
230
231         src_ip_net = '3ffe:51::1'
232         dst_ip_net = '3ffe:71::1'
233         ip_prefix_len = 64
234
235         self.create_ip_routes(dst_ip_net, ip_prefix_len, is_ipv6=1)
236
237         pkts = self.create_stream(
238             self.pg0, src_ip_net, dst_ip_net,
239             ip_prefix_len, self.pg_if_packet_sizes, ip_l=IPv6)
240         self.pg0.add_stream(pkts)
241
242         self.pg_enable_capture(self.pg_interfaces)
243         self.pg_start()
244
245         # We expect packets on pg1, pg2 and pg3, but not on pg0
246         rx_count = 0
247         for pg_if in self.pg_interfaces[1:]:
248             capture = pg_if._get_capture(timeout=1)
249             self.assertNotEqual(
250                 len(capture), 0, msg="No packets captured on %s" % pg_if.name)
251             rx_count += self.verify_capture(pg_if, capture, ip_l=IPv6)
252         self.pg0.assert_nothing_captured(remark="IP packets forwarded on pg0")
253
254         # Check that all packets were forwarded via pg1, pg2 and pg3
255         self.assertEqual(rx_count, len(pkts))
256
257
258 if __name__ == '__main__':
259     unittest.main(testRunner=VppTestRunner)