From 32e1c010b0c34fd0984f7fc45fae648a182025c5 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Tue, 22 Nov 2016 17:07:28 +0000 Subject: IP Multicast FIB (mfib) - IPv[46] mfib tables with support for (*,G/m), (*,G) and (S,G) exact and longest prefix match - Replication represented via a new replicate DPO. - RPF configuration and data-plane checking - data-plane signals sent to listening control planes. The functions of multicast forwarding entries differ from their unicast conterparts, so we introduce a new mfib_table_t and mfib_entry_t objects. However, we re-use the fib_path_list to resolve and build the entry's output list. the fib_path_list provides the service to construct a replicate DPO for multicast. 'make tests' is added to with two new suites; TEST=mfib, this is invocation of the CLI command 'test mfib' which deals with many path add/remove, flag set/unset scenarios, TEST=ip-mcast, data-plane forwarding tests. Updated applications to use the new MIFB functions; - IPv6 NS/RA. - DHCPv6 unit tests for these are undated accordingly. Change-Id: I49ec37b01f1b170335a5697541c8fd30e6d3a961 Signed-off-by: Neale Ranns --- test/test_dhcp.py | 16 -- test/test_ip6.py | 131 ++++------ test/test_ip_mcast.py | 612 ++++++++++++++++++++++++++++++++++++++++++++++ test/test_mfib.py | 23 ++ test/vpp_interface.py | 3 +- test/vpp_ip_route.py | 101 +++++++- test/vpp_papi_provider.py | 34 ++- 7 files changed, 823 insertions(+), 97 deletions(-) create mode 100644 test/test_ip_mcast.py create mode 100644 test/test_mfib.py (limited to 'test') diff --git a/test/test_dhcp.py b/test/test_dhcp.py index bdff679c..04ab2e11 100644 --- a/test/test_dhcp.py +++ b/test/test_dhcp.py @@ -4,8 +4,6 @@ import unittest import socket from framework import VppTestCase, VppTestRunner -from vpp_ip_route import IpRoute, RoutePath -from vpp_lo_interface import VppLoInterface from scapy.layers.l2 import Ether, getmacbyip from scapy.layers.inet import IP, UDP, ICMP @@ -482,17 +480,6 @@ class TestDHCP(VppTestCase): server_addr_vrf1 = self.pg1.remote_ip6n src_addr_vrf1 = self.pg1.local_ip6n - # - # Add the Route to receive the DHCP packets - # - route_dhcp_vrf0 = IpRoute(self, dhcp_solicit_dst, 128, - [], is_local=1, is_ip6=1) - route_dhcp_vrf0.add_vpp_config() - route_dhcp_vrf1 = IpRoute(self, dhcp_solicit_dst, 128, - [], is_local=1, is_ip6=1, - table_id=1) - route_dhcp_vrf1.add_vpp_config() - dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst)) p_solicit_vrf0 = (Ether(dst=dmac, src=self.pg2.remote_mac) / IPv6(src=dhcp_solicit_src_vrf0, @@ -732,8 +719,5 @@ class TestDHCP(VppTestCase): is_ipv6=1, is_add=0) - route_dhcp_vrf0.remove_vpp_config() - route_dhcp_vrf1.remove_vpp_config() - if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/test_ip6.py b/test/test_ip6.py index ea669b70..e188970a 100644 --- a/test/test_ip6.py +++ b/test/test_ip6.py @@ -5,6 +5,7 @@ import socket from framework import VppTestCase, VppTestRunner from vpp_sub_interface import VppSubInterface, VppDot1QSubint +from vpp_pg_interface import is_ipv6_misc from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q @@ -12,10 +13,9 @@ from scapy.layers.inet6 import IPv6, UDP, ICMPv6ND_NS, ICMPv6ND_RS, \ ICMPv6ND_RA, ICMPv6NDOptSrcLLAddr, getmacbyip6, ICMPv6MRD_Solicitation from util import ppp from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ptop, in6_islladdr, \ - in6_mactoifaceid + in6_mactoifaceid, in6_ismaddr from scapy.utils import inet_pton, inet_ntop - def mk_ll_addr(mac): euid = in6_mactoifaceid(mac) addr = "fe80::" + euid @@ -287,28 +287,39 @@ class TestIPv6(VppTestCase): self.send_and_assert_no_replies(self.pg0, pkts, "No response to NS for unknown target") - def send_and_expect_ra(self, intf, pkts, remark, src_ip=None): - if not src_ip: - src_ip = intf.remote_ip6 - intf.add_stream(pkts) - self.pg0.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - rx = intf.get_capture(1) + def validate_ra(self, intf, rx, dst_ip=None): + if not dst_ip: + dst_ip = intf.remote_ip6 - self.assertEqual(len(rx), 1) - rx = rx[0] + # unicasted packets must come to the unicast mac + self.assertEqual(rx[Ether].dst, intf.remote_mac) + + # and from the router's MAC + self.assertEqual(rx[Ether].src, intf.local_mac) # the rx'd RA should be addressed to the sender's source self.assertTrue(rx.haslayer(ICMPv6ND_RA)) self.assertEqual(in6_ptop(rx[IPv6].dst), - in6_ptop(src_ip)) + in6_ptop(dst_ip)) # and come from the router's link local self.assertTrue(in6_islladdr(rx[IPv6].src)) self.assertEqual(in6_ptop(rx[IPv6].src), in6_ptop(mk_ll_addr(intf.local_mac))) + + def send_and_expect_ra(self, intf, pkts, remark, dst_ip=None, + filter_out_fn=is_ipv6_misc): + intf.add_stream(pkts) + self.pg0.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + rx = intf.get_capture(1, filter_out_fn=filter_out_fn) + + self.assertEqual(len(rx), 1) + rx = rx[0] + self.validate_ra(intf, rx, dst_ip) + def test_rs(self): """ IPv6 Router Solicitation Exceptions @@ -319,6 +330,9 @@ class TestIPv6(VppTestCase): # Before we begin change the IPv6 RA responses to use the unicast # address - that way we will not confuse them with the periodic # RAs which go to the mcast address + # Sit and wait for the first periodic RA. + # + # TODO # self.pg0.ip6_ra_config(send_unicast=1) @@ -365,8 +379,23 @@ class TestIPv6(VppTestCase): IPv6(dst=self.pg0.local_ip6, src=ll) / ICMPv6ND_RS()) pkts = [p] - self.send_and_expect_ra( - self.pg0, pkts, "RS sourced from link-local", src_ip=ll) + self.send_and_expect_ra(self.pg0, pkts, + "RS sourced from link-local", + dst_ip=ll) + + # + # Send the RS multicast + # + self.pg0.ip6_ra_config(send_unicast=1) + dmac = in6_getnsmac(inet_pton(socket.AF_INET6, "ff02::2")) + ll = mk_ll_addr(self.pg0.remote_mac) + p = (Ether(dst=dmac, src=self.pg0.remote_mac) / + IPv6(dst="ff02::2", src=ll) / + ICMPv6ND_RS()) + pkts = [p] + self.send_and_expect_ra(self.pg0, pkts, + "RS sourced from link-local", + dst_ip=ll) # # Source from the unspecified address ::. This happens when the RS @@ -376,74 +405,20 @@ class TestIPv6(VppTestCase): # If we happen to pick up the periodic RA at this point then so be it, # it's not an error. # - self.pg0.ip6_ra_config(send_unicast=1) - p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / - IPv6(dst=self.pg0.local_ip6, src="::") / + self.pg0.ip6_ra_config(send_unicast=1, suppress=1) + p = (Ether(dst=dmac, src=self.pg0.remote_mac) / + IPv6(dst="ff02::2", src="::") / ICMPv6ND_RS()) pkts = [p] - - self.pg0.add_stream(pkts) - self.pg0.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - capture = self.pg0.get_capture(1, filter_out_fn=None) - found = 0 - for rx in capture: - if (rx.haslayer(ICMPv6ND_RA)): - # and come from the router's link local - self.assertTrue(in6_islladdr(rx[IPv6].src)) - self.assertEqual(in6_ptop(rx[IPv6].src), - in6_ptop(mk_ll_addr(self.pg0.local_mac))) - # sent to the all hosts mcast - self.assertEqual(in6_ptop(rx[IPv6].dst), "ff02::1") - - found = 1 - self.assertTrue(found) - - @unittest.skip("Unsupported") - def test_mrs(self): - """ IPv6 Multicast Router Solicitation Exceptions - - Test scenario: - """ - - # - # An RS from a link source address - # - expect an RA in return - # - nsma = in6_getnsma(inet_pton(socket.AF_INET6, self.pg0.local_ip6)) - d = inet_ntop(socket.AF_INET6, nsma) - - p = (Ether(dst=getmacbyip6("ff02::2")) / - IPv6(dst=d, src=self.pg0.remote_ip6) / - ICMPv6MRD_Solicitation()) - pkts = [p] - - self.pg0.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - self.pg0.assert_nothing_captured( - remark="No response to NS source by address not on sub-net") + self.send_and_expect_ra(self.pg0, pkts, + "RS sourced from unspecified", + dst_ip="ff02::1", + filter_out_fn=None) # - # An RS from a non link source address + # Reset the periodic advertisements back to default values # - nsma = in6_getnsma(inet_pton(socket.AF_INET6, self.pg0.local_ip6)) - d = inet_ntop(socket.AF_INET6, nsma) - - p = (Ether(dst=getmacbyip6("ff02::2")) / - IPv6(dst=d, src="2002::2") / - ICMPv6MRD_Solicitation()) - pkts = [p] - - self.send_and_assert_no_replies(self.pg0, pkts, - "RA rate limited") - self.pg0.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - self.pg0.assert_nothing_captured( - remark="No response to NS source by address not on sub-net") - + self.pg0.ip6_ra_config(no=1, suppress=1, send_unicast=0) if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/test_ip_mcast.py b/test/test_ip_mcast.py new file mode 100644 index 00000000..028853d2 --- /dev/null +++ b/test/test_ip_mcast.py @@ -0,0 +1,612 @@ +#!/usr/bin/env python + +import unittest + +from framework import VppTestCase, VppTestRunner +from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint +from vpp_ip_route import IpMRoute, MRoutePath, MFibSignal + +from scapy.packet import Raw +from scapy.layers.l2 import Ether +from scapy.layers.inet import IP, UDP, getmacbyip +from scapy.layers.inet6 import IPv6, getmacbyip6 +from util import ppp + + +class MRouteItfFlags: + MFIB_ITF_FLAG_NONE = 0 + MFIB_ITF_FLAG_NEGATE_SIGNAL = 1 + MFIB_ITF_FLAG_ACCEPT = 2 + MFIB_ITF_FLAG_FORWARD = 4 + MFIB_ITF_FLAG_SIGNAL_PRESENT = 8 + MFIB_ITF_FLAG_INTERNAL_COPY = 16 + + +class MRouteEntryFlags: + MFIB_ENTRY_FLAG_NONE = 0 + MFIB_ENTRY_FLAG_SIGNAL = 1 + MFIB_ENTRY_FLAG_DROP = 2 + MFIB_ENTRY_FLAG_CONNECTED = 4 + MFIB_ENTRY_FLAG_INHERIT_ACCEPT = 8 + + +class TestIPMcast(VppTestCase): + """ IP Multicast Test Case """ + + def setUp(self): + super(TestIPMcast, self).setUp() + + # create 4 pg interfaces + self.create_pg_interfaces(range(4)) + + # setup interfaces + for i in self.pg_interfaces: + i.admin_up() + i.config_ip4() + i.config_ip6() + i.resolve_arp() + i.resolve_ndp() + + def create_stream_ip4(self, src_if, src_ip, dst_ip): + pkts = [] + for i in range(0, 65): + info = self.create_packet_info(src_if, src_if) + payload = self.info_to_payload(info) + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + IP(src=src_ip, dst=dst_ip) / + UDP(sport=1234, dport=1234) / + Raw(payload)) + info.data = p.copy() + pkts.append(p) + return pkts + + def create_stream_ip6(self, src_if, src_ip, dst_ip): + pkts = [] + for i in range(0, 65): + info = self.create_packet_info(src_if, src_if) + payload = self.info_to_payload(info) + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + IPv6(src=src_ip, dst=dst_ip) / + UDP(sport=1234, dport=1234) / + Raw(payload)) + info.data = p.copy() + pkts.append(p) + return pkts + + def verify_filter(self, capture, sent): + if not len(capture) == len(sent): + # filter out any IPv6 RAs from the captur + for p in capture: + if (p.haslayer(IPv6)): + capture.remove(p) + return capture + + def verify_capture_ip4(self, src_if, sent): + rxd = self.pg1.get_capture(65) + + try: + capture = self.verify_filter(rxd, sent) + + self.assertEqual(len(capture), len(sent)) + + for i in range(len(capture)): + tx = sent[i] + rx = capture[i] + + # the rx'd packet has the MPLS label popped + eth = rx[Ether] + self.assertEqual(eth.type, 0x800) + + tx_ip = tx[IP] + rx_ip = rx[IP] + + # check the MAC address on the RX'd packet is correctly formed + self.assertEqual(eth.dst, getmacbyip(rx_ip.dst)) + + self.assertEqual(rx_ip.src, tx_ip.src) + self.assertEqual(rx_ip.dst, tx_ip.dst) + # IP processing post pop has decremented the TTL + self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl) + + except: + raise + + def verify_capture_ip6(self, src_if, sent): + capture = self.pg1.get_capture(65) + + self.assertEqual(len(capture), len(sent)) + + for i in range(len(capture)): + tx = sent[i] + rx = capture[i] + + # the rx'd packet has the MPLS label popped + eth = rx[Ether] + self.assertEqual(eth.type, 0x86DD) + + tx_ip = tx[IPv6] + rx_ip = rx[IPv6] + + # check the MAC address on the RX'd packet is correctly formed + self.assertEqual(eth.dst, getmacbyip6(rx_ip.dst)) + + self.assertEqual(rx_ip.src, tx_ip.src) + self.assertEqual(rx_ip.dst, tx_ip.dst) + # IP processing post pop has decremented the TTL + self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim) + + def test_ip_mcast(self): + """ IP Multicast Replication """ + + # + # a stream that matches the default route. gets dropped. + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1") + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + self.pg0.assert_nothing_captured( + remark="IP multicast packets forwarded on default route") + + # + # A (*,G). + # one accepting interface, pg0, 3 forwarding interfaces + # + route_232_1_1_1 = IpMRoute( + self, + "0.0.0.0", + "232.1.1.1", 32, + MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE, + [MRoutePath(self.pg0.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT), + MRoutePath(self.pg1.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD), + MRoutePath(self.pg2.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD), + MRoutePath(self.pg3.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)]) + route_232_1_1_1.add_vpp_config() + + # + # An (S,G). + # one accepting interface, pg0, 2 forwarding interfaces + # + route_1_1_1_1_232_1_1_1 = IpMRoute( + self, + "1.1.1.1", + "232.1.1.1", 64, + MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE, + [MRoutePath(self.pg0.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT), + MRoutePath(self.pg1.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD), + MRoutePath(self.pg2.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)]) + route_1_1_1_1_232_1_1_1.add_vpp_config() + + # + # An (*,G/m). + # one accepting interface, pg0, 1 forwarding interfaces + # + route_232 = IpMRoute( + self, + "0.0.0.0", + "232.0.0.0", 8, + MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE, + [MRoutePath(self.pg0.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT), + MRoutePath(self.pg1.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)]) + route_232.add_vpp_config() + + # + # a stream that matches the route for (1.1.1.1,232.1.1.1) + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1") + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # We expect replications on Pg1, 2, + self.verify_capture_ip4(self.pg1, tx) + self.verify_capture_ip4(self.pg2, tx) + + # no replications on Pg0 + self.pg0.assert_nothing_captured( + remark="IP multicast packets forwarded on PG0") + self.pg3.assert_nothing_captured( + remark="IP multicast packets forwarded on PG3") + + # + # a stream that matches the route for (*,232.0.0.0/8) + # Send packets with the 9th bit set so we test the correct clearing + # of that bit in the mac rewrite + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.255.255.255") + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # We expect replications on Pg1 only + self.verify_capture_ip4(self.pg1, tx) + + # no replications on Pg0, Pg2 not Pg3 + self.pg0.assert_nothing_captured( + remark="IP multicast packets forwarded on PG0") + self.pg2.assert_nothing_captured( + remark="IP multicast packets forwarded on PG2") + self.pg3.assert_nothing_captured( + remark="IP multicast packets forwarded on PG3") + + # + # a stream that matches the route for (*,232.1.1.1) + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip4(self.pg0, "1.1.1.2", "232.1.1.1") + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # We expect replications on Pg1, 2, 3. + self.verify_capture_ip4(self.pg1, tx) + self.verify_capture_ip4(self.pg2, tx) + self.verify_capture_ip4(self.pg3, tx) + + # no replications on Pg0 + self.pg0.assert_nothing_captured( + remark="IP multicast packets forwarded on PG0") + + route_232_1_1_1.remove_vpp_config() + route_1_1_1_1_232_1_1_1.remove_vpp_config() + route_232.remove_vpp_config() + + def test_ip6_mcast(self): + """ IPv6 Multicast Replication """ + + # + # a stream that matches the default route. gets dropped. + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1") + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + self.pg0.assert_nothing_captured( + remark="IPv6 multicast packets forwarded on default route") + + # + # A (*,G). + # one accepting interface, pg0, 3 forwarding interfaces + # + route_ff01_1 = IpMRoute( + self, + "::", + "ff01::1", 128, + MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE, + [MRoutePath(self.pg0.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT), + MRoutePath(self.pg1.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD), + MRoutePath(self.pg2.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD), + MRoutePath(self.pg3.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)], + is_ip6=1) + route_ff01_1.add_vpp_config() + + # + # An (S,G). + # one accepting interface, pg0, 2 forwarding interfaces + # + route_2001_ff01_1 = IpMRoute( + self, + "2001::1", + "ff01::1", 256, + MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE, + [MRoutePath(self.pg0.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT), + MRoutePath(self.pg1.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD), + MRoutePath(self.pg2.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)], + is_ip6=1) + route_2001_ff01_1.add_vpp_config() + + # + # An (*,G/m). + # one accepting interface, pg0, 1 forwarding interface + # + route_ff01 = IpMRoute( + self, + "::", + "ff01::", 16, + MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE, + [MRoutePath(self.pg0.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT), + MRoutePath(self.pg1.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)], + is_ip6=1) + route_ff01.add_vpp_config() + + # + # a stream that matches the route for (*, ff01::/16) + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip6(self.pg0, "2002::1", "ff01:2::255") + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # We expect replications on Pg1 + self.verify_capture_ip6(self.pg1, tx) + + # no replications on Pg0, Pg3 + self.pg0.assert_nothing_captured( + remark="IP multicast packets forwarded on PG0") + self.pg2.assert_nothing_captured( + remark="IP multicast packets forwarded on PG2") + self.pg3.assert_nothing_captured( + remark="IP multicast packets forwarded on PG3") + + # + # a stream that matches the route for (*,ff01::1) + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip6(self.pg0, "2002::2", "ff01::1") + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # We expect replications on Pg1, 2, 3. + self.verify_capture_ip6(self.pg1, tx) + self.verify_capture_ip6(self.pg2, tx) + self.verify_capture_ip6(self.pg3, tx) + + # no replications on Pg0 + self.pg0.assert_nothing_captured( + remark="IPv6 multicast packets forwarded on PG0") + + # + # a stream that matches the route for (2001::1, ff00::1) + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1") + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # We expect replications on Pg1, 2, + self.verify_capture_ip6(self.pg1, tx) + self.verify_capture_ip6(self.pg2, tx) + + # no replications on Pg0, Pg3 + self.pg0.assert_nothing_captured( + remark="IP multicast packets forwarded on PG0") + self.pg3.assert_nothing_captured( + remark="IP multicast packets forwarded on PG3") + + route_ff01.remove_vpp_config() + route_ff01_1.remove_vpp_config() + route_2001_ff01_1.remove_vpp_config() + + def _mcast_connected_send_stream(self, dst_ip): + self.vapi.cli("clear trace") + tx = self.create_stream_ip4(self.pg0, + self.pg0.remote_ip4, + dst_ip) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # We expect replications on Pg1. + self.verify_capture_ip4(self.pg1, tx) + + return tx + + def test_ip_mcast_connected(self): + """ IP Multicast Connected Source check """ + + # + # A (*,G). + # one accepting interface, pg0, 1 forwarding interfaces + # + route_232_1_1_1 = IpMRoute( + self, + "0.0.0.0", + "232.1.1.1", 32, + MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE, + [MRoutePath(self.pg0.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT), + MRoutePath(self.pg1.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)]) + + route_232_1_1_1.add_vpp_config() + route_232_1_1_1.update_entry_flags( + MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED) + + # + # Now the (*,G) is present, send from connected source + # + tx = self._mcast_connected_send_stream("232.1.1.1") + + # + # Constrct a representation of the signal we expect on pg0 + # + signal_232_1_1_1_itf_0 = MFibSignal(self, + route_232_1_1_1, + self.pg0.sw_if_index, + tx[0]) + + # + # read the only expected signal + # + signals = self.vapi.mfib_signal_dump() + + self.assertEqual(1, len(signals)) + + signal_232_1_1_1_itf_0.compare(signals[0]) + + # + # reading the signal allows for the generation of another + # so send more packets and expect the next signal + # + tx = self._mcast_connected_send_stream("232.1.1.1") + + signals = self.vapi.mfib_signal_dump() + self.assertEqual(1, len(signals)) + signal_232_1_1_1_itf_0.compare(signals[0]) + + # + # A Second entry with connected check + # one accepting interface, pg0, 1 forwarding interfaces + # + route_232_1_1_2 = IpMRoute( + self, + "0.0.0.0", + "232.1.1.2", 32, + MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE, + [MRoutePath(self.pg0.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT), + MRoutePath(self.pg1.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)]) + + route_232_1_1_2.add_vpp_config() + route_232_1_1_2.update_entry_flags( + MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED) + + # + # Send traffic to both entries. One read should net us two signals + # + signal_232_1_1_2_itf_0 = MFibSignal(self, + route_232_1_1_2, + self.pg0.sw_if_index, + tx[0]) + tx = self._mcast_connected_send_stream("232.1.1.1") + tx2 = self._mcast_connected_send_stream("232.1.1.2") + + # + # read the only expected signal + # + signals = self.vapi.mfib_signal_dump() + + self.assertEqual(2, len(signals)) + + signal_232_1_1_1_itf_0.compare(signals[1]) + signal_232_1_1_2_itf_0.compare(signals[0]) + + route_232_1_1_1.remove_vpp_config() + route_232_1_1_2.remove_vpp_config() + + def test_ip_mcast_signal(self): + """ IP Multicast Signal """ + + # + # A (*,G). + # one accepting interface, pg0, 1 forwarding interfaces + # + route_232_1_1_1 = IpMRoute( + self, + "0.0.0.0", + "232.1.1.1", 32, + MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE, + [MRoutePath(self.pg0.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT), + MRoutePath(self.pg1.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)]) + + route_232_1_1_1.add_vpp_config() + route_232_1_1_1.update_entry_flags( + MRouteEntryFlags.MFIB_ENTRY_FLAG_SIGNAL) + + # + # Now the (*,G) is present, send from connected source + # + tx = self._mcast_connected_send_stream("232.1.1.1") + + # + # Constrct a representation of the signal we expect on pg0 + # + signal_232_1_1_1_itf_0 = MFibSignal(self, + route_232_1_1_1, + self.pg0.sw_if_index, + tx[0]) + + # + # read the only expected signal + # + signals = self.vapi.mfib_signal_dump() + + self.assertEqual(1, len(signals)) + + signal_232_1_1_1_itf_0.compare(signals[0]) + + # + # reading the signal allows for the generation of another + # so send more packets and expect the next signal + # + tx = self._mcast_connected_send_stream("232.1.1.1") + + signals = self.vapi.mfib_signal_dump() + self.assertEqual(1, len(signals)) + signal_232_1_1_1_itf_0.compare(signals[0]) + + # + # Set the negate-signal on the accepting interval - the signals + # should stop + # + route_232_1_1_1.update_path_flags( + self.pg0.sw_if_index, + (MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT | + MRouteItfFlags.MFIB_ITF_FLAG_NEGATE_SIGNAL)) + + tx = self._mcast_connected_send_stream("232.1.1.1") + + signals = self.vapi.mfib_signal_dump() + self.assertEqual(0, len(signals)) + + # + # Clear the SIGNAL flag on the entry and the signals should + # come back since the interface is still NEGATE-SIGNAL + # + route_232_1_1_1.update_entry_flags( + MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE) + + tx = self._mcast_connected_send_stream("232.1.1.1") + + signals = self.vapi.mfib_signal_dump() + self.assertEqual(1, len(signals)) + signal_232_1_1_1_itf_0.compare(signals[0]) + + # + # Lastly remove the NEGATE-SIGNAL from the interface and the + # signals should stop + # + route_232_1_1_1.update_path_flags(self.pg0.sw_if_index, + MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT) + + tx = self._mcast_connected_send_stream("232.1.1.1") + signals = self.vapi.mfib_signal_dump() + self.assertEqual(0, len(signals)) + + # + # Cleanup + # + route_232_1_1_1.remove_vpp_config() + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/test_mfib.py b/test/test_mfib.py new file mode 100644 index 00000000..4d0d2a3f --- /dev/null +++ b/test/test_mfib.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +import unittest + +from framework import VppTestCase, VppTestRunner + + +class TestMFIB(VppTestCase): + """ MFIB Test Case """ + + def setUp(self): + super(TestMFIB, self).setUp() + + def test_mfib(self): + """ MFIB Unit Tests """ + error = self.vapi.cli("test mfib") + + if error: + self.logger.critical(error) + self.assertEqual(error.find("Failed"), -1) + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_interface.py b/test/vpp_interface.py index ee4a9ef6..29edb70e 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -260,9 +260,10 @@ class VppInterface(object): """Configure IPv6 RA suppress on the VPP interface.""" self.test.vapi.sw_interface_ra_suppress(self.sw_if_index) - def ip6_ra_config(self, suppress=0, send_unicast=0): + def ip6_ra_config(self, no=0, suppress=0, send_unicast=0): """Configure IPv6 RA suppress on the VPP interface.""" self.test.vapi.ip6_sw_interface_ra_config(self.sw_if_index, + no, suppress, send_unicast) diff --git a/test/vpp_ip_route.py b/test/vpp_ip_route.py index fc9133fb..1804fbbd 100644 --- a/test/vpp_ip_route.py +++ b/test/vpp_ip_route.py @@ -11,7 +11,7 @@ MPLS_IETF_MAX_LABEL = 0xfffff MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1 -class RoutePath: +class RoutePath(object): def __init__( self, @@ -31,6 +31,14 @@ class RoutePath: self.nh_addr = socket.inet_pton(socket.AF_INET, nh_addr) +class MRoutePath(RoutePath): + + def __init__(self, nh_sw_if_index, flags): + super(MRoutePath, self).__init__("0.0.0.0", + nh_sw_if_index) + self.nh_i_flags = flags + + class IpRoute: """ IP Route @@ -94,6 +102,97 @@ class IpRoute: is_add=0) +class IpMRoute: + """ + IP Multicast Route + """ + + def __init__(self, test, src_addr, grp_addr, + grp_addr_len, e_flags, paths, table_id=0, is_ip6=0): + self._test = test + self.paths = paths + self.grp_addr_len = grp_addr_len + self.table_id = table_id + self.e_flags = e_flags + self.is_ip6 = is_ip6 + + if is_ip6: + self.grp_addr = socket.inet_pton(socket.AF_INET6, grp_addr) + self.src_addr = socket.inet_pton(socket.AF_INET6, src_addr) + else: + self.grp_addr = socket.inet_pton(socket.AF_INET, grp_addr) + self.src_addr = socket.inet_pton(socket.AF_INET, src_addr) + + def add_vpp_config(self): + for path in self.paths: + self._test.vapi.ip_mroute_add_del(self.src_addr, + self.grp_addr, + self.grp_addr_len, + self.e_flags, + path.nh_itf, + path.nh_i_flags, + table_id=self.table_id, + is_ipv6=self.is_ip6) + + def remove_vpp_config(self): + for path in self.paths: + self._test.vapi.ip_mroute_add_del(self.src_addr, + self.grp_addr, + self.grp_addr_len, + self.e_flags, + path.nh_itf, + path.nh_i_flags, + table_id=self.table_id, + is_add=0, + is_ipv6=self.is_ip6) + + def update_entry_flags(self, flags): + self.e_flags = flags + self._test.vapi.ip_mroute_add_del(self.src_addr, + self.grp_addr, + self.grp_addr_len, + self.e_flags, + 0xffffffff, + 0, + table_id=self.table_id, + is_ipv6=self.is_ip6) + + def update_path_flags(self, itf, flags): + for path in self.paths: + if path.nh_itf == itf: + path.nh_i_flags = flags + break + self._test.vapi.ip_mroute_add_del(self.src_addr, + self.grp_addr, + self.grp_addr_len, + self.e_flags, + path.nh_itf, + path.nh_i_flags, + table_id=self.table_id, + is_ipv6=self.is_ip6) + + +class MFibSignal: + def __init__(self, test, route, interface, packet): + self.route = route + self.interface = interface + self.packet = packet + self.test = test + + def compare(self, signal): + self.test.assertEqual(self.interface, signal.sw_if_index) + self.test.assertEqual(self.route.table_id, signal.table_id) + self.test.assertEqual(self.route.grp_addr_len, + signal.grp_address_len) + for i in range(self.route.grp_addr_len / 8): + self.test.assertEqual(self.route.grp_addr[i], + signal.grp_address[i]) + if (self.route.grp_addr_len > 32): + for i in range(4): + self.test.assertEqual(self.route.src_addr[i], + signal.src_address[i]) + + class MplsIpBind: """ MPLS to IP Binding diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 1b2895e9..90c954dc 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -252,10 +252,12 @@ class VppPapiProvider(object): {'sw_if_index': sw_if_index}) def ip6_sw_interface_ra_config(self, sw_if_index, + no, suppress, - send_unicast,): + send_unicast): return self.api(self.papi.sw_interface_ip6nd_ra_config, {'sw_if_index': sw_if_index, + 'is_no': no, 'suppress': suppress, 'send_unicast': send_unicast}) @@ -1178,3 +1180,33 @@ class VppPapiProvider(object): 'is_add': is_add, 'oui': oui, }) + + def ip_mroute_add_del(self, + src_address, + grp_address, + grp_address_length, + e_flags, + next_hop_sw_if_index, + i_flags, + table_id=0, + create_vrf_if_needed=0, + is_add=1, + is_ipv6=0, + is_local=0): + """ + """ + return self.api( + self.papi.ip_mroute_add_del, + {'next_hop_sw_if_index': next_hop_sw_if_index, + 'entry_flags': e_flags, + 'itf_flags': i_flags, + 'create_vrf_if_needed': create_vrf_if_needed, + 'is_add': is_add, + 'is_ipv6': is_ipv6, + 'is_local': is_local, + 'grp_address_length': grp_address_length, + 'grp_address': grp_address, + 'src_address': src_address}) + + def mfib_signal_dump(self): + return self.api(self.papi.mfib_signal_dump, {}) -- cgit 1.2.3-korg